50% off SaaS Starter Kit — only for the first 100 buildersGrab it →
← Back to blog
emailMay 18, 2026·9 min read

Email Deliverability: Why Your Emails Land in Spam (and How to Fix It)

SPF, DKIM, DMARC — three acronyms standing between your transactional emails and the spam folder. Here's what they actually mean and how to set them up.

Robert Seghedi

Robert Seghedi

Co-founder, peal.dev

Email Deliverability: Why Your Emails Land in Spam (and How to Fix It)

We once had a SaaS app where users would sign up, never confirm their email, and churn immediately. Spent two weeks thinking the onboarding flow was broken. Rewrote the welcome screen. A/B tested the CTA button color (yes, we went there). Turns out the confirmation email was landing in spam for half our users. Two weeks of work, zero impact, one embarrassing realization.

Email deliverability is one of those things nobody teaches you until you're already losing users to the Promotions tab. So let's fix that. This post covers why emails end up in spam, the DNS records you actually need to configure, and how to test everything before it becomes a production disaster.

Why Email Providers Don't Trust You By Default

When you send an email from your app, you're essentially asking Gmail or Outlook to trust that you are who you say you are. The problem is anyone can set the 'From' header to anything — hello@apple.com, noreply@yourbank.com, whatever. Email was designed in a more trusting era of the internet, and that naivety is why we now have three layers of DNS-based authentication to prove we're legitimate.

Email providers use a combination of technical signals and reputation scores to decide where your email goes. The technical signals are the DNS records — SPF, DKIM, DMARC. The reputation score is built over time based on open rates, bounce rates, spam complaints, and how long your domain has been active. New domains start with zero reputation, which is why sending bulk email from a brand-new domain is basically screaming 'I am spam' to every inbox provider.

SPF: Telling the World Which Servers Can Send Your Email

SPF (Sender Policy Framework) is a DNS TXT record that lists every IP address or service allowed to send email on behalf of your domain. When an email arrives claiming to be from your domain, the receiving server checks your SPF record to verify the sending server is on the approved list.

If you're using Resend, SendGrid, or Postmark to send transactional emails, you need to add their sending infrastructure to your SPF record. Here's what a typical SPF record looks like:

# DNS TXT record for your domain
# Name: @ (or yourdomain.com)
# Value:
v=spf1 include:amazonses.com include:sendgrid.net ~all

# Breaking this down:
# v=spf1        → this is an SPF record
# include:...   → trust these services to send on my behalf
# ~all          → soft fail anything else (use -all for hard fail)

# If using Resend:
v=spf1 include:_spf.resend.com ~all

# If using multiple services (transactional + newsletter):
v=spf1 include:_spf.resend.com include:servers.mcsv.net ~all

One gotcha: you can only have one SPF record per domain. If you have two TXT records that both start with 'v=spf1', the lookup will fail and you'll look worse than having no SPF at all. Combine everything into a single record.

The ~all vs -all debate: ~all (soft fail) marks unauthorized senders as suspicious but still delivers. -all (hard fail) rejects them outright. Start with ~all while you're still figuring out all your sending sources, then graduate to -all once you're confident nothing legitimate will break.

DKIM: Cryptographic Proof Your Email Wasn't Tampered With

DKIM (DomainKeys Identified Mail) works differently from SPF. Instead of whitelisting IP addresses, it uses public-key cryptography. Your email provider signs outgoing emails with a private key. The receiving server fetches your public key from DNS and verifies the signature. If the email was modified in transit, the signature breaks and the check fails.

Your email provider generates the DKIM keys for you — you just need to add the public key to your DNS. It looks like this:

# DNS TXT record for DKIM
# The name format is: selector._domainkey.yourdomain.com
# Your provider gives you both the selector and the value

# Example for Resend:
# Name: resend._domainkey.yourdomain.com
# Value:
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...

# Example for Postmark:
# Name: pm._domainkey.yourdomain.com  
# Value:
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...

# You'll never type these manually — copy-paste from your provider's dashboard
# Just make sure there are no line breaks in the value

The selector (the part before '._domainkey') is just a label your provider chooses. You can have multiple DKIM records for the same domain, one per provider, using different selectors. This is totally fine and expected.

DMARC: The Policy That Ties It All Together

DMARC (Domain-based Message Authentication, Reporting, and Conformance) is the record that tells receiving servers what to do when SPF or DKIM checks fail. Without DMARC, even having SPF and DKIM doesn't give inbox providers a clear policy to enforce. It's also what unlocks DMARC reporting — you get emails (or use a service) that shows you who's sending email claiming to be from your domain.

# DNS TXT record for DMARC
# Name: _dmarc.yourdomain.com
# Value:

# Start here — monitoring only, nothing gets rejected:
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com

# After a week of clean reports, move to quarantine:
v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-reports@yourdomain.com

# Full enforcement (the goal):
v=DMARC1; p=reject; rua=mailto:dmarc-reports@yourdomain.com

# Breaking it down:
# p=none       → just monitor, don't take action
# p=quarantine → send failures to spam
# p=reject     → block failures entirely
# pct=25       → apply policy to 25% of failing mail (gradual rollout)
# rua=         → where to send aggregate reports

Don't jump straight to p=reject. Start with p=none for a week and actually check the reports. You'll often discover that some third-party tool you forgot about (Zendesk, a scheduling tool, your old Mailchimp integration) is sending email on your behalf and isn't in your SPF record. Fix those sources first, then tighten the policy.

The Practical Setup With Resend and Next.js

Most modern Next.js apps use Resend for transactional email, and it's genuinely the most painless setup we've used. But even with a good provider, you still need to wire up the DNS records. Here's a minimal working example of sending a transactional email with proper authentication in place:

// lib/email.ts
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

interface SendEmailOptions {
  to: string;
  subject: string;
  html: string;
  replyTo?: string;
}

export async function sendEmail({ to, subject, html, replyTo }: SendEmailOptions) {
  const { data, error } = await resend.emails.send({
    // This must match a verified domain in your Resend dashboard
    // The domain needs SPF + DKIM configured to get good deliverability
    from: 'Your App <noreply@yourdomain.com>',
    to,
    subject,
    html,
    replyTo: replyTo ?? 'support@yourdomain.com',
  });

  if (error) {
    // Don't swallow this error — log it properly
    console.error('Email send failed:', error);
    throw new Error(`Failed to send email: ${error.message}`);
  }

  return data;
}

// app/api/auth/confirm/route.ts
import { sendEmail } from '@/lib/email';

export async function POST(request: Request) {
  const { email, token } = await request.json();

  const confirmUrl = `${process.env.NEXT_PUBLIC_APP_URL}/confirm?token=${token}`;

  await sendEmail({
    to: email,
    subject: 'Confirm your email address',
    html: `
      <p>Click the link below to confirm your email:</p>
      <a href="${confirmUrl}">${confirmUrl}</a>
      <p>This link expires in 24 hours.</p>
    `,
  });

  return Response.json({ success: true });
}

The code is the easy part. What makes or breaks deliverability is the domain setup. In Resend's dashboard, go to Domains, add your domain, and it'll give you the exact DNS records to add. Do not skip this step and assume the default shared domain is fine for production. It's not — you're sharing reputation with every other Resend user on the free tier.

How to Actually Test Your Setup

Once you've added the DNS records, propagation can take anywhere from a few minutes to 48 hours. Use these tools to verify everything is working before you find out the hard way:

  • mail-tester.com — sends you a test address, you send an email to it, it scores your setup out of 10. Aim for 9+. Free for a few tests per day.
  • MXToolbox (mxtoolbox.com) — use the Email Header Analyzer and the individual SPF/DKIM/DMARC checkers. Tells you exactly what's broken.
  • Google Postmaster Tools — if you're sending any volume to Gmail addresses, set this up. It shows your domain reputation, spam rate, and authentication pass rates.
  • Resend/SendGrid/Postmark dashboards — check the bounce and complaint rates on every campaign. A complaint rate above 0.1% will get you flagged.
  • Send to a Gmail address you control and check the email headers — look for 'Authentication-Results' and make sure you see 'dkim=pass', 'spf=pass', and 'dmarc=pass'.

The Gmail header check is underrated. Open any email you sent to yourself, click the three-dot menu, 'Show original'. You'll see the full authentication chain. If any of those three say 'fail', you know exactly what to fix.

The Reputation Stuff That DNS Can't Fix

Perfect DNS records are necessary but not sufficient. Inbox providers also care about behavioral signals. Here's what actually hurts your reputation over time:

  • High bounce rates: Sending to addresses that don't exist damages your reputation fast. Validate email format on signup and remove hard bounces immediately from your list.
  • Spam complaints: Every time someone hits 'Mark as spam', that's a strike. Make unsubscribe easy — counterintuitively, a clean unsubscribe keeps your complaint rate low.
  • Low engagement: Emails that nobody opens eventually look like spam. Gmail especially uses engagement as a ranking signal.
  • Sending spikes: Going from 0 to 10,000 emails overnight looks suspicious. Warm up new domains by starting with small volumes and ramping over 2-4 weeks.
  • Spammy content: Trigger words like 'FREE!!!', 'You've been selected', 'Click now' in subject lines still matter. So does a wall of images with no text.
  • Missing List-Unsubscribe header: For any bulk or marketing email, this header is now required by Gmail and Yahoo. Your email provider should add this automatically, but verify.
The fastest way to kill your domain's email reputation is to import a bought or old list and blast it. We've seen founders do this on launch day and wonder why their transactional emails stopped working three days later. Your confirmation emails share reputation with your marketing emails.

Custom Domain vs Shared Sending Domain

One last thing worth saying explicitly: if you're on a free tier or haven't configured a custom domain with your email provider, your emails are probably going out from something like onboarding@resend.dev or a subdomain shared with thousands of other apps. The reputation of that shared domain is... mixed, to put it politely.

For any app with real users, configure a custom sending domain. Use a subdomain like mail.yourdomain.com or send.yourdomain.com rather than your root domain — that way, if your email reputation takes a hit (from a bug that sends duplicate emails at 3am, hypothetically, not that this has ever happened to us), it doesn't affect your main website's domain reputation.

If you're using one of the peal.dev templates, the email setup comes pre-wired with Resend integration and the right environment variable structure — you still need to do the DNS work, but you won't spend time figuring out where to plug things in.

The TL;DR Checklist

  • Add SPF record: one TXT record at @, include your email provider's SPF, use ~all to start
  • Add DKIM record: copy the TXT record from your provider's dashboard, exact value, no line breaks
  • Add DMARC record: start with p=none and an rua address, tighten after a week of clean reports
  • Use a custom sending domain, ideally a subdomain like mail.yourdomain.com
  • Verify with mail-tester.com before you go live
  • Set up Google Postmaster Tools if you're sending any volume to Gmail
  • Remove hard bounces immediately and keep complaint rates below 0.1%
  • Never send to a bought or cold list from the same domain as your transactional email

Email deliverability isn't glamorous work. It's DNS records and reputation management and checking headers in Gmail. But getting it right once means your users actually receive the emails you send them — and that's worth more than any onboarding flow optimization.

Newsletter

Liked this post? There's more where it came from.

Dev guides, honest build stories, and the occasional 2am debugging confession — straight to your inbox. No spam, unsubscribe anytime.

Browse templates
Written by humansWeekly dropsSubscriber perks

Join the Discord

Ask questions, share builds, get help from founders