{"course":{"slug":"cloud-and-shipping","title":"Shipping It","url":"https://addaly.com/learn/cloud-and-shipping","level":"intermediate","track":"build"},"lesson":{"slug":"email-that-arrives","title":"Why your email went to spam","url":"https://addaly.com/learn/cloud-and-shipping/email-that-arrives","position":38,"of":74,"minutes":9,"answer":"Transactional email fails silently, and the three DNS records that stop it failing are the ones the major providers now check on every message.","keyIdea":"Transactional email fails silently, and the three DNS records that stop it failing are the ones the major providers now check on every message."},"body":{"format":"markdown","text":"## The failure nobody reports\n\nA user requests a password reset. The email goes to spam, or is dropped by the receiving server without a bounce. They do not tell you. They tell nobody. They leave.\n\nEmail is infrastructure with no error message, which is why it deserves a lesson of its own in a course about shipping.\n\n## You cannot send it from your own server\n\nTwo facts settle this.\n\nOutbound port 25 is blocked by essentially every cloud provider by default: AWS, Google Cloud, Azure, DigitalOcean, Hetzner. That is deliberate, because compromised virtual machines are the internet's main source of spam.\n\nAnd even with it open, a brand-new IP address has no reputation, which means the large mailbox providers treat it with suspicion for weeks. Building sending reputation is a months-long process that a relay has already done for you.\n\nSo use a relay. Do not run your own mail server for transactional email. Running one for your own inbox is a defensible hobby; putting your password resets behind it is not.\n\n## The three records\n\n**SPF**: a TXT record on your domain listing who may send on your behalf.\n\n```\nv=spf1 include:_spf.example-provider.com -all\n```\n\nThere is a hard limit of 10 DNS lookups when evaluating SPF, and each `include:` may cost several. Add four providers and SPF silently fails with a permerror, which is a common cause of \"it worked until we added the newsletter tool\".\n\n**DKIM**: a cryptographic signature on each message, with the public key published in DNS at a selector your provider gives you. This is what proves the message was not forged or altered, and it survives forwarding, which SPF does not.\n\n**DMARC**: a policy saying what to do when SPF and DKIM fail, plus an address to send reports to.\n\n```\nv=DMARC1; p=none; rua=mailto:dmarc@example.com\n```\n\nStart at `p=none`, which changes nothing and only collects reports. Read them for two weeks: they will show you every system sending as your domain, including the two you forgot. Then move to `p=quarantine` and later `p=reject`. Going straight to `p=reject` is how people stop their own invoicing system from delivering.\n\nSince February 2024, Google and Yahoo require all three from bulk senders, plus one-click unsubscribe on marketing mail and a spam complaint rate kept under 0.3%. That is not a guideline; mail that fails it is rejected.\n\nCheck any domain from a terminal:\n\n```bash\ndig +short TXT example.com | grep spf\ndig +short TXT _dmarc.example.com\n```\n\n## Authentication is necessary and not sufficient\n\nThe rest is behaviour, and it is where most deliverability is actually won or lost:\n\n- **Send from a subdomain.** `mail.example.com` for transactional, a different one for marketing. Then a badly received campaign cannot damage the reputation carrying your password resets. Keep them separate from the first day; separating them later is much harder.\n- **Warm up gradually.** A new sending domain going from zero to 50,000 messages in a day looks exactly like a compromised account.\n- **Never buy a list.** Spam traps are seeded addresses that exist only on purchased lists, and hitting one gets you blocked outright.\n- **Honour unsubscribes immediately**, and make the link obvious. A complaint is far more damaging than an unsubscribe: a user who cannot find the unsubscribe link presses the spam button, and that is the metric that gets you blocked.\n- **Set a real `Reply-To`.** `no-reply@` addresses depress engagement, and engagement is a ranking signal.\n\n## Process bounces and complaints\n\nYour provider sends webhooks for delivery, bounce and complaint events. Handle them, and store the result.\n\nA **hard bounce** means the address does not exist. Continuing to send to it is one of the strongest negative signals there is. Mark it dead and stop. A **soft bounce** is temporary (a full mailbox) and can be retried a few times. A **complaint** means somebody pressed spam; suppress that address permanently, whatever your records say about consent.\n\n## Testing it\n\nSend to a real Gmail account and open \"Show original\". You want to see `SPF: PASS`, `DKIM: PASS` and `DMARC: PASS`. **mail-tester.com** is free, gives you a score out of 10 and names what is wrong. Do this before launch, not after the first support ticket.\n\nIn development, do not send at all. **Mailpit** or **MailHog** (one binary each, free) capture everything and show it in a web UI.\n\n## The free path\n\n**Brevo** has a free tier of a few hundred messages a day; **Resend** and **Mailgun** have small free tiers; **Amazon SES** is the cheap option at large volume and gives you almost no hand-holding; **Postmark** costs more and is the most reliable for transactional mail specifically. Free tiers move constantly, so check before you build against one, and keep the sending code behind a small interface so switching provider is an afternoon rather than a project."},"check":{"question":"Your password reset emails deliver to Gmail for months, then start landing in spam shortly after marketing launches a newsletter from the same domain. Authentication records are unchanged and still pass. What is the most likely cause?","options":[{"text":"Gmail changed its filtering rules, and there is nothing you can do but wait","correct":false,"tempting":"Attributes it to an external change that coincides suspiciously with your own, and abandons the investigation at the first plausible excuse."},{"text":"The newsletter tool was added to SPF, pushing it past the 10-lookup limit","correct":false,"tempting":"A real and nasty failure mode, but it would cause SPF to fail; and the question states authentication still passes."},{"text":"The newsletter's complaint rate has damaged the domain's sending reputation, which the transactional mail shares because both send from the same domain","correct":true},{"text":"The transactional volume is too low to maintain reputation on its own","correct":false,"tempting":"Volume alone does not push previously-delivering mail into spam, and it does not explain the timing."}]},"prev":{"slug":"uploads-and-object-storage","title":"Files people send you","url":"https://addaly.com/learn/cloud-and-shipping/uploads-and-object-storage"},"next":{"slug":"backups-and-restore","title":"The restore you have never tested","url":"https://addaly.com/learn/cloud-and-shipping/backups-and-restore"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Why your email went to spam', in Shipping It. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/cloud-and-shipping/email-that-arrives (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Why your email went to spam","courseTitle":"Shipping It","module":{"index":4,"slug":"load-cost-and-abuse","title":"Load, cost and the traffic that is not customers"},"answer":null,"keyIdea":"Transactional email fails silently, and the three DNS records that stop it failing are the ones the major providers now check on every message.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-failure-nobody-reports","runs":[{"text":"The failure nobody reports"}]},{"t":"p","runs":[{"text":"A user requests a password reset. The email goes to spam, or is dropped by the receiving server without a bounce. They do not tell you. They tell nobody. They leave."}]},{"t":"p","runs":[{"text":"Email is infrastructure with no error message, which is why it deserves a lesson of its own in a course about shipping."}]},{"t":"h2","id":"you-cannot-send-it-from-your-own-server","runs":[{"text":"You cannot send it from your own server"}]},{"t":"p","runs":[{"text":"Two facts settle this."}]},{"t":"p","runs":[{"text":"Outbound port 25 is blocked by essentially every cloud provider by default: AWS, Google Cloud, Azure, DigitalOcean, Hetzner. That is deliberate, because compromised virtual machines are the internet’s main source of spam."}]},{"t":"p","runs":[{"text":"And even with it open, a brand-new IP address has no reputation, which means the large mailbox providers treat it with suspicion for weeks. Building sending reputation is a months-long process that a relay has already done for you."}]},{"t":"p","runs":[{"text":"So use a relay. Do not run your own mail server for transactional email. Running one for your own inbox is a defensible hobby; putting your password resets behind it is not."}]},{"t":"h2","id":"the-three-records","runs":[{"text":"The three records"}]},{"t":"p","runs":[{"text":"SPF","b":true},{"text":": a TXT record on your domain listing who may send on your behalf."}]},{"t":"code","lang":"","label":"Text","text":"v=spf1 include:_spf.example-provider.com -all"},{"t":"p","runs":[{"text":"There is a hard limit of 10 DNS lookups when evaluating SPF, and each "},{"text":"include:","code":true},{"text":" may cost several. Add four providers and SPF silently fails with a permerror, which is a common cause of “it worked until we added the newsletter tool”."}]},{"t":"p","runs":[{"text":"DKIM","b":true},{"text":": a cryptographic signature on each message, with the public key published in DNS at a selector your provider gives you. This is what proves the message was not forged or altered, and it survives forwarding, which SPF does not."}]},{"t":"p","runs":[{"text":"DMARC","b":true},{"text":": a policy saying what to do when SPF and DKIM fail, plus an address to send reports to."}]},{"t":"code","lang":"","label":"Text","text":"v=DMARC1; p=none; rua=mailto:dmarc@example.com"},{"t":"p","runs":[{"text":"Start at "},{"text":"p=none","code":true},{"text":", which changes nothing and only collects reports. Read them for two weeks: they will show you every system sending as your domain, including the two you forgot. Then move to "},{"text":"p=quarantine","code":true},{"text":" and later "},{"text":"p=reject","code":true},{"text":". Going straight to "},{"text":"p=reject","code":true},{"text":" is how people stop their own invoicing system from delivering."}]},{"t":"p","runs":[{"text":"Since February 2024, Google and Yahoo require all three from bulk senders, plus one-click unsubscribe on marketing mail and a spam complaint rate kept under 0.3%. That is not a guideline; mail that fails it is rejected."}]},{"t":"p","runs":[{"text":"Check any domain from a terminal:"}]},{"t":"code","lang":"bash","label":"Bash","text":"dig +short TXT example.com | grep spf\ndig +short TXT _dmarc.example.com"},{"t":"h2","id":"authentication-is-necessary-and-not-sufficient","runs":[{"text":"Authentication is necessary and not sufficient"}]},{"t":"p","runs":[{"text":"The rest is behaviour, and it is where most deliverability is actually won or lost:"}]},{"t":"ul","items":[[{"text":"Send from a subdomain.","b":true},{"text":" "},{"text":"mail.example.com","code":true},{"text":" for transactional, a different one for marketing. Then a badly received campaign cannot damage the reputation carrying your password resets. Keep them separate from the first day; separating them later is much harder."}],[{"text":"Warm up gradually.","b":true},{"text":" A new sending domain going from zero to 50,000 messages in a day looks exactly like a compromised account."}],[{"text":"Never buy a list.","b":true},{"text":" Spam traps are seeded addresses that exist only on purchased lists, and hitting one gets you blocked outright."}],[{"text":"Honour unsubscribes immediately","b":true},{"text":", and make the link obvious. A complaint is far more damaging than an unsubscribe: a user who cannot find the unsubscribe link presses the spam button, and that is the metric that gets you blocked."}],[{"text":"Set a real ","b":true},{"text":"Reply-To","b":true,"code":true},{"text":".","b":true},{"text":" "},{"text":"no-reply@","code":true},{"text":" addresses depress engagement, and engagement is a ranking signal."}]]},{"t":"h2","id":"process-bounces-and-complaints","runs":[{"text":"Process bounces and complaints"}]},{"t":"p","runs":[{"text":"Your provider sends webhooks for delivery, bounce and complaint events. Handle them, and store the result."}]},{"t":"p","runs":[{"text":"A "},{"text":"hard bounce","b":true},{"text":" means the address does not exist. Continuing to send to it is one of the strongest negative signals there is. Mark it dead and stop. A "},{"text":"soft bounce","b":true},{"text":" is temporary (a full mailbox) and can be retried a few times. A "},{"text":"complaint","b":true},{"text":" means somebody pressed spam; suppress that address permanently, whatever your records say about consent."}]},{"t":"h2","id":"testing-it","runs":[{"text":"Testing it"}]},{"t":"p","runs":[{"text":"Send to a real Gmail account and open “Show original”. You want to see "},{"text":"SPF: PASS","code":true},{"text":", "},{"text":"DKIM: PASS","code":true},{"text":" and "},{"text":"DMARC: PASS","code":true},{"text":". "},{"text":"mail-tester.com","b":true},{"text":" is free, gives you a score out of 10 and names what is wrong. Do this before launch, not after the first support ticket."}]},{"t":"p","runs":[{"text":"In development, do not send at all. "},{"text":"Mailpit","b":true},{"text":" or "},{"text":"MailHog","b":true},{"text":" (one binary each, free) capture everything and show it in a web UI."}]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"p","runs":[{"text":"Brevo","b":true},{"text":" has a free tier of a few hundred messages a day; "},{"text":"Resend","b":true},{"text":" and "},{"text":"Mailgun","b":true},{"text":" have small free tiers; "},{"text":"Amazon SES","b":true},{"text":" is the cheap option at large volume and gives you almost no hand-holding; "},{"text":"Postmark","b":true},{"text":" costs more and is the most reliable for transactional mail specifically. Free tiers move constantly, so check before you build against one, and keep the sending code behind a small interface so switching provider is an afternoon rather than a project."}]}],"check":{"question":[{"text":"Your password reset emails deliver to Gmail for months, then start landing in spam shortly after marketing launches a newsletter from the same domain. Authentication records are unchanged and still pass. What is the most likely cause?"}],"options":[{"text":[{"text":"Gmail changed its filtering rules, and there is nothing you can do but wait"}],"correct":false,"tempting":[{"text":"Attributes it to an external change that coincides suspiciously with your own, and abandons the investigation at the first plausible excuse."}]},{"text":[{"text":"The newsletter tool was added to SPF, pushing it past the 10-lookup limit"}],"correct":false,"tempting":[{"text":"A real and nasty failure mode, but it would cause SPF to fail; and the question states authentication still passes."}]},{"text":[{"text":"The newsletter’s complaint rate has damaged the domain’s sending reputation, which the transactional mail shares because both send from the same domain"}],"correct":true,"tempting":null},{"text":[{"text":"The transactional volume is too low to maintain reputation on its own"}],"correct":false,"tempting":[{"text":"Volume alone does not push previously-delivering mail into spam, and it does not explain the timing."}]}],"order":[0,2,3,1],"why":null},"prev":{"slug":"uploads-and-object-storage","title":"Files people send you","minutes":9},"next":{"slug":"backups-and-restore","title":"The restore you have never tested","minutes":8}}}