Send-MailMessage and scripts after Microsoft 365 SMTP Basic auth is off
Backup jobs, scheduled tasks, monitoring checks and old line-of-business apps often send mail with Send-MailMessage (or Python's smtplib, blat, System.Net.Mail) by logging in to smtp.office365.com:587 with a username and password. That login is Basic auth. Microsoft disables it by default in existing tenants at the end of December 2026, and the scripts start failing with 550 5.7.30 or 535 5.7.139. Here's how to move each kind of script to something that keeps working.
What breaks, and how you'll notice
Send-MailMessage : The SMTP server requires a secure connection or the client was not authenticated.
The server response was: 5.7.30 Basic authentication is not supported for Client Submission.
Scheduled scripts usually swallow this. The backup still runs, but nobody gets the "backup failed" email, so the first sign is a missing alert weeks later. Find the senders now: Exchange admin center ▸ Reports ▸ Mail flow ▸ SMTP AUTH clients, filtered on Basic authentication, and the Entra sign-in logs with Client app = Authenticated SMTP. Checklist: Find devices and scripts still using SMTP AUTH Basic.
Send-MailMessage is already obsolete. Microsoft's own docs mark the cmdlet obsolete because it can't guarantee a secure connection, and it has no OAuth support. Don't spend time patching it; replace the call.
Pick a replacement per script
| Script type | Best fix |
|---|---|
| PowerShell you can edit | Microsoft Graph Send-MgUserMail with an app registration and a certificate (below). |
| Python / Node / any code you can edit | Graph POST /users/{sender}/sendMail with a client-credentials token (below). |
| Compiled app or appliance where you can only set host, port, user, password | Point it at a local SMTP relay that sends through Graph (for example ScanRelay), or a third-party SMTP service. |
| Internal alerts only, many sources | Microsoft's High Volume Email (HVE) works for internal recipients only. Fine for alerts to your own team, not for mail to customers. |
| Stopgap while you migrate | Re-enable SMTP AUTH Basic for that one mailbox (Set-CASMailbox -SmtpClientAuthenticationDisabled $false) until the final removal date. Plan it as temporary. |
Step 1: one app registration for all your scripts
- Entra admin center ▸ App registrations ▸ New registration. Single tenant, no redirect URI.
- Certificates & secrets: upload a certificate (preferred for unattended scripts) or create a client secret and note its expiry date.
- API permissions: Microsoft Graph ▸ Application ▸
Mail.Send, then grant admin consent. - Limit which mailboxes it can send as. Without this,
Mail.Sendcan send as any mailbox in the tenant. Use Exchange Online RBAC for Applications (a management scope containing only the sender mailbox, assigned theApplication Mail.Sendrole), or the older application access policy.
Step 2a: PowerShell with Microsoft Graph
Install-Module Microsoft.Graph.Users.Actions -Scope AllUsers # once
Connect-MgGraph -ClientId $AppId -TenantId $TenantId -CertificateThumbprint $Thumb -NoWelcome
$mail = @{
Message = @{
Subject = "Backup failed on SRV01"
Body = @{ ContentType = "Text"; Content = "Job Nightly-Full exited with code 2." }
ToRecipients = @(@{ EmailAddress = @{ Address = "[email protected]" } })
}
SaveToSentItems = $true
}
Send-MgUserMail -UserId "[email protected]" -BodyParameter $mail
The mail goes out from your own tenant, lands in the sender's Sent Items, and is covered by your retention and journaling. No SPF or DNS changes.
Step 2b: Python (or anything that can make an HTTPS call)
import msal, requests
app = msal.ConfidentialClientApplication(APP_ID, authority=f"https://login.microsoftonline.com/{TENANT_ID}",
client_credential=CLIENT_SECRET)
tok = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])["access_token"]
msg = {"message": {"subject": "Disk 90% on SRV01",
"body": {"contentType": "Text", "content": "C: has 9 GB free."},
"toRecipients": [{"emailAddress": {"address": "[email protected]"}}]},
"saveToSentItems": True}
r = requests.post("https://graph.microsoft.com/v1.0/users/[email protected]/sendMail",
headers={"Authorization": f"Bearer {tok}"}, json=msg, timeout=30)
r.raise_for_status() # 202 Accepted means Exchange took it
Gotchas
- Attachments: inline file attachments on
sendMailare limited to about 3 MB in the request. Bigger files need a draft plus an upload session. - Secret expiry: client secrets expire (max 2 years). Put the expiry date in your monitoring, or use a certificate.
- Throttling: Graph throttles per mailbox. Fine for alerts; for bulk mail use a proper sending service.
- OAuth SMTP is also an option (XOAUTH2 with the
SMTP.SendAsApppermission), but it needs an Exchange service principal and mailbox permissions, and most script libraries don't support it. Graph is simpler.
Can't change the script? Use a relay
If the sender only lets you type a host, port and password, run a small relay on your network and point the app at it. ScanRelay accepts plain SMTP from your allowlisted devices and apps and sends through Graph from your tenant, with a disk queue and retries. It's free and self-hosted for one tenant.
Related: 550 5.7.30 explained · MSP cutover checklist · Scan to email options compared
Sources
- Microsoft Exchange Team: Updated SMTP AUTH Basic Authentication deprecation timeline
- Microsoft Learn: Send-MailMessage (obsolete)
- Microsoft Graph: user: sendMail
- Microsoft Learn: Role Based Access Control for Applications in Exchange Online
- Microsoft Learn: Authenticate an IMAP, POP or SMTP connection using OAuth