Home/Transactional Email/SMTP ports/PowerShell send email SMTP

PowerShell send email SMTP: Send-MailMessage, why it is obsolete, and what changes when an agent writes the script

Sending email over SMTP in PowerShell is done with the Send-MailMessage cmdlet, which Microsoft has formally marked obsolete. Ask an agent for a PowerShell email script and it will usually write Send-MailMessage with a password in the script. Microsoft states that the cmdlet does not guarantee secure connections to SMTP servers and recommends against using it, while noting that no immediate replacement exists in PowerShell.

VerifiedBy Kam Low, Co-founder and CTO·Updated

The obsolete cmdlet and what its status means

Microsoft's reference for Send-MailMessage carries an explicit warning rather than a soft deprecation note. The cmdlet remains present and functional in PowerShell, so existing scripts continue to run, but its documented status is obsolete and its use is not recommended.

The reason given is the security guarantee, not the feature set. Send-MailMessage cannot guarantee an encrypted connection to the server, which is what disqualifies it for anything carrying credentials or personal data across an untrusted network.

The absence of a built-in replacement is why the cmdlet is still encountered. Microsoft's guidance points to the platform compatibility note DE0005 rather than to another cmdlet, so replacing it means adopting an external library or a service API rather than a different built-in command.

Required parameters

Three parameters are mandatory: From, To and Subject. A message missing any of them fails before a connection is attempted.

Send-MailMessage parameters

required and common
Parameter
Required
What it does
From
Yes
Sender address
To
Yes
Recipient addresses
Subject
Yes
Subject line
SmtpServer
Yes, unless $PSEmailServer is set
The server the cmdlet connects to
Port
No
Selects the submission port
UseSsl
No
Enables TLS, without changing Microsoft's warning
Body, BodyAsHtml
No
Message text, and the switch that marks it as HTML
Attachments
No
One or more file paths
Cc, Bcc
No
Additional recipient addresses
Credential
No
A PSCredential object, never a plaintext password

A server must also be specified, either through the SmtpServer parameter or by setting the $PSEmailServer preference variable, which supplies the default for the session. Without one, the command fails.

The Port parameter selects the submission port, covered in SMTP ports and which one to use, and UseSsl enables TLS. The cmdlet's encryption behavior is what Microsoft's warning concerns, so neither switch makes the cmdlet suitable for sensitive traffic on an untrusted path.

What an agent does differently with a PowerShell mail script

Asked for a script that emails a report, an agent reaches for Send-MailMessage. Most published examples predate Microsoft's warning, so the generated script looks correct and runs.

Four habits follow from that starting point.

  • It writes the obsolete cmdlet, usually with UseSsl added, which does not change the warning Microsoft attaches to it.
  • It embeds the credential, building a PSCredential from a plaintext password inside the .ps1 file so the script runs without a prompt.
  • It schedules the script to run unattended, which means nobody reads the output when a send fails.
  • It wraps the send in a retry loop. The cmdlet has no idempotency, so a retry after a partial failure can send a message the server already accepted.

Each habit is reasonable in isolation. Together they produce a job that sends with a stored password through an obsolete cmdlet, on a schedule, with retries that can duplicate mail.

Common parameters

Body carries the message text, and BodyAsHtml marks that text as HTML rather than plain text. Without the switch, HTML markup is delivered literally as characters.

Attachments takes one or more file paths. Cc and Bcc take additional recipient addresses, following the same address rules as To.

Credential accepts a PSCredential object rather than a plaintext password argument. Building one from a hardcoded string in the script defeats the purpose, so the credential belongs in a secret store or an encrypted file read at runtime.

Authentication errors on major providers

An account password fails against Google and Microsoft accounts with multi-factor authentication enabled. The SMTP client cannot complete the second factor, so the provider rejects the credential outright rather than prompting.

The working credential on those providers is an application-specific password generated in the account security settings, or an OAuth token. Send-MailMessage supports the former through the Credential parameter and does not implement OAuth.

Credential scope produces the same symptom from the other direction. A least-privilege API key can be rejected by a check that needs a wider scope, so a rejected credential is not always a wrong credential.

Microsoft has been retiring basic authentication for client SMTP submission, pointing at OAuth instead. A script authenticating with a password against Microsoft 365 is running against a mechanism scheduled for removal rather than a stable interface.

Where an internal relay still fits

An unauthenticated internal relay remains common in IT automation, where a server on a trusted network accepts mail from known internal hosts without credentials. That is a network-boundary control rather than an authentication one.

Its scope is narrow. Alerts to internal recipients through an internal relay never traverse the public internet, and the mail is not subject to another organization's filtering.

A relay that faces anything wider than a trusted network needs the opposite posture. The port is firewall-restricted to known hosts, TLS and SMTP authentication are mandatory, and arbitrary destinations, extra recipients and unsigned or replayed requests are rejected.

A script that sends through an obsolete cmdlet against a mechanism a provider is retiring will eventually stop sending. Nobody notices, because nobody misses a message they never expected to see.

Mail leaving the organization is a different problem. Once a message goes to an external recipient, domain authentication and sender reputation govern whether it arrives, and an internal relay provides neither delivery status nor bounce handling.

Worked example: replace a scheduled PowerShell report email

The prompt, in the user's words: Our nightly PowerShell job emails a report with Send-MailMessage. Move it to something supported.

Before, unguided: the agent keeps Send-MailMessage, adds UseSsl and a stored password, and leaves the scheduled task as it was. The report still goes through an obsolete cmdlet, and nobody looks at the message before the next run.

After, with the agent working through Nitrosend's MCP server, the move is a gated sequence:

  1. nitro_get_status gets the account context, sender identity and readiness.
  2. nitro_manage_domains adds and verifies the sending domain the report comes from.
  3. nitro_manage_template creates a reusable email template for the nightly report.
  4. nitro_review_delivery runs a read-only review with validation, spam analysis, preflight and previews. It never sends.
  5. A single real send of the template follows through nitro_send_test_message, addressed only to the test recipients from nitro_configure_account.
  6. A person checks the test before the scheduled job changes.
  7. The job drops Send-MailMessage and sends through the transactional email API with an idempotency key per night, so a retried run does not send the report twice.
  8. nitro_get_insights returns email analytics with trends, benchmarks and recommendations once the reports are flowing.

The report copy does not need to be hand-written either. Nitrosend's platform includes an AI agent responsible for most of the email writing, so the template step can start from a description of the report.

The job keeps its schedule. What changes is that the credential is an API key held in the job's environment, and that the email was reviewed and tested once before it ran unattended. The email API documents the request the job makes.

Where it goes wrong when an agent writes the script

  • A password in the .ps1 file or the job history. A credential built from a plaintext string ends up in source control, and a transcript or task log can capture it.
  • A scheduled job that retries and double-sends. Send-MailMessage has no idempotency, so a loop that re-runs after a timeout can resend a report the server already accepted.
  • Basic auth that the provider has turned off. The agent writes a password login against Microsoft 365 or Google, the provider rejects it, and the failure reads like a wrong password.
  • The report sent to a recipient list the model guessed. Addresses in the script that come from no config file or directory are a sign the model filled them in, so they need checking before the first scheduled run.
  • Plan limits on an unattended job. On Nitrosend pricing, the Free plan allows 100 to 5,000 recipients per rolling 24 hours by sender standing, and a new account starts at the low end.

Why application mail moves off SMTP cmdlets

The cmdlet reports whether the server accepted the message and nothing after that. Bounces, deferrals and spam-folder placement all happen after acceptance, so a script that completes without error has not established that anything was delivered.

Obsolete · no built-in replacement
PS> Send-MailMessage -SmtpServer …
WARNING: This cmdlet is obsolete and does not guarantee secure connections. There is no built-in replacement.
The replacement · authenticated API
POST transactional email API
Idempotency-Key: nightly-report-2026-10-05
accepted · message id returned
per-message status, safe to retry
The cmdlet still runs, which is the trap: nothing breaks on the day the mail stops arriving.
Microsoft marked the cmdlet obsolete and shipped no successor. Application mail moves to an authenticated API with per-message status.

There is no retry, queue or idempotency behavior. A transient failure surfaces as a terminating error for the script to handle, and a re-run after a partial failure can duplicate messages already accepted.

An HTTP API replaces both gaps. A request returns a message identifier that can be queried for status and accepts an idempotency key so a retried request does not send twice, neither of which the SMTP conversation provides.

Authentication changes shape with it. An API key held server-side in the script's environment does the authenticating, which is what API keys are for, and no mailbox credential or second factor is involved.

Nitrosend has used the agent path on its own mail. An AI agent built and sent the launch email for a new Nitrosend feature, and George Hartley, co-founder, approved it and did nothing else.

The API replaces the cmdlet for a script that still has a person maintaining it. For an AI agent it goes one step further. Through Nitrosend's MCP server the agent calls the same gated sequence as the worked example above, so nobody hand-maintains a script whose only job is Send-MailMessage with better error handling.

FAQ

Is Send-MailMessage still supported in PowerShell?

Send-MailMessage still runs, so existing scripts keep sending, but Microsoft's reference marks it obsolete and recommends against using it because it does not guarantee secure connections to SMTP servers. Microsoft names no built-in replacement and points to compatibility note DE0005 instead. The cmdlet still requires 3 parameters: From, To and Subject.

What replaces Send-MailMessage for application mail?

An external library or a service API, because PowerShell ships no successor cmdlet. A transactional email API authenticates with an API key held in the job's environment, returns a message identifier that can be queried for status, and accepts an idempotency key. That covers the 2 gaps the cmdlet leaves: delivery status and safe retries.

Can a scheduled PowerShell job retry a send safely?

Not with Send-MailMessage. The cmdlet has no idempotency, so a re-run after a timeout can resend a message the server already accepted. Through a transactional email API, the job sends 1 idempotency key per night, so a retried run does not send the report twice, and the scheduled task itself can stay as it is.

Go deeper

First send in thirty seconds.

Simple pricing. Unlimited contacts.

Every plan includes full stack emailing: Flows, Newsletter Campaigns and Transactional Email, plus our NitroWheel LLM and all agent integrations (Claude, ChatGPT, Codex, Cursor and others). Pay for what you send, not who you store.

Choose your billing interval

Every paid plan includes 3 agent inboxes and no Nitrosend footer.

Starter

For your first email program.

$16USD / month

$192 billed yearly.

20,000 emails / month

Choose Starter
  • 5 brands · 3 seats
  • 5 custom sending domains
  • 500 email verifications/month
  • $1.50 / 1,000 extra emails

Pro

For a growing audience.

$40USD / month

$480 billed yearly.

60,000 emails / month

Choose Pro
  • 10 brands · 5 seats
  • 10 custom sending domains
  • 1,000 email verifications/month
  • $1.20 / 1,000 extra emails

Growth

For teams and agencies.

$120USD / month

$1,440 billed yearly.

200,000 emails / month

Choose Growth
  • Unlimited brands · 10 seats
  • 25 custom sending domains
  • 2,500 email verifications/month
  • $0.90 / 1,000 extra emails

Scale

For high-volume sending.

$240USD / month

$2,880 billed yearly.

500,000 emails / month

Choose Scale
  • Unlimited brands · 20 seats
  • 100 custom sending domains
  • 5,000 email verifications/month
  • $0.70 / 1,000 extra emails
Enterprise

Hey, big sender.

Sending 500,000+ emails a month? Get a custom plan designed for you, with volume email pricing and capacity that fits your business.

Start free. No card needed.

5,000 emails to start, then 500 emails every month. No expiry on your starter emails.

Start free

Daily allowances depend on your plan and sender standing. Strong list, domain and delivery evidence can raise standing, including on day one. Trusted receives the full plan allowance; available email credits, safety checks and delivery pacing still apply.

Free forever. No credit card required. See full comparison →