Why OTP and Magic-Link Emails Go to Spam (2026)
OTP and magic-link emails land in spam when they look like bulk mail. Use a sending subdomain, SPF/DKIM/DMARC, and turn off click tracking for auth links.
Why auth mail is different
A six-digit OTP, a magic login link, a password reset, or an email-verification click is not "just another email." It is the gate to an account. When it lands in spam, the user thinks your product is broken — and often retries until you burn rate limits or suppression entries.
Receivers treat these messages with the same filters they apply to everything else. If the From domain shares reputation with marketing blasts, if DNS authentication fails, or if a security URL is rewritten through a click tracker, the message looks risky. This article focuses on auth / account emails, not a generic "why is my email going to spam" checklist. For the broad five-step diagnosis, see Why Is My Email Going to Spam?. Here we stay on OTP and magic-link delivery.
Rovela delivers what you hand it via POST /emails. Token generation, expiry, and one-time use still live in your app — same pattern as the verification-code and password-reset examples, and the sibling posts on magic login and email verification links.
Isolate reputation on a sending subdomain
The official OTP example sends from security@mail.acme.com, not from the apex marketing address. That is intentional.
Transactional auth mail and product newsletters rarely share the same engagement profile. Bulk campaigns get deletes, spam complaints, and list fatigue. Codes and login links should not inherit that history. Put auth (and other high-trust transactional mail) on a dedicated subdomain such as mail.yourdomain.com or notify.yourdomain.com, verify it separately, and keep marketing on another identity.
Practically:
- Use a verified subdomain in
fromfor OTP, magic links, resets, and verify-email messages. - Do not rotate the same apex address between weekly digests and login codes.
- Warm a brand-new subdomain gradually; do not dump a large cold list through it on day one.
How to set that up end-to-end: Sending Subdomain Setup and the domain verification guide.
Pass SPF, DKIM, and DMARC — then keep them passing
Receivers ask three questions: may this server send for you (SPF), was the message altered (DKIM), and what should happen if either fails (DMARC). Auth mail with broken authentication is an easy spam candidate even when the copy is clean.
From the domains guide:
- One SPF record on the sending domain. Merge
include:values; twov=spf1TXT records is a permanent fail. - DKIM publishes the public key under the selector; Rovela signs outbound mail once the domain is verified.
- DMARC usually starts at
p=nonefor reporting, then moves toquarantine/rejectwhen reports look clean — tightening too early can quarantine your own legitimate mail. - Return-path records on the bounce subdomain matter; without them, bounces do not come back cleanly and suppression logic suffers.
Verification is not forever. DNS migrations and "cleanup" of unused TXT records can quietly drop DKIM or DMARC. When users report "codes go to spam," check Authentication-Results on a sample message and re-verify the domain in the dashboard before rewriting templates.
Turn click tracking off for magic, reset, and verify links
This is the auth-specific footgun.
The password-reset docs state it clearly: click tracking rewrites links. If tracking is on for the sending domain, the reset (or magic-login / verify-email) URL is wrapped through a tracking host. The link often still works, but the token no longer goes straight to your app. Security-conscious filters and careful users treat that as suspicious; some corporate gateways strip or rewrite further.
Guidance that matches the docs:
- Prefer a separate sending domain or subdomain with click tracking off for security mail (OTP can skip links entirely; magic login, reset, and verify-email cannot).
- Always put the full absolute URL in the
textpart as well as in HTML. Some filters strip HTML links; plain text is the fallback. - Do not bury the only copy of the token behind a marketing-style "Click here!" button with no plaintext URL.
OTP messages that show the code in the subject and body avoid this class of problem altogether — see the verification-code example. Link-based flows need the tracking decision made deliberately.
Content hygiene for short auth emails
Auth copy should look like a receipt, not a campaign.
- Send both
htmlandtext. Security examples in the docs always include both. - Keep subjects factual:
Your Acme code: 482913,Reset your Acme password,Sign in to Acme. Avoid urgency spam cues stacked with emoji and ALL CAPS. - One clear action. No cross-sell blocks, no "unsubscribe from all offers" footers that make the message look promotional (use list-unsubscribe patterns on marketing mail, not on one-shot auth).
- Say how long the code or link lasts, and what to do if the user did not request it.
- Put the OTP in the subject when your threat model allows it (notification glance); omit it if users share screens often — as the OTP docs note.
None of this replaces authentication or subdomain isolation. Clean copy on a burned shared domain still struggles.
Suppressions, bounces, and what 200 means
A successful POST /emails returns 200 with a message id — accepted for sending, not proof of inbox placement. If users say the code never arrived, check the message timeline in the dashboard and subscribe to bounce events (handling bounces).
When the API returns 400 with recipient is suppressed, that address hard-bounced or complained earlier. Do not tell the user "check your inbox"; ask for another address — exactly as the verification-code docs describe. Repeatedly forcing sends to suppressed recipients does not fix spam; it signals poor list hygiene.
Rate-limit OTP sends per address (the docs use roughly one code per minute) so a retry button cannot flood an inbox or your quota. Plan limits are on Pricing; this article does not invent inbox rates or vendor benchmarks.
Auth-email deliverability checklist
- Auth mail sends from a verified subdomain, not the marketing From.
- SPF (single record), DKIM, DMARC, and return-path are present and still resolve (domains guide).
- Magic / reset / verify links use a path with click tracking off, or tracking is disabled for that sending identity (password-reset notes).
-
Every auth message includes
htmlandtext, with the full URL in text when there is a link. - Subjects and bodies stay short and transactional; no campaign-style CTAs.
-
Suppressions and bounces are handled;
200is not treated as "in inbox." - Deep product flows follow OTP, reset, magic login, and verification links.
Further reading
- Sending Subdomain Setup
- Why Is My Email Going to Spam? — general checklist (related, not a duplicate of this auth-focused piece)
- Transactional email for AI apps
- Domain verification
- Pricing
Auth emails fail when they look and behave like bulk mail. Separate the subdomain, keep DNS honest, leave security links untracked, and keep the copy short. Rovela handles signing and delivery over HTTP; your app still owns the token — and the inbox outcome still depends on how that message is presented to the receiver.