Email delivery
Transactional emails never arrive
Your app sends sign-up confirmations, password resets and receipts. In testing they arrived. Now real users say nothing turns up, and every missing email is a user locked out or a customer without proof of payment. Email looks like one API call, but delivery depends on infrastructure your AI builder never touched: DNS records that vouch for your sender, a provider that is actually in production mode, and handling for the mail that comes back. Here are the six failures that stop transactional email arriving, and what each one looks like from inside the codebase.
Fixed £4,500 · 3 days · pass-or-refund · next slot w/c 3 August
What actually breaks
- Your DNS never authorised anyone to send as your domainSPF is a DNS record listing the servers allowed to send mail as your domain. DKIM is a signature your provider adds to each message, checked against a key you publish in DNS. DMARC then requires that the domain users see in the From line matches whichever one passed. Your AI tool wrote the code that calls the send API; it cannot edit your DNS, so none of these records exist. Mail arrives claiming your domain with no proof, and to a receiving server, unproven password resets look like phishing.
- Gmail and Yahoo stopped tolerating unauthenticated mail in 2024In February 2024, Gmail and Yahoo stopped treating authentication as optional. Every sender to Gmail now needs SPF or DKIM to pass, and must keep the spam rate reported in Postmaster Tools below 0.3 per cent; Google's guidance is to stay under 0.1 per cent. Senders at five thousand messages a day or more need both, plus a published DMARC policy and an aligned From domain. Unauthenticated mail that once crawled into inboxes can now be rejected before filtering starts.
- You are still on the platform's test mailerSupabase's built-in mailer is for development: it is limited to two auth emails an hour across your project, shared between sign-ups, resets and invites, and it only delivers to your own team's addresses. Resend's onboarding@resend.dev sandbox will only send to your own inbox. Both worked in the demo because you were the recipient. The first real user to request a reset either burns the hourly allowance or is refused outright, and the platform is behaving as designed. Production needs your own provider, your own verified domain, and a raised limit.
- The send is fired, never awaited, and sometimes cancelledOn Vercel, Cloudflare and similar platforms, work still running after your function returns its response has no guarantee of finishing. If code fires the send request and returns without awaiting it, the platform may cancel it: Cloudflare's docs say un-awaited promises may be cancelled when the invocation completes. Whether it survives depends on the runtime and what else the instance is doing, so the failure is intermittent. Your single test email arrives; real receipts vanish under load. Await the send, or use the platform's waitUntil.
- Nobody handles bounces, so the provider suspends the accountSome of the addresses your users type do not exist. Those sends hard bounce, and providers hold you responsible: Amazon SES places your account under review at a 5 per cent bounce rate and can pause all sending at 10 per cent; spam complaints trigger review at 0.1 per cent. Other providers enforce similar thresholds. If nothing records bounces and stops resending to dead addresses, the rate climbs until the account is suspended, and then every email stops at once: resets, receipts, all of it, usually at your busiest moment.
- The provider said accepted and nobody checked deliveredA success response from the send API means the provider queued the message, nothing more. Delivery, deferral and bouncing happen minutes later and are reported separately, through the provider's event feed and dashboard. AI-generated code stops at the send call: it subscribes to none of those events, so a message that bounced and a message that landed look identical from inside your app. The first signal you get is a support ticket from a user who never received the link.
Why the demo hid all six
Every one of these failures is invisible in a demo. You tested with your own address, which is exactly the address a sandbox mailer or team-only SMTP is allowed to reach. Volume was far too low to hit a rate limit or a bounce threshold. Your dev server stays alive after each response, so un-awaited sends completed anyway. One successful production test clears nothing either: the un-awaited path fails intermittently, not every time. Diagnosis therefore starts from the receiving end: send a test to a real Gmail account and read the message headers, where Authentication-Results states plainly whether SPF, DKIM and DMARC passed and for which domain. Then read the provider's event log rather than your application log, because only the provider knows what happened after accepted.
What “done” looks like
We agree one of these in writing before you pay anything. If it passes, the sprint passed. If it doesn't, you get a full refund and keep the work.
- SPF, DKIM and DMARC published for your sending domain, with a live Gmail test showing all three pass in Authentication-Results
- A production email provider on your own verified domain, the platform's test mailer out of the loop, and rate limits raised to match real traffic
- Every send awaited or handed to waitUntil, with bounce and complaint events feeding a suppression list instead of being retried
- Delivery events logged and visible, so a failed reset email shows up in your logs before it shows up in a support ticket
We agree the acceptance test before any work starts. If it doesn't pass within three working days, you get a full refund and keep every change I've committed. The risk is mine, not yours.