An n8n flow that polls Google Postmaster Tools, parses DMARC aggregate reports from a shared mailbox, runs DNSBL lookups across the major blocklists, and pulls bounce and complaint rates from Smartlead and Instantly — then alerts the assigned RevOps owner in Slack the moment any domain crosses a documented threshold, with a Claude-drafted remediation step attached. The bundle at apps/web/public/artifacts/email-deliverability-monitor-n8n/ ships the complete n8n export plus a _README.md covering import, environment variables, credential setup, threshold tuning, and per-branch verification.
When to use this
Use it when outbound volume is high enough that finding out a sending domain has been suppressed after the fact is a multi-week revenue event — typically when at least one team is sending more than 10,000 messages per week across two or more dedicated domains. By the time Gmail flips the bit and starts routing to spam, Postmaster Tools is already showing the spam rate over 0.3% — the Gmail and Yahoo bulk-sender threshold in effect since February 2024 — and you have already lost a deliverability cycle on every account currently in sequence. The point of this flow is to fire the alert when the rate crosses 0.1% — days before suppression, not days after.
It is also the right pattern when you operate multiple sending platforms (Smartlead for cold, Outreach for warm follow-up, a separate ESP for marketing) and need a single view that compares complaint and bounce rates across all of them on the same scale. The flow normalizes each vendor’s reporting into one record per domain per day so the RevOps lead can see in one Slack message whether the problem is platform-wide or isolated to one sender.
The Claude-drafted remediation step is the part that turns the alert from a notification into a runnable response. Each alert carries the specific corrective action — pause the sequence on a named domain, drop volume by 30% on warmup, request reinstatement from a named blocklist — based on which threshold tripped, not a generic “investigate deliverability.”
When NOT to use this
Skip this if your outbound volume is under 1,000 messages per week from a single domain. At that volume, Postmaster Tools will not show a usable spam-rate signal — the dashboard requires roughly 100+ daily deliveries to a Gmail address before reporting at all — and DMARC aggregate reports arrive too sparsely to drive a daily check. Watching deliverability manually inside the sending platform’s UI is the right tier of monitoring at that scale.
Skip it if you do not control your own sending domains. The flow assumes you have configured SPF, DKIM with at least one selector per domain, and DMARC with rua=mailto: reports going to a mailbox you can read. If you send through a shared subdomain on a vendor’s infrastructure (the default on most ESPs’ free tiers), DMARC RUA reports are aggregated at the vendor, not at you, and most of the polling paths in this flow return nothing useful.
Do not use the alert as the only deliverability discipline. The flow watches for the symptom — spam rate climbing, bounce rate climbing, a blocklist listing appearing — but the upstream cause (list hygiene, copy that triggers filters, sending pattern that looks like a burst) is a behavior problem. The weekly review where the RevOps lead and SDR manager look at the breakdown by sender and topic still has to happen.
Setup
Import the bundle. Drop apps/web/public/artifacts/email-deliverability-monitor-n8n/email-deliverability-monitor-n8n.json into n8n via Workflows → Import from File. Three trigger paths: a Schedule Trigger that runs every hour (Postmaster + DNSBL + ESP metric polls), a separate Schedule Trigger that runs every 15 minutes (IMAP poll for DMARC reports), and a manual webhook for ad-hoc domain checks.
Configure your domain register. The list of domains to watch lives in the Domain Register (Static) Code node — an array of objects shaped { domain, sendingPlatform, owner, slackHandle, severity }. Edit the array once with your real domains; the rest of the flow keys off it. Severity (primary / warmup / secondary) drives the alert color and the on-call routing.
Set environment variables. Twelve variables in total — Postmaster API token, IMAP credentials for the DMARC mailbox, Smartlead and Instantly API keys, DNSBL zone list, Slack channel names per severity tier, and the threshold values themselves. Full table is in _README.md. The thresholds default to: spam-rate alert at 0.1%, suppression threshold at 0.3%, bounce-rate alert at 5%, complaint-rate alert at 0.08%.
Wire credentials. Five credentials are required:
PLACEHOLDER_POSTMASTER_CRED_ID — Google OAuth2 with gmail.postmaster.readonly scope
PLACEHOLDER_IMAP_CRED_ID — IMAP login for the mailbox receiving DMARC RUA reports
PLACEHOLDER_SMARTLEAD_CRED_ID — HTTP Header Auth with the Smartlead API key
PLACEHOLDER_INSTANTLY_CRED_ID — HTTP Header Auth with the Instantly API key
PLACEHOLDER_SLACK_CRED_ID — Slack bot token with chat:write
Point DMARC RUA to a real mailbox. In your DNS, the DMARC record for each watched domain should include rua=mailto:dmarc-reports@yourcompany.com. Create the mailbox if it does not exist. Most major mailbox providers (Google Workspace, Microsoft 365, Fastmail) deliver DMARC XML attachments without issue; the IMAP parser in the flow unzips .zip and .gz attachments and reads the XML directly.
Walk the verification._README.md lists a five-step verification that exercises each branch — a manual webhook hit, a forced threshold trip, a stale-report test, a DNSBL false-positive test, and a multi-domain burst test. Run all five before activating the schedule triggers.
What the flow does
Schedule — Hourly Sweep fires every hour at the top of the hour and runs three parallel branches in a SplitInBatches: Postmaster Tools API (https://gmailpostmastertools.googleapis.com/v1/domains/<domain>/trafficStats) for each domain in the register, Smartlead /api/v1/campaign-statistics and Instantly /api/v2/accounts/health for the ESP-side numbers, and a DNSBL probe that performs A-record lookups against each blocklist’s zone (<reversed-ip>.<zone>) for the resolved IP of each domain’s MX record. The branches converge at Merge — Per-Domain Snapshot, which collapses one record per (domain, sourceMetric, dateBucket) and rejects records older than 26 hours so a slow API does not poison the most recent comparison.
Threshold Check (Code) reads the merged snapshot against the threshold envs and emits one of three statuses per metric: ok, alert (rate above alert threshold but below suppression), or critical (rate above suppression threshold OR domain appears on a blocklist). The Code node holds the policy in one place — every threshold and its rationale are commented inline so the RevOps lead can read and tune them without opening a JIRA ticket. The status is computed against the trailing 7-day rolling mean as well as the latest point, so a single noisy day does not page anyone, but a sustained 4-day drift does.
Dedup Gate (Static Data) reads $getWorkflowStaticData('global') for an alerted_<domain>_<metric>_<bucket> key. If the same domain crossed the same threshold within the last 12 hours, the gate halts the branch silently — exactly the behavior needed for an alert flow that polls hourly but where the underlying signal does not change that fast. Static data persists only on production executions, never on manual Execute Workflow runs, which is why the verification in _README.md uses the live schedule trigger rather than the manual-run button.
Claude — Remediation Draft posts to the Anthropic API with claude-haiku-4-5, an 8-second timeout, and a system prompt that maps the tripped metric to a specific corrective action: spam rate over 0.1% on a primary domain returns “pause the highest-volume sequence on <domain> for 24 hours and audit the last 200 sent messages for complaint patterns”; a blocklist listing returns “open a delisting request at <blocklist URL> and record why this IP was sending volume above its warmed-in cap”; bounce rate over 5% returns “run a list-scrub pass with <provider> on the last 30 days of imports before re-enabling sequences.” The draft is labeled “edit before action” in the Slack message — the rep confirms the action fits the situation; the flow does not auto-execute.
Slack — Notify posts to the channel matched to the alert’s severity (#deliverability-primary, #deliverability-warmup, #deliverability-secondary) with a Block Kit message: header with severity color, the offending metric and value vs. threshold, the rolling-mean comparison, and the Claude remediation draft. Critical-severity alerts also @-mention the on-call handle from the domain register so the right person is paged without polling the channel.
Schedule — DMARC Poll runs every 15 minutes and queries the IMAP mailbox for new messages with attachments matching *.xml, *.zip, or *.gz. Parse DMARC XML unzips and walks each report, extracts per-domain <source_ip> / <count> / <disposition> / <dkim> / <spf> triples, and pushes a structured record into the same threshold check path. The DMARC poll is the only branch that can catch a forged-sender problem from outside your sending platforms — it picks up spoofing attempts that show up nowhere else in the metric stack.
Cost reality
Per check, claude-haiku-4-5 runs only on threshold trips — the median day produces zero LLM calls. On a bad week, the flow might fire 5–10 alerts at roughly 500 input and 120 output tokens each, costing under $0.005 per alert at Haiku 4.5 pricing (~$0.80/M input, ~$4/M output). Monthly Claude cost stays under $1 for a typical 5-domain register.
Postmaster Tools API is free with a Google Workspace account; quota is well above hourly polling for a few dozen domains. Smartlead’s API is included with their $94/month base plan as of 2026-05; Instantly’s API is included with the Growth plan at $97/month. DNSBL queries to public lists (Spamhaus, Barracuda, SORBS, SpamCop) are free for non-commercial query volume; high-volume use requires a paid data feed at $1,500–$3,000/year per zone, which is out of scope at the volume this flow generates.
n8n self-hosted is free; n8n Cloud Starter at $20/month handles the 700–1,500 monthly executions a 5-domain register produces. The Slack bot, IMAP mailbox, and DNS lookups add no incremental cost. Total deliverability-monitoring spend over the baseline ESP cost: under $25/month.
Failure modes
Postmaster Tools data lag. Google’s Postmaster API publishes the previous day’s data in batches throughout the next day — the data point for 2026-05-25 may not appear until late on 2026-05-26 (UTC). A naive “latest point” comparison will alarm on a domain that simply has no recent data yet. Guard: Threshold Check (Code) requires at least two data points within the trailing 72 hours before flagging critical, and falls back to alert (not critical) when only one point is present. The threshold logic is commented inline and exposed as POSTMASTER_MIN_POINTS_FOR_CRITICAL so the on-call can tune it without a code change.
DMARC reports arrive in formats not every parser handles. Some mailbox providers (notably Microsoft 365 with certain anti-malware rules) strip .zip attachments or rewrite .gz to .gz_renamed. The IMAP poll will see the message but Parse DMARC XML will skip it and the failure will be silent. Guard: every IMAP message is logged with attachmentMatched: true/false, and the daily count is reported in a #deliverability-ops digest. If false exceeds 10% over a week, an escalation alert fires and the mailbox provider configuration is the suspect. _README.md documents the Microsoft 365 setting (Anti-Malware → Common Attachment Filter) that most often causes this.
DNSBL false positives during a delisting cycle. Some blocklists (notably Spamhaus DROP) cache aggressively at recursive resolvers. A domain that was delisted hours ago can still resolve as listed against a slow caching resolver. Guard: the DNSBL probe queries authoritative resolvers (8.8.8.8, 1.1.1.1) explicitly rather than the n8n host’s default resolver, and a critical alert requires the same listing to be confirmed against at least two of three resolvers in DNSBL_RESOLVERS. A single-resolver hit downgrades the severity to alert so the on-call investigates without paging.
Smartlead complaint-rate field changes. Smartlead’s /api/v1/campaign-statistics response shape has changed twice in the last 18 months — the complaint_rate field was renamed from spam_complaints in 2025-Q2. If Smartlead changes it again, the threshold check will read null and quietly pass every domain through ok. Guard: Merge — Per-Domain Snapshot rejects any record whose key metric field is null and routes the rejection to the #deliverability-ops log channel so a schema drift surfaces the same day, not the day after suppression.
vs alternatives
vs Google Postmaster Tools UI directly. Postmaster’s web UI is the source of truth for Gmail-side metrics, and for a single-domain operation it is enough — the UI shows spam rate, IP reputation, domain reputation, and delivery errors in one view. It does not, however, correlate with Microsoft, Yahoo, or your ESP’s complaint data, and it does not page anyone — a human has to remember to check it. This flow uses the same Postmaster API the UI uses and adds the multi-source correlation plus the alert channel. If your only sending platform is one Gmail-anchored domain, Postmaster UI plus a weekly calendar reminder is the lower-cost answer.
vs Mailgun, SendGrid, or Smartlead’s native deliverability dashboards. Each major sending platform ships its own deliverability view — Mailgun’s Deliverability Dashboard, SendGrid’s Reputation panel, Smartlead’s Master Inbox health metrics. Those are the most accurate source for that platform’s own send, but they are scoped to that platform. If you send across two or three platforms, the native dashboards force the RevOps lead to log into each one separately and compare apples-to-oranges scales. This flow’s load-bearing job is the cross-platform normalization. Use the native dashboards for the deep dive once the alert has named the affected platform.
vs paid third-party monitors like 250ok / Validity / GlockApps. These services run seed-list tests against the major mailbox providers and can detect placement drift earlier and at higher fidelity than Postmaster Tools alone, at $400–$2,500/month per domain depending on coverage. They are the right tier for a deliverability program at the $50M+ ARR scale where one Gmail folder switch is a measurable revenue hit. They are overkill for the under-$10M ARR teams this flow targets — the seed-list signal arrives at the same daily resolution as Postmaster Tools, and the threshold-and-alert layer this flow provides is what is actually missing at the smaller end. If your seed-list deliverability is already paid and monitored, keep that and skip this flow.
Pairs naturally with intent-spike-handler-n8n for the inbound side of the same RevOps n8n stack.
# Email deliverability monitor — n8n flow
This bundle contains a complete n8n workflow that polls Google Postmaster Tools, parses DMARC aggregate reports from a shared IMAP mailbox, runs DNSBL lookups across the major public blocklists, and pulls bounce / complaint metrics from Smartlead and Instantly — then alerts the assigned RevOps owner in Slack the moment any watched domain crosses a documented threshold, with a Claude-drafted remediation step attached.
Three entry points:
- **Hourly Sweep** — `Schedule — Hourly Sweep` fires at the top of every hour and runs the Postmaster, ESP, and DNSBL branches in parallel against every domain in the register.
- **DMARC Poll** — `Schedule — DMARC Poll` runs every 15 minutes and checks the IMAP mailbox for new DMARC aggregate reports.
- **Ad-hoc check** — `Webhook — Ad-hoc Domain Check` accepts `POST /webhook/deliverability-check` for one-off checks against a single domain.
## What this flow does
The hourly sweep loads the static domain register, fans out a Split In Batches (size 3 to stay under the per-second limits on Postmaster Tools and the public DNSBL zones), and runs four parallel polls per domain: Postmaster Tools `trafficStats` for the last 7 days, Smartlead `campaign-statistics` if the domain's `sendingPlatform` is `smartlead`, Instantly `accounts/health` if it's `instantly`, and a DNSBL probe that resolves the MX → IP → queries each configured blocklist zone via two of three resolvers.
The branches converge in `Merge — Per-Domain Snapshot`, which collapses each upstream's shape into one record per `(domain, sourceMetric, dateBucket)` and rejects records older than 26 hours. `Threshold Check (Code)` then applies per-metric alert and critical thresholds against the latest point AND a trailing 7-day rolling mean of the prior points. The thresholds default to the Gmail/Yahoo bulk-sender envelope (spam rate alert at 0.1%, critical at 0.3%) and are overridable via env vars.
`Dedup Gate (Static Data)` reads `$getWorkflowStaticData('global')` for a 12-hour-bucketed `alerted_<domain>_<metric>_<status>` key. If the same alert fired in the last 12 hours, the branch halts silently. Otherwise the gate stamps the key and continues. Static data persists only on production executions — never on manual Execute Workflow runs — which is why the verification below uses live triggers, not the manual-run button.
`Claude — Remediation Draft` posts to the Anthropic API with `claude-haiku-4-5`, an 8-second timeout, and a system prompt that asks for a structured JSON remediation with three fields: `action`, `why`, and `runbookUrl`. The system prompt names what the reader can actually do — pause a sequence, open a delisting request, run a list scrub — so the draft is runnable, not advisory. `Parse Remediation` falls back to a deterministic per-metric template when the LLM call times out or the response fails to parse, and tags `draftSource` so the rep sees whether they got `claude-haiku-4-5` or `template-fallback`.
`Slack — Notify` posts to one of three channels based on alert status and domain severity, using a Block Kit message with a header (severity color), a fields grid (metric, value, alert/critical thresholds, 7-day mean, severity), the remediation as a section block, and a context block @-mentioning the domain owner.
The DMARC branch is independent: `IMAP — DMARC Mailbox` reads new messages matching the `Report Domain` subject pattern, `Parse DMARC XML` unzips `.gz` / `.zip` attachments and walks the XML, and the resulting per-record rows feed into the same `Merge — Per-Domain Snapshot` node. DMARC records with both SPF and DKIM evaluating to `fail` are tagged `spoofingSuspect: true` — the only branch in the flow that can catch a forged-sender attack from outside your sending platforms.
## Import
1. In n8n, open **Workflows → Import from File** and select `email-deliverability-monitor-n8n.json`.
2. Open the workflow's **Settings** and confirm `Execution Order` is `v1` and `Timezone` matches your business hours (defaults to `America/New_York`). The cron expression interprets its schedule in this zone.
3. Edit the `Domain Register (Static)` Code node to your real domains. The default array contains three placeholder entries (`outbound.example.com`, `warm.example.io`, `news.example.com`); replace them.
4. Set the environment variables listed below.
5. Wire all five credentials listed in the Credentials section.
6. Run the five-step verification before activating either Schedule Trigger.
## Domain Register
The watched domains live in the `Domain Register (Static)` Code node — not in env vars, not in a database. The list is short, and putting it in the workflow keeps the version history in n8n's workflow versions alongside the threshold logic.
Each entry:
| Field | Values | Purpose |
|---|---|---|
| `domain` | the FQDN you send from (e.g. `outbound.example.com`) | Used as the API parameter for Postmaster, Smartlead, Instantly; used for MX lookup in DNSBL probe |
| `sendingPlatform` | `smartlead` / `instantly` / `outreach` / `gmail-direct` | Routes which ESP API gets polled (other branches no-op) |
| `owner` | email | Recorded in alert context |
| `slackHandle` | Slack handle without `@` | @-mentioned in the alert |
| `severity` | `primary` / `warmup` / `secondary` | Drives channel routing and on-call paging |
## Environment variables
Set these in your n8n instance's environment (n8n Cloud: **Settings → Environment Variables**; self-hosted: your `.env` file or container environment):
| Variable | Where to find it | Example |
|---|---|---|
| `DNSBL_ZONES` | Comma-separated list of blocklist zones to query. Defaults to Spamhaus, Barracuda, SpamCop. | `zen.spamhaus.org,b.barracudacentral.org,bl.spamcop.net` |
| `DNSBL_RESOLVERS` | Comma-separated resolver IPs. The probe queries 2-of-3 by default. | `8.8.8.8,1.1.1.1,9.9.9.9` |
| `SPAM_RATE_ALERT_THRESHOLD` | Decimal — value at which the alert fires. Default 0.001 (0.1%). | `0.001` |
| `SPAM_RATE_CRITICAL_THRESHOLD` | Decimal — value at which the critical fires. Default 0.003 (0.3%, the Gmail/Yahoo bulk-sender ceiling). | `0.003` |
| `BOUNCE_RATE_ALERT_THRESHOLD` | Decimal — alert. Default 0.05. | `0.05` |
| `BOUNCE_RATE_CRITICAL_THRESHOLD` | Decimal — critical. Default 0.10. | `0.10` |
| `COMPLAINT_RATE_ALERT_THRESHOLD` | Decimal — alert. Default 0.0008 (0.08%). | `0.0008` |
| `COMPLAINT_RATE_CRITICAL_THRESHOLD` | Decimal — critical. Default 0.003. | `0.003` |
| `POSTMASTER_MIN_POINTS_FOR_CRITICAL` | Integer — minimum trailing-72h points required before a Postmaster critical fires. Downgrades to alert when fewer points are available. | `2` |
| `SLACK_CHANNEL_CRITICAL` | Channel name (with `#`) for critical alerts. | `#deliverability-primary` |
| `SLACK_CHANNEL_WARMUP` | Channel for warmup-severity alerts. | `#deliverability-warmup` |
| `SLACK_CHANNEL_SECONDARY` | Channel for secondary-severity alerts. | `#deliverability-secondary` |
The DNSBL public lists allow non-commercial query volume free of charge. If your domain register grows past roughly 30 watched domains, switch to a paid data feed for the Spamhaus zone you query most often — public-resolver rate limits will start to drop queries silently.
## Credentials
### `PLACEHOLDER_POSTMASTER_CRED_ID` — Google Postmaster OAuth
Used by `HTTP — Postmaster Tools`. Postmaster Tools requires a Google Workspace account with the domain already verified in `https://postmaster.google.com`. Create an OAuth 2.0 client in Google Cloud Console with the `https://www.googleapis.com/auth/postmaster.readonly` scope, add it as a **Google OAuth2 API** credential in n8n, and authorize against the same account that owns the Postmaster Tools verification.
### `PLACEHOLDER_IMAP_CRED_ID` — DMARC RUA Mailbox
Used by `IMAP — DMARC Mailbox`. Create a dedicated mailbox (`dmarc-reports@yourcompany.com` is the convention) and point every DMARC RUA record at it. In n8n, add an **IMAP** credential with the mailbox host, port (993 for IMAPS), username, and an app password if your provider requires one. Google Workspace and Microsoft 365 both require app passwords or service-account delegation rather than the primary account password.
### `PLACEHOLDER_SMARTLEAD_CRED_ID` — Smartlead API key
Used by `HTTP — Smartlead Stats`. Generate an API key in Smartlead under **Settings → API Keys**. In n8n, add an **HTTP Header Auth** credential with header name `Authorization` and value `Bearer <your_key>`. Smartlead's API rate-limit is 1 request/second per key as of 2026-05; the Split In Batches batch size of 3 with the default Schedule Trigger interval of 1 hour stays well under this.
### `PLACEHOLDER_INSTANTLY_CRED_ID` — Instantly API key
Used by `HTTP — Instantly Health`. Generate an API key in Instantly under **Settings → Integrations → API Keys**. In n8n, add an **HTTP Header Auth** credential with header name `Authorization` and value `Bearer <your_key>`. Instantly's `/api/v2` endpoints require an account on the Growth plan or above.
### `PLACEHOLDER_SLACK_CRED_ID` — Slack bot token
Used by `Slack — Notify`. Create a Slack app at `https://api.slack.com/apps`, add the `chat:write` bot scope, install it to your workspace, and invite the bot user to each of the three deliverability channels. In n8n, add an **HTTP Header Auth** credential with header name `Authorization` and value `Bearer xoxb-…`. Create the channels (`#deliverability-primary`, `#deliverability-warmup`, `#deliverability-secondary`, `#deliverability-ops`) before activating; the channel names are env-overridable but must exist.
### `PLACEHOLDER_ANTHROPIC_CRED_ID` — Anthropic API key
Used by `Claude — Remediation Draft`. Generate an API key at `https://console.anthropic.com`. In n8n, add an **HTTP Header Auth** credential with header name `x-api-key` and value set to the key. The node uses `claude-haiku-4-5` to keep the call under 2 seconds at typical payload size. Cost is roughly $0.005 per alert on the median payload (~500 input + 120 output tokens).
## DMARC mailbox setup
For each watched domain, the DNS DMARC record should look like:
```
_dmarc.outbound.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourcompany.com; ruf=mailto:dmarc-reports@yourcompany.com; fo=1; adkim=r; aspf=r; pct=100"
```
Use `p=quarantine` or `p=reject` once you have established baseline reports show all legitimate sources passing. Starting on `p=none` will produce reports but no quarantining — the alert flow works either way; the policy choice is unrelated.
Most major mailbox providers deliver DMARC XML attachments without rewriting. Three known sources of silent attachment loss:
1. **Microsoft 365 with the default Anti-Malware Policy** — the Common Attachment Filter strips `.zip` from inbound mail. Setting: **Microsoft 365 Defender → Email & collaboration → Policies & rules → Anti-malware → Default policy → Protection settings → Common Attachment Filter → File types**. Remove `zip` from the blocked list, or create a transport rule that exempts messages from `dmarc-reports@yourcompany.com`.
2. **Gmail with strict attachment scanning** — `.gz` files are sometimes flagged. Gmail does not strip them but may quarantine the message. If reports stop arriving from a specific reporter, check the Quarantine in Google Workspace admin.
3. **Provider-side rewriting** — a few providers rename `.gz` to `.gz_renamed`. The parser will skip these and log `attachmentMatched: false`. Add a filename rule at the mailbox level to revert the name.
## Verification
Run all five before activating either Schedule Trigger. The flow should NOT be Active during steps 1-4.
### 1. Manual webhook hit against a known domain
POST a single-domain check to the webhook endpoint:
```bash
curl -X POST "https://<your-n8n-host>/webhook/deliverability-check" \
-H "Content-Type: application/json" \
-d '{"domain": "outbound.example.com"}'
```
Expected: 202 response, and within ~30 seconds you see the Slack message in `#deliverability-primary` IF the domain has any threshold trip. If not, the execution log in n8n should show all four branches running with `status: ok` outputs from `Threshold Check (Code)`.
### 2. Forced threshold trip
Temporarily set `SPAM_RATE_ALERT_THRESHOLD=0` and `SPAM_RATE_CRITICAL_THRESHOLD=0`, then re-run the manual webhook. Every Postmaster point will trip. You should see one Slack alert per domain — NOT multiple, because the dedup gate within a 12-hour window collapses them. Reset the env vars afterward.
### 3. Stale-report test
Edit `Merge — Per-Domain Snapshot` temporarily to set the `MAX_AGE_MS` constant to `0`. Re-run. Every Postmaster record should be rejected (collected-at older than 0ms), and you should see the resulting `status: ok` (no data → no alert) rather than spurious alerts on missing data. Revert the constant.
### 4. DNSBL false-positive test
In `DNSBL Probe (Code)`, hardcode a known-clean IP (e.g. `8.8.8.8`) in place of the MX lookup result and re-run. The probe should return `dnsblStatus: ok` with an empty `listings` array. Then hardcode a known-listed test IP from `127.0.0.2` (the standard Spamhaus self-test address — listed on `zen.spamhaus.org` by design) and confirm `dnsblStatus: critical` with the listing recorded. Revert.
### 5. Multi-domain burst test
Add five extra domains to `Domain Register (Static)` temporarily and run the manual webhook. Confirm the Split In Batches handles them without rate-limit errors from Postmaster Tools. The execution log should show the second batch starting ~1 second after the first. Revert.
After verifying all five, set both Schedule Triggers to Active and confirm the next top-of-hour execution runs cleanly in production mode (the dedup gate's static data only persists on production runs).
## Known limits
1. **Single-file zip assumption.** `Parse DMARC XML` uses a minimal zip reader that assumes one file per archive (the DMARC norm). Multi-file zips will fail to parse. If you see `dmarc-parse-error` rows with `error: 'not a zip'` for `.zip` attachments, replace the unwrap function with a real zip library (`adm-zip` is available in most n8n environments).
2. **Postmaster Tools data lag.** Google publishes the previous day's data in batches throughout the next day. The `POSTMASTER_MIN_POINTS_FOR_CRITICAL` env var downgrades single-point days from critical to alert; do not lower it below 2 without accepting noisier paging.
3. **DNSBL public-resolver rate limits.** Spamhaus, Barracuda, and SpamCop all rate-limit queries from common public resolvers. At a register size of 5-10 domains with hourly polling this is well within limits; past ~30 domains, switch the high-volume zone to a paid data feed.
4. **No automatic delisting requests.** The flow surfaces the delisting URL in the Claude remediation draft. Filing the request still requires a human; most blocklists require account creation and a reason-for-listing explanation.
5. **Not runtime-tested in this repo.** The bundle is a complete export and has been hand-walked node by node, but it has not been imported into a live n8n instance against a production Google Workspace and Smartlead account. Treat the verification above as a real first run, not a smoke test.