Source: https://cli.nylas.com/guides/send-mgusermail-powershell

# Send-MgUserMail: Graph Email in PowerShell

Microsoft deprecated Send-MailMessage and pointed developers to the Graph PowerShell SDK. The replacement cmdlet, Send-MgUserMail, works but demands Azure AD app registration, Mail.Send permissions, and deeply nested hashtables. This guide walks through the full Graph workflow, then shows a 1-command alternative.

Written by [Nick Barraclough](https://cli.nylas.com/authors/nick-barraclough) Product Manager

Updated May 21, 2026

> **TL;DR:** `Send-MgUserMail` sends email through Microsoft Graph but requires Azure AD app registration, `Mail.Send` permissions, and 25+ lines of nested hashtables. Replace it with [`nylas email send`](https://cli.nylas.com/docs/commands/email-send) for a single-command send that works across Outlook, Gmail, Exchange, Yahoo, iCloud, and IMAP.

> **Disclosure:** Nylas CLI is built by Nylas, Inc. This comparison reflects our testing and product understanding as of May 21, 2026.

Command references used in this guide: [`nylas email send`](https://cli.nylas.com/docs/commands/email-send) for sending email and [`nylas auth config`](https://cli.nylas.com/docs/commands/auth-config) for API key authentication.

## What is Send-MgUserMail and why does it exist?

`Send-MgUserMail` is a PowerShell cmdlet in the `Microsoft.Graph.Users.Actions` module that sends email through the Microsoft Graph API. Microsoft created it as the official successor to `Send-MailMessage`, which was deprecated in PowerShell 7.0 (March 2020) because its `System.Net.Mail.SmtpClient` can't negotiate TLS securely.

The deprecation warning is direct. Running `Send-MailMessage` in PowerShell 7 prints:

```text
WARNING: The command 'Send-MailMessage' is obsolete.
This cmdlet does not guarantee secure connections to SMTP servers.
```

According to [Microsoft's Send-MailMessage documentation](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage), the recommended path is a third-party library. In practice, most Microsoft-centric teams land on the Graph PowerShell SDK. The SDK contains over 9,000 cmdlets across 40+ sub-modules, and the email-related ones live in `Microsoft.Graph.Users.Actions` and `Microsoft.Graph.Mail`.

## Which Graph cmdlets handle email?

Microsoft Graph PowerShell splits email sending into 3 cmdlets, each wrapping a different Graph API endpoint. The split confuses teams migrating from `Send-MailMessage`, which was a single cmdlet that handled everything. Understanding which cmdlet to use depends on whether you're sending immediately, dispatching a saved draft, or creating a draft for later review.

| Cmdlet | Graph API endpoint | Purpose |
| --- | --- | --- |
| `Send-MgUserMail` | [POST /users/{id}/sendMail](https://learn.microsoft.com/en-us/graph/api/user-sendmail) | Send a new email immediately (most common) |
| `Send-MgUserMessage` | [POST /users/{id}/messages/{id}/send](https://learn.microsoft.com/en-us/graph/api/message-send) | Send an existing draft (2-step: create then send) |
| `New-MgUserMessage` | [POST /users/{id}/messages](https://learn.microsoft.com/en-us/graph/api/user-post-messages) | Create a draft without sending |

For scripts that replace `Send-MailMessage`, `Send-MgUserMail` is the right choice. It sends in a single call, saves a copy to Sent Items by default, and doesn't require a separate draft step. The other 2 cmdlets are for draft workflows where a human reviews before sending.

## How do I send with delegated auth (interactive login)?

Delegated authentication uses an interactive browser login where a user signs in and consents to the `Mail.Send` scope. This is the quickest path for ad-hoc scripts and developer workstations. The full flow takes 4 steps and about 25 lines of PowerShell, compared to 8 lines for the deprecated `Send-MailMessage`.

Start by installing the Graph module. The `Microsoft.Graph.Users.Actions` sub-module weighs approximately 15 MB and pulls in the core SDK as a dependency. Installation takes 30-90 seconds depending on your PowerShell gallery mirror.

```powershell
# Step 1: Install the Graph module (one-time)
Install-Module Microsoft.Graph.Users.Actions -Scope CurrentUser -Force
```

Next, connect to Graph with the `Mail.Send` scope. This opens a browser window for authentication. The token is cached in memory for the duration of your PowerShell session, typically 60 minutes before refresh.

```powershell
# Step 2: Connect with delegated permissions
Connect-MgGraph -Scopes "Mail.Send"
```

Build the message as a nested hashtable. The `-BodyParameter` format is the part that trips up most developers. Recipients must be wrapped in an `EmailAddress` object inside a `ToRecipients` array, and the body needs both `ContentType` and `Content` keys. Getting any level of nesting wrong produces a `400 Bad Request` with a generic "Invalid request body" message.

```powershell
# Step 3: Build the message params
$params = @{
    Message = @{
        Subject = "Quarterly report"
        Body = @{
            ContentType = "HTML"
            Content = "<h1>Q2 Results</h1><p>Revenue up 12% YoY.</p>"
        }
        ToRecipients = @(
            @{
                EmailAddress = @{
                    Address = "recipient@example.com"
                }
            }
        )
    }
}

# Step 4: Send
Send-MgUserMail -UserId "you@company.com" -BodyParameter $params
```

That's 20 lines of PowerShell for a single email. The nested hashtable format is required by the Graph API's JSON schema, and there's no shorthand parameter set on the cmdlet.

## How do I send with app-only auth (unattended scripts)?

App-only authentication uses client credentials instead of an interactive login. This is the path for CI/CD pipelines, scheduled tasks, and background services that run without a user present. Setup requires 5 configuration steps in the Azure portal before writing any PowerShell, and the Azure AD app registration typically takes 10-15 minutes for someone familiar with the portal.

First, register an application in Azure AD. Navigate to [portal.azure.com](https://portal.azure.com/) > Microsoft Entra ID > App registrations > New registration. Record the Application (client) ID and Directory (tenant) ID. Then create a client secret under Certificates & secrets. Secrets expire after a maximum of 24 months, so you'll need a rotation process.

Next, grant the `Mail.Send` application permission (not delegated) under API permissions > Microsoft Graph > Application permissions. An Entra ID admin must click "Grant admin consent" before the permission takes effect. Without this step, the cmdlet returns a `403 Forbidden` error.

Once the Azure AD app is registered, connect using a certificate or client secret. The [Connect-MgGraph documentation](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/connect-mggraph) recommends certificate-based auth for production, but client secret is simpler for initial testing.

```powershell
# Connect with client credentials (app-only)
$clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$secret = Get-Secret -Name "GraphClientSecret" -AsPlainText

$secureSecret = ConvertTo-SecureString $secret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($clientId, $secureSecret)

Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential
```

After connecting, use the same `Send-MgUserMail` call from the delegated section. The `-UserId` parameter can be any mailbox in the tenant that the app permission covers, which is how shared mailboxes work with app-only auth.

## How do I add attachments with Send-MgUserMail?

Attachments in `Send-MgUserMail` are base64-encoded byte arrays nested inside the `Attachments` property of the message hashtable. Each attachment needs 4 keys: `@odata.type`, `Name`, `ContentType`, and `ContentBytes`. The Graph API accepts up to 4 MB per attachment in a single request, or up to 150 MB using the upload session API.

The code below reads a local PDF file, encodes it to base64, and embeds it in the params hashtable. For files over 4 MB, you'd need to switch to the [upload session endpoint](https://learn.microsoft.com/en-us/graph/api/attachment-createuploadsession), which adds another 15-20 lines of retry and chunk logic.

```powershell
$fileBytes = [System.IO.File]::ReadAllBytes("C:Reportsinvoice.pdf")
$base64 = [System.Convert]::ToBase64String($fileBytes)

$params = @{
    Message = @{
        Subject = "Invoice attached"
        Body = @{
            ContentType = "Text"
            Content = "Please find the March invoice."
        }
        ToRecipients = @(
            @{ EmailAddress = @{ Address = "client@example.com" } }
        )
        Attachments = @(
            @{
                "@odata.type" = "#microsoft.graph.fileAttachment"
                Name = "invoice.pdf"
                ContentType = "application/pdf"
                ContentBytes = $base64
            }
        )
    }
}

Send-MgUserMail -UserId "you@company.com" -BodyParameter $params
```

That's 22 lines for a single attachment. Each additional file adds another nested hashtable entry inside the `Attachments` array.

## How do I send from a shared mailbox?

Sending from a shared mailbox with `Send-MgUserMail` requires passing the shared mailbox address as the `-UserId` parameter. The calling user (delegated auth) or the Azure AD app (app-only auth) must have `Mail.Send` permission for that mailbox. In delegated mode, the user also needs "Send As" or "Send on Behalf" rights configured in Exchange Online, which an Exchange admin sets through the admin center or `Set-Mailbox`.

The Graph API doesn't differentiate between personal and shared mailboxes at the endpoint level. The same `/users/{id}/sendMail` endpoint handles both. The `-UserId` parameter accepts either a UPN (like `support@company.com`) or an object ID, and the Graph API routes the send through whichever mailbox matches.

```powershell
# Send from a shared mailbox
$params = @{
    Message = @{
        Subject = "Support ticket #4821 resolved"
        Body = @{
            ContentType = "Text"
            Content = "Your issue has been resolved. Reply to reopen."
        }
        ToRecipients = @(
            @{ EmailAddress = @{ Address = "customer@example.com" } }
        )
    }
}

# Use the shared mailbox UPN as -UserId
Send-MgUserMail -UserId "support@company.com" -BodyParameter $params
```

The message appears in the shared mailbox's Sent Items folder. In organizations with more than 10 shared mailboxes, managing the "Send As" permissions across users and apps becomes its own maintenance burden.

## How does the CLI compare to Send-MgUserMail?

Nylas CLI replaces the 25-line Graph PowerShell workflow with a single command. No Azure AD app registration, no `Mail.Send` permissions to configure, no nested hashtables. Authentication uses an API key stored once in your OS keychain, and the same command works across 6 providers. Setup takes under 2 minutes.

Install the CLI on Windows with the PowerShell install script. The script downloads the latest release from GitHub, verifies a SHA-256 checksum, and places the binary in `~/.config/nylas/bin`. The entire download is under 20 MB.

```powershell
# Install Nylas CLI on Windows
irm https://cli.nylas.com/install.ps1 | iex
```

For Homebrew, shell-script, and Go installs, see the [getting started guide](https://cli.nylas.com/guides/getting-started).

Authenticate once with your API key. The CLI stores it in Windows Credential Manager and reads it at runtime, so no secrets appear in your scripts. Authentication persists across sessions.

```powershell
# One-time authentication
nylas auth config
# Paste your API key from dashboard-v3.nylas.com

# Verify the connection
nylas auth whoami
```

Now send the same email that took 25 lines with `Send-MgUserMail`. The CLI version is 4 lines including the backtick continuations. It handles OAuth2 token negotiation, TLS 1.2+ enforcement, and MIME type detection automatically.

```powershell
nylas email send `
  --to "recipient@example.com" `
  --subject "Quarterly report" `
  --body "<h1>Q2 Results</h1><p>Revenue up 12% YoY.</p>" `
  --yes
```

The table below compares the two approaches across 8 dimensions. The Graph path offers deep Microsoft 365 integration (calendar, OneDrive, Teams), while the CLI prioritizes simplicity and provider portability.

| Dimension | Send-MgUserMail | nylas email send |
| --- | --- | --- |
| Lines to send 1 email | 25+ | 4 |
| Setup time | 10-15 min (Azure AD app) | Under 2 min |
| Authentication | MSAL / client credentials | API key in OS keychain |
| Providers supported | Microsoft 365 only | Outlook, Gmail, Exchange, Yahoo, iCloud, IMAP |
| Admin consent required | Yes (app-only) | No |
| Attachment support | Base64 in hashtable (4 MB inline) | Use Nylas API/SDK for MIME attachments |
| HTML detection | Manual ContentType key | Auto-detected |
| Secret rotation | Client secret expires in 24 months | API key, no expiration |

## What are common Send-MgUserMail errors?

Graph PowerShell errors are notoriously vague. The API returns HTTP status codes with generic messages, and the PowerShell module wraps them in `Microsoft.Graph.PowerShell.Authentication` exceptions that hide the original error detail. These are the 5 most common failures when using `Send-MgUserMail`, based on Stack Overflow threads from 2023-2026.

### 403 Forbidden: insufficient permissions

The Azure AD app doesn't have `Mail.Send` permission, or admin consent hasn't been granted. Check API permissions in the Azure portal under App registrations > your app > API permissions. The "Status" column must show a green checkmark with "Granted for [tenant]".

### 400 Bad Request: invalid body

The hashtable structure doesn't match the Graph API schema. The most common mistake is putting `Subject` and `Body` at the top level of `$params` instead of nested under `Message`. Another frequent cause: using `To` instead of `ToRecipients`.

### 401 Unauthorized: token expired

The `Connect-MgGraph` session token expired (default: 60 minutes for delegated, 60 minutes for app-only). Re-run `Connect-MgGraph` to refresh. For long-running scripts, add a `Connect-MgGraph` call inside the loop or use the MSAL token cache.

### ErrorSendAsDenied: mailbox permission missing

When sending from a shared mailbox, the user or app lacks "Send As" rights. An Exchange admin must grant it via `Add-RecipientPermission -Identity shared@company.com -Trustee you@company.com -AccessRights SendAs`.

### ResourceNotFound: unknown UserId

The `-UserId` value doesn't match any mailbox in the tenant. Verify the UPN or object ID with `Get-MgUser -UserId "user@company.com"`. Typos and deprovisioned accounts are the most common cause.

## Next steps

After learning the `Send-MgUserMail` workflow, these guides cover related migration paths, comparisons, and PowerShell email patterns.

- [Replace Send-MailMessage](https://cli.nylas.com/guides/replace-send-mailmessage) -- migrating from the older SMTP cmdlet instead of Graph?
- [Replace Send-MgUserMessage](https://cli.nylas.com/guides/replace-send-mgusermessage) -- the 2-step draft-then-send workflow
- [Send-MgUserMail vs Send-MailMessage](https://cli.nylas.com/guides/send-mgusermail-vs-send-mailmessage) -- side-by-side comparison of SMTP, Graph, and CLI
- [Send email from PowerShell](https://cli.nylas.com/guides/send-email-powershell) -- advanced patterns, scheduling, and scripting
- [Manage Office 365 email from PowerShell](https://cli.nylas.com/guides/office365-email-powershell) -- read, search, and organize your M365 inbox
- [Full command reference](https://cli.nylas.com/docs/commands) -- every flag and subcommand documented
