Open a message that went to spam and read its headers before you change anything. The Authentication-Results line says whether SPF, DKIM and DMARC passed, and that splits the problem in two. With transactional emails landing in spam, a fail points to DNS. A pass leaves 3 causes to check in order: content, sender reputation, and the shared IP.
Transactional emails landing in spam: what to do right now
Transactional emails landing in spam get diagnosed in 6 steps: send the real message to a Gmail and an Outlook mailbox, read the Authentication-Results header, check the provider’s bounce and complaint rates, read Google Postmaster Tools where it has data, tell users on the reset screen to check spam, and change nothing else until the cause is known.
- 01 Trigger the real message from the live app, a password reset or a sign-up confirmation rather than a test template, to a Gmail mailbox and an Outlook mailbox you control. Note where each copy lands: inbox, spam, junk, or nowhere. Evidence to save: the time you sent it and the folder each copy reached.
- 02 Open the message source and read the Authentication-Results line. In Gmail, next to Reply, click More, then Show original. In Outlook on the web or Outlook.com, select More actions at the top of the message, then View, then View message details. Find the spf, dkim and dmarc results. Evidence to save: the full header text, with the date.
- 03 Open your email provider's dashboard and read the bounce rate, the complaint rate, any suppression or account review notice, and whether marketing and transactional mail go out through the same stream. Evidence to save: a dated screenshot of those numbers.
- 04 Open Google Postmaster Tools and read the spam rate and authentication dashboards for your sending domain. It shows nothing until the domain is added and verified with a DNS record, and a low-volume app can see empty dashboards, so skip this step if yours are empty and work from the header and the provider dashboard. Evidence to save: both dashboards, dated.
- 05 Keep the app usable. Add one line to the reset and verification screens telling users to check spam or junk and how long the link stays valid, and give support a way to confirm a user's account from the admin side while the cause is open. Evidence to save: the wording you shipped and when it went live.
- 06 Change nothing else yet: no new email provider, sending domain or From address in the middle of the incident, because a new domain starts with no sending history. Write down the time and what you saw at each step.
These steps cover the incident. The standing controls come afterward, as part of the wider work on how to improve email deliverability.
Step 2 does most of the diagnosis. RFC 8601 defines the Authentication-Results header field “to indicate the results of message authentication efforts”: the receiving server records which methods it applied and their results, such as spf=pass or dkim=fail, with the domain it checked beside each. Gmail’s help on full headers gives the Show original path above, and Microsoft’s header help gives the View message details path for Outlook on the web.
The result that counts is dmarc. RFC 9989, the current DMARC standard, gives a “pass” when a DMARC record exists and the From domain “is aligned” with an authenticated identifier from the message, so one aligned pass from SPF or DKIM is enough. SPF is checked on the return path, not on the From address: “DMARC relies solely on SPF validation of the MAIL FROM identity.” Many email providers use their own bounce domain as the return path, so I’d read spf=pass for the provider’s domain beside an aligned dkim=pass for yours as a correct setup.
The illustrative header below, with example domains, shows a failure that is easy to miss: SPF and DKIM both pass, and DMARC still fails.
Authentication-Results: mx.example.net;
spf=pass smtp.mailfrom=bounces.example.org (provider's bounce domain, not aligned);
dkim=pass header.d=example.org (signed by the provider, not aligned);
dmarc=fail header.from=example.com (no aligned pass)
A correct version of the same message would show header.d=example.com on the DKIM line and dmarc=pass. A dmarc=none result means the From domain publishes no DMARC record at all.
For step 4, Google Postmaster Tools dashboards cover “spam rate, reputation, message authentication, and delivery errors”, and its data “only applies to messages sent to personal Gmail accounts”. Google also warns that “Data might be missing if the total number of messages for a given day is too low”, which is why a low-volume app can find its dashboards empty.
For step 5, the check-spam line costs nothing and can cut down the support tickets that say the email never came. If the app runs on Lovable Cloud, the documented reasons a confirmation goes silent, and the resend step, are under why nobody is getting the confirmation email.
Why this happens
Spam placement of app email has 4 causes: authentication fails or passes for a domain other than yours, the message reads like marketing, the sending domain or stream has a poor reputation, or one mailbox provider distrusts the shared IP. One message’s headers separate the first cause from the other three.
The order in the table is the order I’d check in, not a measure of how often each cause happens.
| Cause | What the evidence looks like | Where you see it | Fix |
|---|---|---|---|
| Authentication: a fail, or a pass for the provider’s domain only | spf=fail, dkim=none or dmarc=fail, or SPF and DKIM pass only for the provider’s domains | The Authentication-Results line in the full headers (Gmail: Show original; Outlook on the web: View message details) | Authentication row of the fix table |
| The message reads like marketing | Headers pass and the provider dashboard is clean, but the template carries promotion, tracked links or no plain-text part | The template itself, and a test send with tracking off | Content row |
| Poor reputation of the domain or stream | A rising complaint or bounce rate on the provider dashboard; a high spam rate in Postmaster Tools where it has data | The provider dashboard; Postmaster Tools, for mail to personal Gmail accounts only | Streams and reputation rows |
| One mailbox provider distrusts the shared IP, or a blocklist | Gmail delivers and Outlook junks, or the reverse, with the same passing headers | The two test mailboxes, the Outlook.com postmaster site and the provider’s support team | Shared IP and blocklist rows |
Nobody notices early because this is a silent failure. A message the provider accepted and the mailbox’s spam filter then filed in junk raises no error anywhere, and many apps would not record the errors that do happen. In my audits, 17 of the 21 third-party apps had no error tracking or alerting: when a user hits an error, nothing records it. Those 21 were 11 public apps and 10 held-out apps I audited in June and July 2026, picked for review rather than drawn at random, so the count describes that set and is not a rate for AI-built apps in general.
Authentication fails, or passes for the wrong domain
The header shows spf=fail, dkim=none or dmarc=fail. Or SPF and DKIM both pass, but only for the provider’s domains, neither is aligned with your From domain, and DMARC fails. An spf=pass for the provider’s return-path domain on its own is normal when DKIM is aligned.
The usual reasons in a builder app, from my own list: the app still sends through the auth provider’s built-in sender, the domain was added at the email provider but its DNS records were never published, the domain has two SPF records, or a DKIM CNAME sits behind a DNS proxy. Supabase’s SMTP guide says its built-in server “is not meant for production use”. The move off it is custom SMTP for Supabase Auth.
Google’s email sender guidelines require everyone who sends email to Gmail accounts to “Set up SPF or DKIM email authentication for your sending domains”, and senders of more than 5,000 messages a day to Gmail accounts to set up SPF, DKIM and DMARC. Yahoo’s sender best practices say “Yahoo strongly urges all senders to publish a DMARC policy for each domain that sends mail.” Getting the records right is SPF, DKIM and DMARC setup work, and I won’t repeat it here.
The message reads like marketing
Authentication passes and the dashboard is clean, but a password reset or a receipt carries an upsell block, a newsletter footer, several tracked links or a URL shortener. Other suspects: links whose domain differs from the From domain, such as the provider’s default click-tracking domain; an image-only layout with no plain-text part; a subject line that does not name the transaction.
Gmail’s sender guidelines list the upsell case under sending practices to avoid: “Don’t mix different types of content in the same message. For example, don’t include promotions in sales receipt messages.” Treat the others as things to test, not as filter rules. I’d send the same template with click tracking off and the promotional block removed, to the same two mailboxes, and compare where each version lands.
The domain or the stream has a poor reputation
Complaints are the input Gmail names: “If messages from your domain are frequently reported as spam, future messages from you are more likely to be marked as spam.” Its guidelines also tell all senders to “Keep spam rates reported in Postmaster Tools below 0.3%.”
Where those complaints come from in an app, from my own list: marketing mail sent from the same domain and stream as resets; sign-up forms abused by bots, so strangers receive verification mail they never asked for; and repeated sends to addresses that already bounced. A brand-new domain has no history yet, which I suspect looks much the same from outside. Someone else sending as your domain also lands on your reputation, and stopping that is a matter of how to stop email spoofing. Suppression and the bounce and complaint webhooks belong in the email bounce handling checklist.
Take an app that sends password resets and a monthly newsletter from the same domain through the same provider stream. One newsletter goes to an old list and many recipients mark it as spam; under the Gmail rule quoted above, future messages from that domain become more likely to be marked as spam, so the resets start landing in spam too. Mail that must arrive should never share a reputation with mail people can mark as spam. Supabase’s guide makes the same case for auth mail kept apart from marketing: “If the reputation of one falls, it won’t affect your whole application or operation.”
The shared IP, a blocklist, or emails going to spam folder at one mailbox provider only
When Gmail delivers and Outlook junks the same message, or the reverse, the cause is most likely that provider’s view of the sending IP or domain, not the message. Check authentication against Outlook’s own rule as well: the Outlook.com postmaster site says that starting May 5th, 2025, Outlook.com is enforcing SPF, DKIM and DMARC for domains sending over 5,000 emails per day, that non-compliant messages “will be sent to the junk folder”, and that “Shortly we will reject the messages until the DNS records are corrected.” Once rejection starts, a bounce from Outlook.com can have the same cause.
Shared IP pools are the email provider’s to manage, so my first move is a support ticket with the saved headers. A lookup of the sending IP and domain on the blocklist operator’s own lookup page rules a listing in or out.
If the spam folder in question is your own, with mail you receive rather than mail your app sends, the fix is a recipient-side setting, covered in the questions at the end. Mail that is deferred or throttled, rather than filed in junk, is a different failure with different error text, and it falls under email rate limit exceeded.
How to fix it: the fix for each cause, and how to confirm it worked
The fix for spam placement follows the cause: correct the DNS records, strip promotion and third-party link domains from must-arrive messages, move them to their own stream, and stop sending to bounced addresses. Confirm each fix with the same real message sent to the same two mailboxes, with the headers saved.
| Cause | The fix | How to confirm |
|---|---|---|
| Authentication | Publish or correct the records, then send again | The new header shows dmarc=pass, with your domain in header.d or an aligned smtp.mailfrom |
| Content | Strip promotion from must-arrive messages, use a custom tracking domain or turn click tracking off for resets, add a plain-text part, name the transaction in the subject | The changed template reaches the inbox in both test mailboxes |
| Shared stream | Move transactional mail to its own stream or subdomain | The provider shows resets in the transactional stream, with their own complaint and bounce numbers |
| Reputation | Stop sending to bounced and complaining addresses, protect the sign-up form from bots, then send steadily | The complaint rate on the dashboard falls, and later sends reach the inbox |
| Shared IP | A support ticket with the saved headers; stay on shared IPs, which Amazon SES calls best for customers who send low volumes of email | The one provider that junked the mail starts delivering it |
| Blocklist | The list operator’s removal process, after the cause is fixed | The operator’s lookup no longer shows the listing |
Authentication is fixed at the DNS host and confirmed in a received header, never only in the DNS records. The content fix is mostly deletion: a reset needs one link and a sentence, and the upsell can go in a separate message.
Separating streams is the fix that keeps the next bad newsletter away from your resets. Postmark’s message streams split transactional streams, “for one-to-one emails triggered by a user action (password resets, receipts, shipping updates)”, from broadcast streams “for one-to-many sends”, and Postmark says “Broadcast traffic uses dedicated infrastructure so transactional delivery stays fast and reliable.” Choosing the provider and setting up the streams belongs to transactional email best practices.
Reputation is the slow one. Stop sending to bounced and complaining addresses today and put a bot check on the sign-up form, then keep sending steady, clean mail. Reputation tends to recover with clean volume over time, and nobody can promise a date for it.
A dedicated IP is rarely the answer for a small app. Amazon SES dedicated IP addresses documentation says: “If you don’t plan to send large volumes of email on a regular and predictable basis, we recommend that you use shared IP addresses.” For a blocklist, I follow the operator’s removal process only after the cause is fixed, never before.
Confirm every fix the way you diagnosed it: the same real message, to the same two mailboxes, with the new headers saved next to the first set and both dated. A seed-list test across more mailbox providers is the next level of proof, and that belongs to how to test email deliverability.
How to stop it happening again
Five standing controls, each a practice rather than a tool, make a repeat less likely and show it sooner if it happens.
- 01 Check authentication in received headers after every DNS or provider change, not only in the DNS records.
- 02 Trigger every message type that must arrive, and trace each one to the provider's log and to the recipient mailbox.
- 03 Let bounce and complaint events suppress the address, so the next send to it is skipped.
- 04 Keep transactional and marketing mail on separate streams or services.
- 05 Alert on the complaint and bounce rates, with the provider's own alert where it offers one or an alert built on the stored bounce and complaint events, and read Postmaster Tools after any template or provider change once the domain sends enough daily mail for its dashboards to show data.
Supabase’s SMTP guide states the fourth control directly: “Don’t mix Auth emails with marketing emails.” If resets were failing for days, I’d tell the affected users plainly and reissue the links that expired in the meantime.
Common questions about app emails going to spam
How do I stop my emails from going to spam folders?
For mail your app sends, make three moves: authenticate the sending domain, send app mail from its own stream or subdomain, and fix whichever cause the message headers point to. Each move has its row in the fix table above.
Why are my Outlook emails going to people’s spam?
Failed authentication is the one cause Outlook.com documents for app mail, so check it first. Since May 5, 2025, Outlook.com has enforced SPF, DKIM and DMARC for domains that send over 5,000 emails a day, sends non-compliant mail to the junk folder, and says it will “Shortly” reject such mail until the DNS records are corrected. If your headers pass, work through the other three causes in the same order as above.
Why are my emails going into my spam folder?
Your mailbox provider’s spam filter put them there. Gmail notes that after you report an email as spam, “Emails from the same sender might be sent to the Spam folder in the future.” If the mail sitting in your own spam folder comes from your own app, the causes are the sender-side ones on this page.
How do I unspam my email?
As a recipient in Gmail, check the box next to the message and click Not spam at the top, then add the sender to Google Contacts, which stops Gmail sending their messages to Spam. As a sender there is no such button; the way back is fixing whichever of the four causes your headers point to.
Fixing the bug in this guide gets you past today. If you would rather have the whole foundation checked and built in one go, that is what the sprint below is for.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase