Guide

Send-MgUserMail vs Send-MailMessage

Compare Send-MgUserMail and Send-MailMessage for PowerShell email in 2026. Learn what changed, why both routes add setup work, and how one CLI command can replace common send scripts.

Written by Qasim Muhammad Staff SRE

Reviewed by Qasim Muhammad

VerifiedCLI 3.1.1 · Outlook, Exchange · last tested May 14, 2026

What is the difference in 2026?

Send-MailMessage is the older SMTP cmdlet marked obsolete in PowerShell 7, while Send-MgUserMail sends mail through Microsoft Graph. In 2026, the practical difference is security model: one depends on SMTP settings, and the other depends on Graph scopes and consent.

Microsoft's Send-MailMessage docs warn that the cmdlet is obsolete. The Send-MgUserMail docs point to Graph permissions and a message payload. Both can send email, but neither is a tiny drop-in for every script.

The underlying Graph API is documented as user: sendMail. That reference is useful when you need to inspect payload fields, save-to-sent behavior, and Graph permission requirements before choosing a migration route.

The choice is not only old cmdlet versus new cmdlet. It is also whether a notification script should be tied to Microsoft 365 at all. If the same automation may later send from Gmail, Exchange, an Agent Account, or an IMAP-backed mailbox, a provider-neutral command is easier to carry forward than a Graph-only PowerShell module.

Why do both paths become heavy?

The old SMTP path becomes heavy because every script needs server, port, credential, and TLS decisions. The Graph path becomes heavy because every script needs a Microsoft identity setup, a connection step, Mail.Send permission, and a structured message body before the send call.

This minimal Graph PowerShell shape is accurate for a simple text email, but it still has 3 moving parts: connect, build the body parameter, and send. Tenant admin consent, delegated versus application permissions, and throttling behavior sit outside the snippet.

Connect-MgGraph -Scopes "Mail.Send"

$params = @{
  Message = @{
    Subject = "Daily report"
    Body = @{
      ContentType = "Text"
      Content = "Report is ready"
    }
    ToRecipients = @(
      @{
        EmailAddress = @{
          Address = "ops@example.com"
        }
      }
    )
  }
  SaveToSentItems = $true
}

Send-MgUserMail -UserId "me" -BodyParameter $params

How does one CLI command compare?

One CLI command is enough for the common PowerShell send case: recipient, subject, body, and non-interactive confirmation. The same syntax works for Outlook, Exchange, Gmail, Yahoo, iCloud, and IMAP accounts, so the script does not fork into Microsoft-only and non-Microsoft branches.

Run the command below from PowerShell, Task Scheduler, GitHub Actions, or any CI job after nylas auth config --api-key. It skips an interactive prompt with --yes and can emit JSON for logging with the global --json flag.

Link directly to nylas email send when documenting this replacement. The command page lists the send flags, global JSON flag, scheduled-send option, hosted template options, and confirmation behavior that a migration reviewer needs to inspect.

nylas email send --to ops@example.com --subject "Daily report" --body "Report is ready" --yes --json

How does authentication compare?

Send-MailMessage usually depends on an SMTP credential or relay. Send-MgUserMail depends on Microsoft Graph authentication and the Mail.Send permission. The CLI path depends on one Nylas API key configured once per host, then a grant selection for the account that should send the message.

For unattended PowerShell, keep auth explicit. Use nylas auth config to store the API key on the runner, nylas auth status to fail fast when credentials are missing, and nylas auth list to confirm which grants the script can use.

$env:NYLAS_API_KEY = "nyk_..."
nylas auth config --api-key $env:NYLAS_API_KEY
nylas auth status --json
nylas auth list --json

How should you migrate a script?

Migrate the lowest-risk 5 scripts first: daily reports, build alerts, monitoring emails, user notifications, and one-off admin sends. These usually map cleanly from -To, -Subject, and -Body to --to, --subject, and --body.

Old fieldGraph PowerShell fieldCLI flag
-ToToRecipients--to
-SubjectMessage.Subject--subject
-BodyMessage.Body.Content--body
SMTP credentialConnect-MgGraphnylas auth config

Keep the deeper migration guides open for edge cases: SMTP relay retirement, tenant consent, shared mailboxes, and Graph-only workflows can need more review than a simple notification script.

What does a safe PowerShell wrapper look like?

A safe wrapper should pass fields as arguments, return JSON, and stop the job when the send fails. Keep the first version under 30 lines: a function that accepts recipient, subject, and body; invokes the CLI; checks $LASTEXITCODE; and writes a structured log entry.

This wrapper is intentionally small. It does not hide the command from reviewers, and it keeps the exact recipient, subject, and command status visible in logs. Add attachments, templates, and scheduling only after the basic send path has run in production for at least 1 release cycle.

function Send-NylasNotification {
  param(
    [Parameter(Mandatory=$true)] [string] $To,
    [Parameter(Mandatory=$true)] [string] $Subject,
    [Parameter(Mandatory=$true)] [string] $Body
  )

  $result = nylas email send --to $To --subject $Subject --body $Body --yes --json
  if ($LASTEXITCODE -ne 0) {
    throw "nylas email send failed for $To"
  }

  $result | ConvertFrom-Json
}

When should PowerShell use an Agent Account?

Use an Agent Account when the script should send from an app-owned address rather than a licensed Microsoft user. A provider=nylas Agent Account has its own mailbox, calendar, webhooks, and policy rules. That makes it a better fit for alerts@, reports@, and support-agent@ identities than a shared human mailbox.

The setup is nylas agent account create followed by nylas agent status. Once the grant exists, the same nylas email send command sends through that identity, while policy and rule commands can block risky outbound recipients before the message leaves.

When should you stay on Graph PowerShell?

Stay on Graph PowerShell when the script is doing Microsoft 365 administration, not just sending a message. Examples include user lifecycle automation, mailbox settings, compliance labels, tenant reports, and workflows that already depend on other Graph cmdlets in the same transaction. In those cases, keeping Send-MgUserMail beside the rest of the Graph module may be the simpler operational choice.

Move to the CLI when the script is mostly a notification sender. A 5-line status email, a nightly report, or a CI failure alert should not require every future maintainer to understand Graph app registration, delegated versus application permissions, and consent prompts. The smaller the email role, the more valuable the command boundary becomes.

Use this rule of thumb during review: if removing the send line leaves a Microsoft 365 admin script, keep Graph. If removing the send line leaves a generic job that happens to notify people, use nylas email send or an Agent Account sender.

What should the replacement log?

Log 4 fields for every migrated send: recipient count, subject, command exit code, and message ID from JSON output. Do not log full bodies by default. That gives enough evidence to debug delivery without putting customer text or internal report contents into build logs.

For scheduled PowerShell jobs, store the JSON response beside the job run ID for at least 30 days. If the job sends from an Agent Account, pair the send log with webhook events such as message.send_success or message.send_failed. That creates a trace from script execution to mail delivery.

Next steps