Mistakes that most startups do sending OTP emails
Hey founders, Alexey here, CTO @ UniOne - transactional email service for startups.
My team just wrote a guide this week and figured I'd share the meat of it, since this comes up in every founder DM I get.
Here is the pattern I see often: a founder builds their auth flow, sends OTP through the same SMTP they use for marketing and promo newsletters, everything works fine at first users. Then suddenly it breaks, password resets start hitting spam folders and nobody on the team can figure out why..
3 crucial things most startupers miss when they ship:
1 - Shared sending streams quietly destroy auth deliverability. When your promo email gets flagged as spam by even a small number of recipients, that reputation hit drags down everything else on the same stream, including the critical transactional emails your users actually need to log in. Gmail and Microsoft see one sender domain with a complaint rate, and they act accordingly.
The fix isn't expensive - just use a dedicated subdomain like "auth.yourdomain.com" for transactional traffic, set up separate DKIM keys, and ideally use a separate IP pool if your provider supports it. The whole thing takes about 1 hour (or even less) of DNS configuration and saves you from a category of incidents that's genuinely hard to debug once it starts happening.
2 - Retries without idempotency turn into duplicate codes, which turn into spam complaints. If your backend retries on a network timeout and the OTP gets delivered twice, users get confused, panic and mark it as spam.
We added idempotency keys to our API specifically because we kept seeing this pattern - same key means same code, no duplicate send, no panicked user. Worth implementing on your side regardless of which ESP you use.
3 - Webhooks beat polling for anything OTP-related. You need to know within seconds whether the code was accepted, deferred, bounced, or hit a spam folder, and polling stats every five minutes is just too slow for an auth flow where users abandon in under a minute. Set up webhooks, store the job_id next to your OTP request in your own database, and when a user complains "I never got the code," you can pull up the exact event timeline in 10 seconds.
Full write-up with API + SMTP code examples, DNS setup, and the full sending flow is available in our blog. Please, let me know in the comments if you want me to share the direct link to the guide
Happy to answer specific questions in the comments