Skip to content

guides

Email deliverability monitoring: catch problems before customers do

April 30, 2026

#deliverability#monitoring#dmarc#postmaster-tools#alerts
Email deliverability monitoring: catch problems before customers do
Visual: Bramble Ashworth on okslop

You ship a password-reset email a hundred thousand times a day and assume it works, until a support ticket says "I never got the code." By then the dashboards have been red for a week and you had no idea.

Email is the one part of your stack that can fail completely while reporting total success. A spam-foldered message is accepted by the receiver, delivered without error, and quietly filed where nobody looks. There is no 500, no exception, no log line that says "this went to spam." So most teams run blind: they find out mail is broken when a customer complains, which means by definition some number of customers already silently churned, reset nothing, and moved on.

Monitoring fixes the blindness. The receivers actually publish the data you need, you just have to go pull it and watch it. This guide builds a real deliverability monitor by hand: the four signal sources, the thresholds worth alerting on, and the scheduled jobs that turn "I hope email works" into "I get paged when it doesn't." Near the end, we show where a stack like Tinbox surfaces these signals as first-party data instead of making you scrape three dashboards.

How do I monitor email deliverability?

Watch four signal sources continuously instead of reacting to complaints: receiver postmaster dashboards for reputation and spam rate, DMARC aggregate reports for authentication pass rates, your own pipeline for bounce and complaint rates, and blocklist lookups for your domain and IP. Pull them on a schedule and alert when a threshold trips. No single source is complete, which is why a real monitor combines all four.

Here is what each source tells you and why you need it:

  • Receiver postmaster tools (Gmail, Outlook). The mailbox providers grade your domain and report the spam rate their users assign you. This is the closest thing to "what the receiver thinks of me."
  • DMARC aggregate reports. Every major receiver emails you a daily XML summary of how your mail authenticated at their edge. This catches SPF and DKIM problems across providers you do not otherwise have a dashboard for.
  • Your own send pipeline. Bounces, complaints, and deferrals are signals you generate yourself, in real time, on every send. They are the earliest warning you have.
  • Blocklists. A public listing silently kills delivery to anyone who consults that list. Cheap to check, expensive to miss.

The rest of this guide wires each one up.

What deliverability metrics should I alert on?

Alert on leading indicators, the metrics that predict filtering before a customer notices. The five that matter most: Gmail spam rate, domain reputation, DMARC pass rate, hard bounce rate, and blocklist status. Each has a threshold where you go from "fine" to "act now."

MetricWarn atCritical atSource
Gmail spam rate0.1%0.3%Postmaster Tools
Domain reputationMediumLow / BadPostmaster Tools
DMARC pass rate< 98%< 95%DMARC rua reports
Hard bounce rate (per send)2%5%Your pipeline
Complaint rate (per send)0.1%0.3%Your pipeline / SNDS
Blocklist listingany new listingpersists 24hBlocklist DNS

The 0.3 percent spam-rate line is not arbitrary: it is the explicit ceiling in the Gmail and Yahoo bulk-sender rules, and crossing it gets you filtered regardless of how clean your authentication is. The 0.1 percent warn level gives you room to react before you hit the wall. For the full set of bulk-sender obligations behind these numbers, see the Gmail and Yahoo bulk sender requirements, and for the wider panel of signals worth a threshold, the deliverability metrics worth watching and their alert thresholds lines them up with the numbers.

The reason these are leading indicators matters. By the time a customer reports "I never got the email," the spam rate has usually been elevated and the reputation already slipping for days. Monitoring buys you that lead time.

How do I read DMARC aggregate reports for monitoring?

Publish a DMARC record with a rua address and the receivers will email you a daily XML report of every message they saw from your domain, including whether it passed SPF and DKIM and whether it aligned. The pass rate over time is your cross-receiver authentication monitor, and a sudden drop means something broke in DNS or in your sending path.

First, turn reports on. Add or update the DMARC record so receivers know where to send aggregate reports:

dig +short TXT _dmarc.yourdomain.com
"v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; ri=86400"

The rua is the aggregate-report mailbox and ri=86400 requests daily reporting. Within a day or two you start receiving gzipped XML attachments, one per reporting receiver. The raw format is hostile to read by eye, so the monitoring job parses it. Each report contains rows like this:

<record>
  <row>
    <source_ip>203.0.113.10</source_ip>
    <count>4120</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>pass</spf>
    </policy_evaluated>
  </row>
</record>

A minimal parser computes the pass rate weighted by count, because one report can summarize tens of thousands of messages:

import { XMLParser } from "fast-xml-parser";

// reportXml is the decompressed aggregate report
function dmarcPassRate(reportXml) {
  const parsed = new XMLParser().parse(reportXml);
  const records = [].concat(parsed.feedback.record);
  let total = 0;
  let passing = 0;
  for (const r of records) {
    const count = Number(r.row.count);
    const { dkim, spf } = r.row.policy_evaluated;
    total += count;
    // DMARC passes if either aligned mechanism passes
    if (dkim === "pass" || spf === "pass") passing += count;
  }
  return total === 0 ? 1 : passing / total;
}

Here is the failure mode this catches. You rotate a DKIM key, publish the new public key, but a propagation delay or a typo means a fraction of mail signs with a key the receiver cannot verify. Your test message (sent after propagation) passes, so you think you are fine. The aggregate report tells the truth: it shows pass rate dropped from 99.8 percent to 91 percent overnight, weighted across real traffic you never see. That gap between "my one test passed" and "8 percent of production is failing" is exactly what monitoring exists to surface. If the authentication records themselves are new to you, the SPF, DKIM, and DMARC setup walkthrough covers what each record does and the alignment rules.

How do I monitor bounce and complaint rates from my own pipeline?

Compute delivery, bounce, and complaint rates per send directly from your own send events, because they are the earliest and most accurate signal you have. A bad list or a broken template shows up here in minutes, long before postmaster dashboards (which lag a day) or DMARC reports (also daily) catch up.

You need an event stream from your sending path. Every send produces an outcome: accepted, hard bounce, soft bounce, or later a complaint (a "this is spam" click reported back). Roll those up per send and per rolling window:

// events: { type: 'delivered' | 'hard_bounce' | 'soft_bounce' | 'complaint' }
function rates(events) {
  const n = events.length;
  const count = (t) => events.filter((e) => e.type === t).length;
  return {
    sent: n,
    deliveryRate: count("delivered") / n,
    hardBounceRate: count("hard_bounce") / n,
    complaintRate: count("complaint") / n,
  };
}

const r = rates(sendEvents);
if (r.hardBounceRate > 0.02) {
  alert(`High hard bounce rate: ${(r.hardBounceRate * 100).toFixed(2)}%`);
}
if (r.complaintRate > 0.001) {
  alert(`Complaint rate elevated: ${(r.complaintRate * 100).toFixed(2)}%`);
}

The distinction between hard and soft bounces matters for both alerting and reputation. A hard bounce (550 No such user) is a permanent failure and the address must go on a suppression list immediately so you never send to it again. A soft bounce (452 mailbox full) is temporary and you can retry. Re-sending to hard-bounced addresses is one of the fastest ways to wreck reputation, so the monitor and the suppression list are two halves of the same job. The full classification and suppression model is in the guide to handling email bounces.

One caveat that bites people: bounce and complaint feedback only works if you can receive it. Complaints come back via feedback loops (FBLs) that receivers send to your reporting address, and bounces come back as SMTP responses or asynchronous DSNs. If your sending provider swallows these and only shows you a summary dashboard, your monitor is only as good as their dashboard. Owning the pipeline means owning the raw events.

How do I watch for blocklist listings?

Run scheduled DNS lookups against the major blocklists for your sending IP and domain, because a listing silently stops delivery to every receiver that consults that list and there is no other notification. The check is a single DNS query per list, so it is cheap enough to run daily. If you would rather start with hosted checkers before wiring your own cron, the free tools to test your deliverability and DNS cover the manual equivalents.

Blocklists answer over DNS: you reverse the IP octets, append the blocklist zone, and a 127.0.0.x answer means listed:

# Check sending IP 203.0.113.10 against Spamhaus ZEN
# Reverse octets: 10.113.0.203
dig +short 10.113.0.203.zen.spamhaus.org
# A 127.0.0.x answer = LISTED. Empty = clean.
127.0.0.4

Wrap it so the monitor checks several lists and your domain too:

import { promises as dns } from "node:dns";

async function isListed(query, zone) {
  try {
    const answers = await dns.resolve4(`${query}.${zone}`);
    return answers.some((a) => a.startsWith("127.0.0."));
  } catch (err) {
    if (err.code === "ENOTFOUND" || err.code === "ENODATA") return false; // not listed
    throw err; // real DNS error, don't swallow as "clean"
  }
}

const reversedIp = "10.113.0.203";
const ipListed = await isListed(reversedIp, "zen.spamhaus.org");
const domainListed = await isListed("yourdomain.com", "dbl.spamhaus.org");
if (ipListed || domainListed) alert("Blocklist listing detected");

Note the failure mode in the catch block: ENOTFOUND genuinely means "not listed," but a real DNS timeout or SERVFAIL must not be silently treated as "clean," or your monitor will report green while it is actually broken. A monitor that fails silently is worse than no monitor, because it manufactures false confidence. If a listing does appear, each list has its own delisting process and you fix the root cause first; the blocklist removal guide walks the delisting steps per major list.

How often should I run these checks?

Run pipeline metrics in real time on every send, and run the receiver-side pulls (postmaster, DMARC, blocklists) on a daily schedule, because that is the cadence the data actually updates on. Anything you check by hand you will eventually forget to check, so automate all of it.

A simple scheduler ties it together. On Cloudflare Workers, a Cron Trigger is the natural home for the daily pulls:

{
  "triggers": {
    "crons": ["0 13 * * *"], // daily at 13:00 UTC, after receivers refresh
  },
}
export default {
  async scheduled(event, env, ctx) {
    const [reputation, dmarc, blocklist] = await Promise.all([
      pullPostmasterReputation(env),
      processDmarcReports(env),
      checkBlocklists(env),
    ]);
    const breaches = evaluateThresholds({ reputation, dmarc, blocklist });
    if (breaches.length) await notify(env, breaches);
  },
};

The real-time pipeline metrics ride along on the send path itself, not the cron: compute rates as events arrive and alert inline, because a bad send is an emergency you want to hear about in minutes, not at 13:00 UTC tomorrow.

Route alerts somewhere you actually watch. A breach that lands in an unmonitored email inbox (the irony) helps nobody. Send to Slack, PagerDuty, or whatever your on-call already lives in. And tune the thresholds: too sensitive and you train yourself to ignore the channel, too loose and you are back to learning from customers.

Where does Tinbox fit in deliverability monitoring?

Tinbox surfaces these signals as first-party data because it runs the whole sending and receiving stack on your own Cloudflare account, so the bounce events, complaint feedback, authentication results, and DMARC reports land in your own D1 instead of behind a vendor's dashboard. Everything above is still how email monitoring works; what changes is that you are not stitching together Postmaster Tools, SNDS, a DMARC parser, and a provider's API to assemble one picture.

Tinbox is a self-contained, tenant-deployed email stack: a transactional sender, a newsletter tool, and a human webmail inbox, all on D1, R2, Queues, and Workers in your account. Because the sender is yours, the raw send events are yours: every hard bounce, soft bounce, and complaint is a row you own and can query, not a number on someone else's chart. The pipeline metrics in this guide are computed from data that is already in your database.

On setup, the bootstrapper publishes the DMARC rua record that makes aggregate reporting work, so the cross-receiver authentication monitor is on from the first send:

npx tinbox init yourdomain.com
✔ Provisioned D1 database (tinbox-yourdomain-db)
✔ Provisioned R2 bucket (tinbox-yourdomain-mail)
✔ Generated DKIM keypair (selector: tbx1)

Add these DNS records, then `npx tinbox deploy`:
  SPF    yourdomain.com         TXT  "v=spf1 ... ~all"
  DKIM   tbx1._domainkey...     TXT  "v=DKIM1; k=rsa; p=MIGf..."
  DMARC  _dmarc.yourdomain.com  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@..."

Two honest caveats, because monitoring is where overclaiming gets you burned. First, the receiver-side dashboards are still external: Tinbox owns your send events and parses your DMARC reports, but Google Postmaster Tools and Microsoft SNDS are Google's and Microsoft's products, and you still register your domain with them. No stack can move those in-house, so treat Postmaster reputation as a source you connect, not one Tinbox replaces. Second, no monitor manufactures reputation: catching a problem early is not the same as preventing it, and a new domain still has to be warmed gradually before any of these metrics mean much. Owning the stack shortens the gap between a problem and your knowledge of it; it does not repeal how filters work.

For the broader model of why running your own sender changes the deliverability calculus, the Tinbox overview lays it out, and the deliverability guide is the pillar that ties the authentication, reputation, and content pieces together.

Deploy this yourself: npx tinbox init yourdomain.com. Then turn on the DMARC rua record, register with Postmaster Tools, and wire a daily cron so the dashboard tells you before the customer does. The deliverability docs cover the alert thresholds in more detail.

Frequently asked questions

How do I monitor email deliverability?

Watch four signal sources on a schedule rather than reacting to complaints. First, receiver postmaster tools: Google Postmaster Tools gives you domain reputation and spam rate for Gmail, and Microsoft SNDS gives you complaint data for Outlook. Second, DMARC aggregate (rua) reports, which arrive as daily XML showing SPF and DKIM pass rates across every receiver. Third, your own sending pipeline, where you compute bounce rate, complaint rate, and delivery rate per send. Fourth, blocklist lookups against your domain and sending IP. Pull these on a cron and alert when a threshold trips, such as spam rate above 0.3 percent or a new blocklist listing.

What deliverability metrics should I alert on?

Alert on the metrics that predict or confirm filtering before customers notice. The high-value thresholds: Gmail spam rate above 0.1 percent (warning) or 0.3 percent (critical, the bulk-sender limit), domain reputation dropping below High in Postmaster Tools, DMARC pass rate falling below roughly 95 percent for a steady sender, hard bounce rate above 2 percent on a single send, and any new blocklist listing for your domain or sending IP. These are leading indicators: by the time a customer says 'I never got the email,' the dashboards have usually been red for days.

Why do I only find out my email is broken when a customer complains?

Because the default email stack gives you no proactive signal. A spam-foldered message is accepted and delivered with no bounce, no error, and no log line, so nothing tells you placement dropped. Authentication can quietly start failing after a DNS change, a key rotation, or a provider switch, and you will not see it unless you read DMARC reports or postmaster dashboards. Monitoring closes this gap by polling the receiver-side signals on a schedule and alerting you, so the dashboard tells you before the customer does.

Is Google Postmaster Tools enough to monitor deliverability?

It is necessary but not sufficient. Google Postmaster Tools covers Gmail only, gives domain reputation and spam rate for senders with enough volume, and lags by a day or two. It tells you nothing about Outlook, Yahoo, or corporate receivers, and it goes blank below a volume threshold. A complete monitor pairs Postmaster Tools with Microsoft SNDS for Outlook, DMARC aggregate reports for cross-receiver authentication, your own bounce and complaint telemetry, and blocklist checks. Postmaster Tools is the single most useful source, but relying on it alone leaves most of your traffic unmonitored.

How often should I check email deliverability?

Continuously for the signals you control and daily for the receiver-side ones. Bounce and complaint rates from your own pipeline should be computed on every send and alert in real time, because a bad send shows up immediately. Postmaster reputation, SNDS complaint data, and DMARC aggregate reports update roughly once a day, so a daily scheduled pull is the right cadence. Blocklist lookups are cheap, so run them daily too. Manual spot-checks are fine to start, but anything you check by hand you will eventually forget to check, which is the whole reason to automate it.