ScanRelay / Guides

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 typeBest fix
PowerShell you can editMicrosoft Graph Send-MgUserMail with an app registration and a certificate (below).
Python / Node / any code you can editGraph POST /users/{sender}/sendMail with a client-credentials token (below).
Compiled app or appliance where you can only set host, port, user, passwordPoint it at a local SMTP relay that sends through Graph (for example ScanRelay), or a third-party SMTP service.
Internal alerts only, many sourcesMicrosoft'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 migrateRe-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

  1. Entra admin center ▸ App registrations ▸ New registration. Single tenant, no redirect URI.
  2. Certificates & secrets: upload a certificate (preferred for unattended scripts) or create a client secret and note its expiry date.
  3. API permissions: Microsoft Graph ▸ Application ▸ Mail.Send, then grant admin consent.
  4. Limit which mailboxes it can send as. Without this, Mail.Send can send as any mailbox in the tenant. Use Exchange Online RBAC for Applications (a management scope containing only the sender mailbox, assigned the Application Mail.Send role), 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

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

  1. Microsoft Exchange Team: Updated SMTP AUTH Basic Authentication deprecation timeline
  2. Microsoft Learn: Send-MailMessage (obsolete)
  3. Microsoft Graph: user: sendMail
  4. Microsoft Learn: Role Based Access Control for Applications in Exchange Online
  5. Microsoft Learn: Authenticate an IMAP, POP or SMTP connection using OAuth