If you're running SQL Server Reporting Services and sending subscriptions through Microsoft 365, you've either already hit this wall or you're going to. Microsoft deprecated Basic Authentication for SMTP connections to Exchange Online. SSRS uses Basic Auth for its email delivery. The result: scheduled reports stop going out, nobody says anything for a while, and then someone important notices they haven't gotten their Monday morning report in three weeks.
The official Microsoft answer is "use SMTP AUTH with modern auth" — but SSRS doesn't support that. The other suggestion floating around is to stand up an on-prem SMTP relay. That works, but it's another thing to maintain, another TLS cert to manage, another thing that can break at 2am.
We went a different direction: a lightweight .NET 8 Windows Service that accepts SMTP locally (from SSRS), then forwards the email via Microsoft Graph API using certificate-based authentication. No passwords in config files, no Basic Auth anywhere, nothing deprecated. We called it CodeBlueMailRelay.
Architecture Overview
The service does three things:
- Listens on localhost SMTP (port 25 or 587, your choice) using the SmtpServer NuGet library
- Receives the email that SSRS hands off exactly like it would to any SMTP server
- Forwards it via Microsoft Graph API using MSAL with a certificate credential — no passwords
SSRS gets configured to point at 127.0.0.1 as its SMTP server. As far as SSRS is
concerned, it's talking to a normal SMTP relay. The service handles all the modern auth complexity
behind the scenes.
Azure App Registration Setup
Before writing any code, you need an App Registration in Entra ID with the right permissions and a certificate credential. No client secrets — certificates only, so there's no password to rotate or accidentally commit to source control.
- Create an App Registration in Entra ID
- Add API permission:
Mail.Send(Application permission, not Delegated) - Grant admin consent for the permission
- Generate a self-signed cert (or use your PKI if you have one) and upload the public key to the app registration under Certificates & Secrets
- Install the cert with private key on the server running the relay service
Mail.Send
can send as any mailbox in the tenant. Use application access policies in Exchange Online to restrict
it to only the service account mailbox you're sending from. This is not optional if you care about
security posture.
Restricting the App to One Mailbox (Exchange Online PowerShell)
# Create a mail-enabled security group with just the sender account
New-DistributionGroup -Name "MailRelayServiceAccounts" -Type Security
Add-DistributionGroupMember -Identity "MailRelayServiceAccounts" -Member "ssrs-relay@yourdomain.com"
# Apply the access policy
New-ApplicationAccessPolicy `
-AppId "YOUR-APP-CLIENT-ID" `
-PolicyScopeGroupId "MailRelayServiceAccounts" `
-AccessRight RestrictAccess `
-Description "Limit mail relay app to SSRS sender account only"
The Service Core
The SMTP listener uses the SmtpServer library. The Graph send uses
Microsoft.Graph and Microsoft.Identity.Client (MSAL). Here's
the core send method — stripped down for readability:
private async Task SendViaGraphAsync(MimeMessage message)
{
// Certificate-based credential — no passwords
var cert = GetCertificateFromStore(_config.CertThumbprint);
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(_config.ClientId)
.WithTenantId(_config.TenantId)
.WithCertificate(cert)
.Build();
var graphClient = new GraphServiceClient(
new ClientCredentialProvider(confidentialClient));
var graphMessage = new Message
{
Subject = message.Subject,
Body = new ItemBody
{
ContentType = message.HtmlBody != null
? BodyType.Html
: BodyType.Text,
Content = message.HtmlBody ?? message.TextBody
},
ToRecipients = message.To.Mailboxes.Select(m => new Recipient
{
EmailAddress = new EmailAddress { Address = m.Address }
}).ToList()
};
await graphClient.Users[_config.SenderAddress]
.SendMail
.PostAsync(new SendMailPostRequestBody
{
Message = graphMessage,
SaveToSentItems = false
});
}
Logging and Monitoring
The service uses Serilog with two sinks: a rolling log file and the Windows Event Log. The Event Log integration uses a defined event ID map so ConnectWise Automate (or any RMM worth using) can monitor for specific conditions without parsing log text. If a cert expires or a permission gets yanked in Entra, the monitor fires a ticket before a client notices anything is broken.
SSRS Configuration
In Report Server Configuration Manager, under Email Settings:
- SMTP Server:
127.0.0.1 - From Address: your sender mailbox
- Authentication: No authentication (the relay handles auth)
- Port: whatever you configured the service to listen on (default 25)
Monitoring Integration
The service writes to the Windows Event Log under source CodeBlueMailRelay with
a defined event ID map. If you're running ConnectWise Automate or any RMM with Event Log
monitoring, point a remote monitor at that source and you'll get ticketed on failures without
any custom scripting:
- 1001 — Message send failure
- 1002 — Auth / token failure
- 1003 — Certificate expiring within 30 days
- 1004 — Service failed to start
- 1005 — Service started successfully
The cert expiry warning at 30 days is the one that matters most operationally. Token failures are almost always cert-related anyway — if you catch the expiry warning and renew before it expires, Event ID 1002 should never fire in production.
The Repo
CodeBlueMailRelay is open source and available on GitHub. The INSTALL.md covers
the full setup end to end — certificate generation, Entra app registration, service install
via sc.exe, and deploying from the release zip without needing to compile anything.
The service is licensed for internal use; if you're an MSP looking to deploy this commercially
or package it into a product, reach out.
Wrapping Up
This took a day to build and has been running without issues since. No passwords in config, no Basic Auth, no on-prem SMTP infrastructure. The cert has a 2-year expiration with a calendar reminder. When that comes up, it's a 10-minute renewal — upload new cert to Entra, update thumbprint in the service config, restart the service, done.
If you're still pointing any application at smtp.office365.com with Basic Auth — SSRS, a monitoring tool, a copier, a line-of-business app — this pattern works for all of them. The service doesn't care what hands it an email. Anything that can talk SMTP to localhost can use it as a relay.