Windows Server Setup Guide

August 23, 2026 · View on GitHub

Heads up about the classic "IIS SMTP relay" approach: Microsoft's own documentation on configuring SMTP e-mail in IIS starts with a note that the IIS 6.0 SMTP Virtual Server component it describes has been unsupported since Windows Server 2003/2008 ("we no longer support doing so with IIS SMTP"). It still technically installs on modern Windows Server via the legacy IIS 6 Management Compatibility feature, but it's not something to build a new setup on. The modern equivalent — connecting directly to mx.msgwing.com, no local relay component needed — is the recommended approach below.

The username and password come from an account, and the account is free. Register at msgwing.com, activate, and copy the generated login and password — they are shown once. Mail leaves from that generated @msgwing.com address rather than your own domain, and the cap is 200 messages a day with no paid tier that lifts it. Both limits are stated here rather than discovered later; if either one rules this out for you, the alternatives page names the tools that do not have them.

Top pick: point your app straight at ZeroSMTP (no local relay needed)

You don't need any Windows feature installed at all — ASP.NET, classic ASP, and PHP-on-IIS applications can talk to mx.msgwing.com directly.

ASP.NET / IIS-hosted apps (web.config)

Add this to the application's web.config (or machine.config for a server-wide default):

<configuration>
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="Network" from="your-username@msgwing.com">
        <network
          host="mx.msgwing.com"
          port="587"
          enableSsl="true"
          userName="your-username@msgwing.com"
          password="your-password" />
      </smtp>
    </mailSettings>
  </system.net>
</configuration>

For port 465 (implicit SSL/TLS) instead of 587 (STARTTLS), set port="465"enableSsl="true" covers both, .NET negotiates the right handshake for each port automatically.

Don't hardcode the real password in web.config in source control — use ASP.NET's configuration encryption or environment-specific config transforms for production.

PHP on IIS

Use PHPMailer or Symfony Mailer from this repo and point them at mx.msgwing.com:587/:465 directly — no IIS-level SMTP configuration needed. This also sidesteps PHP's very limited built-in mail() function, which doesn't support authenticated SMTP at all.

For scheduled/background tasks: PowerShell + Task Scheduler

For backup jobs, monitoring alerts, or anything else that isn't a web app, use pwsh-zerosmtp.ps1 from this repo (built on the actively maintained Send-MailKitMessage module — see that file for why, not the deprecated Send-MailMessage cmdlet) as a scheduled task:

$env:ZEROSMTP_USERNAME = "your-username@msgwing.com"
$env:ZEROSMTP_PASSWORD = "your-password"
schtasks /create /tn "ZeroSMTP test" /tr "pwsh.exe -File C:\path\to\pwsh-zerosmtp.ps1" /sc daily /st 08:00

Store real credentials as a scheduled-task-scoped environment variable or via Register-ScheduledTask with a secured credential object — not in plain text in the task definition.

Exchange Server: Send Connector to a ZeroSMTP smart host

Read this caveat first: mail relayed through ZeroSMTP always goes out From an @msgwing.com address (see the FAQ) — it will not preserve your Exchange organization's own domain. That rules this out as a replacement for your main mail flow. Where it's genuinely useful is for system-generated notifications you don't need to come from your own domain — DAG/replication alerts, transport queue warnings, message-tracking reports — routed through a dedicated Send Connector scoped narrowly to just that traffic, not your whole organization's mail flow.

Exchange Admin Center: Mail flow → Send connectors → New → type Internet"Route mail through smart hosts" → add mx.msgwing.com → smart host authentication: Basic Authentication over TLS with your ZeroSMTP username/password → scope the address space and source server(s) to whatever specific system/application is sending this notification traffic, not * for the whole org.

Exchange Management Shell equivalent:

$cred = Get-Credential  # ZeroSMTP username + password
New-SendConnector -Name "ZeroSMTP notifications" `
  -AddressSpaces "SMTP:*;1" `
  -SmartHosts "mx.msgwing.com" `
  -SmartHostAuthMechanism BasicAuthRequireTLS `
  -AuthenticationCredential $cred `
  -Port 587 `
  -RequireTLS $true `
  -SourceTransportServers "YourMailboxServerName"

Narrow -SourceTransportServers (and, if your topology needs it, a transport rule matching just the relevant sender) so this connector only ever handles the specific notification traffic it's meant for.

Its own page now: IIS SMTP relay and Microsoft 365 — the queue and Badmail directories where this fails silently, the three settings that matter, and the three options when the application cannot be reconfigured. The short version is below.

If you're maintaining an old application that only supports "deliver to local SMTP server" and can't be reconfigured to connect directly, the Microsoft doc linked above covers installing the legacy SMTP Server Windows feature and configuring it (via the IIS 6.0 Manager console) as a relay: Properties → Delivery → Outbound Security (basic auth with your ZeroSMTP username/password) and Delivery → Advanced → Smart host set to mx.msgwing.com, with outbound port 587 or 465 configured under Outbound connections. Given Microsoft's own unsupported-since-2008 notice, treat this as a last resort, not a starting point.

Connection reference

Windows/IIS SMTP connection settings

Verifying it works

Check the network path before debugging any application config. On a Windows Server with Node available:

npx zerosmtp-check

It tests ports 25, 587 and 465 from that machine, reports whether STARTTLS and the certificate check pass, and lists the AUTH mechanisms the server offers. Nothing is installed, no credentials are sent and no mail leaves the box — the conversation stops after EHLO.

No Node on that server, which is normal on a locked-down box? The check-connection.ps1 script in this repository does the same with nothing but PowerShell.

If the send is refused rather than hanging, the network is fine and the server is saying no for a reason. Paste what it said:

npx zerosmtp-check --explain "535 5.7.139 Authentication unsuccessful"

Send-MailMessage and System.Net.Mail rewrite the server's answer before you see it, which is why the string in your log is often not the one Exchange Online sent. Every error message and what it means covers seventeen of them, each with whether the cause can still be turned back on before the end of December 2026.

If it hangs instead, see TROUBLESHOOTING.md — Windows Server on some cloud providers is subject to the same outbound port blocking as Linux VMs, and that looks identical to a server being down.