In a recent support case, a Go application (using gomail.v2) sent mail through Exchange 2019 and intermittently failed whenever the message pipeline touched Exchange 2016.
On the surface it looked like “random SMTP errors.” In reality, it was a clean protocol mismatch triggered by SMTPUTF8.
This post captures the symptoms, root cause, and practical remediation options for admins and developers.
🔎 Symptoms
- App connects to Exchange 2019 (Receive Connector on port 25).
- Frontend 2019 proxies the SMTP session to a backend mailbox server on Exchange 2016.
- The client (gomail/net/smtp) issues:
MAIL FROM:<testsmtp@contoso.com> BODY=8BITMIME SMTPUTF8
- Backend 2016 rejects the command with:
502 5.3.3 Command not implemented”.
Result: the message never enters transport.
🧠 Why it happens
1) Exchange 2019 advertises SMTPUTF8
When the client sends EHLO, Exchange 2019 includes:
250-SMTPUTF8
250-8BITMIME
…
Go’s net/smtp (used by gomail) automatically appends SMTPUTF8 to MAIL FROM when the server advertises it.
2) Exchange 2016 does not support SMTPUTF8
Exchange 2016 does not advertise SMTPUTF8 and cannot accept that parameter.
So FE 2019 “promises” an extension the BE 2016 cannot fulfill, and the proxied command fails.
This is expected in mixed-version environments: the frontend supports more SMTP extensions than the backend.
📚 SMTPUTF8 support in Exchange versions
Microsoft introduced full support for SMTPUTF8 / EAI only starting with Exchange Server 2019:
“Support for SMTPUTF8, more specifically Email address internationalization (EAI): Email addresses that contain non-English characters can now be routed and delivered natively.” – Exchange Server 2019 – New features
In practice:
- Exchange 2019/Exchange Online: can route/deliver UTF-8 envelope addresses and honor SMTPUTF8.
- Exchange 2016: a connector parameter may exist but is reserved for internal use; 2016 does not advertise SMTPUTF8 in EHLO and cannot accept the SMTPUTF8 parameter on MAIL FROM.
Hence a 2019 FE + 2016 BE combo breaks when a modern client reacts to 2019’s EHLO.
💡 What SMTPUTF8 actually gives you (short)
- Unicode in envelope addresses (RCPT TO, MAIL FROM), e.g., иван@пример.рф, 用户@例子.公司.
- UTF-8 headers without MIME-encoding (Subject/From/To display names).
- Not about the body (you already could send UTF-8 content via MIME) — this is about the SMTP protocol layer.
🔧 Where the Go client adds SMTPUTF8 (for developers)
Your app uses gomail.v2, which relies on the Go standard library net/smtp.
The decision to append SMTPUTF8 is made inside net/smtp, not in gomail.
File:
$GOROOT/src/net/smtp/smtp.go
(Windows default installs: C:\Go\src\net\smtp\smtp.go)
Key function ((*Client).Mail):
// If the server supports 8BITMIME, add BODY=8BITMIME.
// If the server supports SMTPUTF8, add SMTPUTF8.
func (c *Client) Mail(from string) error {
if err := c.hello(); err != nil { return err }
cmdStr := “MAIL FROM:<%s>”
if c.ext != nil {
if _, ok := c.ext[“8BITMIME”]; ok {
cmdStr += ” BODY=8BITMIME”
}
if _, ok := c.ext[“SMTPUTF8”]; ok {
cmdStr += ” SMTPUTF8″
}
}
_, _, err := c.cmd(250, cmdStr, from)
return err
}
So the moment 2019 returns 250-SMTPUTF8, any Go client using net/smtp will emit:
MAIL FROM:<user@contoso.com> BODY=8BITMIME SMTPUTF8
🗺️ Who is affected (topology & client placement)
In all failing cases the entry point is Exchange 2019 (FE/Receive Connector). What happens next depends on where the session is proxied after authentication:
A) Authenticated client with a mailbox → proxied to the mailbox server
- If the mailbox is on Exchange 2016 (BE 2016), the proxied MAIL FROM … SMTPUTF8 is rejected → failure.
- If the mailbox is on Exchange 2019 (BE 2019), the pipeline accepts SMTPUTF8 → success.
B) Authenticated client without a mailbox → proxied to arbitration mailbox server
- If those arbitration mailboxes reside on Exchange 2016, the proxied command with SMTPUTF8 is rejected → failure.
- If the arbitration mailboxes are on Exchange 2019, the session proceeds → success.
C) Anonymous submitters (no AUTH) → proxied by recipient/routing
- FE 2019 then proxies based on recipient resolution/routing (e.g., the recipient’s mailbox server, accepted domain routing, site/cost, transport anchoring).
- If the proxy target is Exchange 2016, the backend rejects MAIL FROM … SMTPUTF8 → failure.
- If the proxy target is Exchange 2019, the command is accepted → success.
✅ Mitigation options
Option 1 — Disable SMTPUTF8 on the Exchange 2019 Receive Connector clients connect to.
- EHLO from 2019 will not contain 250-SMTPUTF8.
- net/smtp (and thus gomail) will stop adding SMTPUTF8.
- The proxied session to 2016 succeeds.
This is the cleanest org-wide fix until all mailboxes are on 2019.
Example:
Set-ReceiveConnector “EXMB01\Default Frontend EXMB01” -SmtpUtf8Enabled $false
Option 2 — Change client behavior
A. Use a raw SMTP client in Go (manual protocol):
MAIL FROM:<user@contoso.com> BODY=8BITMIME
— no SMTPUTF8, regardless of EHLO.
B. Override net/smtp locally (isolated/offline environments)
Vendor net/smtp into your project and remove the SMTPUTF8 append:
- Copy $GOROOT/src/net/smtp into your repo (e.g., ./local/net/smtp).
- In your go.mod:
replace net/smtp => ./local/net/smtp
- Edit smtp.go → (*Client).Mail to not add SMTPUTF8:
if c.ext != nil {
if _, ok := c.ext[“8BITMIME”]; ok {
cmdStr += ” BODY=8BITMIME”
}
// Do NOT add SMTPUTF8 even if advertised.
// if _, ok := c.ext[“SMTPUTF8”]; ok {
// cmdStr += ” SMTPUTF8″
// }
}
This keeps gomail intact while guaranteeing MAIL FROM never includes SMTPUTF8 from this app.
Note: This is an application-scoped workaround, not a general recommendation.
Option 3 — Complete mailbox migration to Exchange 2019
Once the backend is 2019-only, the pipeline fully supports SMTPUTF8 and the issue disappears.
✅References
📎Exchange Server 2019 – New features (section on SMTPUTF8 / EAI) — “Email addresses that contain non-English characters can now be routed and delivered natively.”
https://learn.microsoft.com/en-us/exchange/new-features/new-features-2019

Leave a comment