Unicode Email Addresses at Signup
by EmailGuard Engineering
Share this article
Enjoyed this article?
More notes on building products, infrastructure, and teams.
by EmailGuard Engineering
More notes on building products, infrastructure, and teams.
A Unicode domain is stored as punycode. A Unicode local part can still pass syntax_validation. Neither result means the receiving server will accept the message.
EmailGuard converts the domain with IDNA2008 lookup, the same rules as RFC 5890 and RFC 5891. On 28 September 2026, idna.Lookup.ToASCII turned münchen.de into xn--mnchen-3ya.de and left xn--bcher-kva.example unchanged. The API's domain field is that ASCII form, lowercased. The field catalog is email valid syntax.
The local part is a separate question. RFC 6531 is SMTPUTF8: the SMTP server advertises that it can accept a UTF-8 mailbox. We do not open that session. Syntax and classification are the jobs in the email validation API guide. Mailbox acceptance is still a confirmation email.
syntax.Validate is the function behind syntax_validation. With a TLD list that contains com, these two addresses returned no error on 28 September 2026:
| Input | Local part we kept | Domain we stored |
|---|---|---|
josé@example.com | josé | example.com |
用户@example.com | 用户 | example.com |
user@bücher.example came back with domain xn--bcher-kva.example and local part user. The punycode step runs after the address parses. A domain that fails IDNA lookup fails syntax.
So a signup form that rejects "non-ASCII email" with a regex will refuse addresses our parser accepts. josé@example.com is a normal mailbox at a host that allows that local part. The regex is the bug. The length cap is still 64 octets for the local part and 255 octets for the domain, from RFC 5321. len here is bytes. A short-looking Unicode local part can cross 64 octets and fail syntax for that reason alone.
syntax_validation: true on 用户@example.com means the string parsed, the domain survived IDNA and the public-suffix checks, and the TLD is one we recognize. It does not mean:
If your mail provider cannot deliver UTF-8 local parts, the confirmation message bounces later. That bounce is a sending problem. Rejecting the address in the parser, before you know the host, drops people whose provider does accept it.
IDNA also has a lookup profile that rejects some lookalike labels. A domain that fails ToASCII fails syntax on purpose. That is not a full homograph program for every brand you own. It is the profile in Go's golang.org/x/net/idna lookup, which is what the API calls.
domain from the detect response when you need a stable ASCII host. münchen.de and xn--mnchen-3ya.de are one host after IDNA. The response gives you the ASCII form once.suggested_email is present. A Unicode domain that failed IDNA will not become a cheerful "did you mean." An ASCII typo like gmail.con still will.You can paste a Unicode address into the email syntax checker and read syntax_validation without writing the call yourself. The authenticated response, including domain, is detect email characteristics.
A detect call for user@münchen.de is the place to read domain, not a second IDNA library in the signup service. Expect the ASCII host in domain and the original string in email. If your database unique index is on the raw email column, user@münchen.de and user@xn--mnchen-3ya.de can both insert. Index the host from domain plus your local-part key if one mailbox should be one row.
Do not split the address on the first @ you find with a Unicode-unaware scan and then punycode whatever fell on the right. The parser uses the address Go's net/mail accepted, then IDNA on that domain. A hand-rolled split will disagree on quoted local parts the parser already accepted or rejected. Send the whole string to detect and store the fields it returns.
Accept the address when syntax_validation is true, then apply disposable, role, and relay policy the same way you do for ASCII. Do not add a second gate that requires every local part to match [A-Za-z0-9._%+-]+. That class is where josé and 用户 go to die, and it also rejects plus tags you may have meant to keep.
If the product genuinely cannot send UTF-8 local parts, say that in the form error after you know your mailer rejected them. Do not pretend the string was illegal syntax. Quota for the detect call is on pricing.
An address with non-ASCII characters in the domain, the local part, or both. The domain has a standard ASCII encoding, punycode, via IDNA2008. The local part stays Unicode unless you choose to refuse it in your own product.
Yes. The domain field is the IDNA2008 lookup form, lowercased. münchen.de becomes xn--mnchen-3ya.de. A domain that is already punycode stays punycode.
It can. josé@example.com and 用户@example.com passed syntax.Validate when com was in the TLD list. That pass is not an SMTPUTF8 check. We do not connect to the mail server to ask.
No, if the only reason is the character set. The regex will disagree with the parser, and it will block plus tags and quoted forms you did not mean to study. Use syntax_validation, then your disposable and role rules.
No. IDN is the domain label. SMTPUTF8 is the mailbox, mostly the local part, on the wire. We convert the domain. We do not probe the mailbox.
domain from detect and stop keeping a second punycode converter in the app.