Audit a marketing email against the CAN-SPAM Act — before the FTC does.

Clone the repo, run one script, get a real pass/fail report against 16 CFR Part 316. Zero API key. Zero network calls. Zero third-party dependencies. Nothing here is a guess dressed up as a compliance check.

A manual pre-send check, not a watcher. Export the draft, run one command, get pass/fail in under 10 seconds. It doesn't sit on your inbox or a folder and check emails automatically — you run it, on the email you're about to send.

Every separate email in violation of the CAN-SPAM Act is subject to a civil penalty of up to $53,088 — the FTC's own 2025 inflation-adjusted figure, still current for 2026. Every email sent counts separately.

Two tracks, labeled by which code path fired — not narrated after the fact

Four of the Act's requirements are genuinely mechanical — presence/absence checks on raw email source. A fifth (the subject line) is mechanical for one narrow pattern and a judgment call everywhere else, and the auditor says so instead of guessing. Two more are named out of scope entirely, because a static single-email tool structurally cannot check them.

AUTOMATED

Header accuracy, ad disclosure, postal address, opt-out

Deterministic code, no model in the loop. A self-test pass reads every one of these functions' own source and fails if it finds a network or LLM call — the label is checked, not claimed.

AI-ASSISTED

Subject-line deception, outside one narrow case

One structural pattern derived straight from 16 CFR §316.3(a)(2) is checked automatically. Everything else about "is this subject deceptive" is a reasonable-recipient judgment — flagged honestly, never silently passed.

OUT OF SCOPE

10-day opt-out honoring, third-party monitoring

Both require observing what happens after the email is sent. A static single-email auditor can't do that — so it says so, every run, instead of pretending.

A real audit run, pasted verbatim

Two invented-business fixtures, not toy examples — full worked runs live in examples.md. This is a multi-violation fail: a Reply-To that resolves to a different domain than the From address, and a missing physical postal address.

$ python3 audit.py fixtures/example-glowup-multi-violation.eml

# CAN-SPAM audit — example-glowup-multi-violation.eml

**2 FAIL** / 2 PASS / 1 NEEDS-REVIEW / 2 out of scope

## [FAIL] Accurate header information — `AUTOMATED`
Reply-To domain (a-different-mailhost.example) does not match
From domain (glowupskincare.example) — a structural inconsistency
in the routing information 16 CFR §316.2(m) requires to be accurate.

## [FAIL] Valid physical postal address present — `AUTOMATED`
No street address, PO Box, or ZIP-code-shaped pattern found in the body.
Cites 16 CFR §316.2(p), verbatim text checked against reference/.

## [PASS] Message identified as an advertisement — `AUTOMATED`
## [PASS] Working, one-step, no-fee opt-out mechanism — `AUTOMATED`
## [SKIPPED] Opt-out honored within 10 business days — `OUT-OF-SCOPE`
## [SKIPPED] Monitoring third-party senders — `OUT-OF-SCOPE`

Watch it run

A short walkthrough of the checker in action.

▶ Watch on YouTube

In the wild

Tested against real, professionally-drafted marketing copy across multiple product lines — not synthetic-only. Zero parser errors on genuine long-form prose: em-dashes, embedded plain-text links, varied paragraph structure, P.S. lines.

Real testing surfaced one real accuracy gap — the ad-disclosure check didn't originally account for CAN-SPAM's own primary-purpose exemption — found and fixed the same day real testing began. See the commit history.

Caveat, stated plainly: these were drafted copy, not confirmed live sends, so the footer-dependent checks (postal address, opt-out) weren't meaningfully exercised — real footers are injected by the sending platform at send time, not present in a draft.

Two ways to use this

Same audit engine either way — the difference is whether code actually runs.

Run it yourself — about 2 minutes

Gets you the real, deterministic checks.

  1. Clone the repo: git clone https://github.com/NFTYoginis/can-spam-auditor
  2. Confirm you have Python 3.9+. Nothing else to install.
  3. Prove the checker works: python3 audit.py --selftest — or --judge-mode to run the 3 adversarial checks this build was actually attacked with, in under a second.
  4. Export a real email as raw source from your email client ("View Original" / "Show source" / "Download .eml").
  5. Run it: python3 audit.py your-email.eml

Drop the folder into a Claude Project

Gets you a specialist that explains findings in plain language.

  1. Download or clone the repo.
  2. Add the whole folder to a Claude Project (or paste identity.md / rules.md / examples.md / reference/ into its instructions).
  3. Paste in an email's raw source and ask it to audit CAN-SPAM compliance.
Only real if code execution is on. This specialist's rules require it to actually run audit.py before calling anything AUTOMATED — in a Project or chat context with no code-execution tool enabled, it can't, and it's instructed to say so rather than fake it. Without code execution you get honestly-labeled AI-ASSISTED impressions only, not the real automated checks. Enable code execution, or use the left-hand path, for the real thing.

FAQ

Does a clean report mean the email is CAN-SPAM compliant?

No. It means the four fully-automated structural checks passed, and the subject line didn't trip the one narrow deception pattern this tool can check mechanically. Two real requirements — opt-out honored within 10 business days, and monitoring a hired third-party sender — aren't checked at all, and can't be, by a static single-email tool. This isn't a legal compliance certification.

Does it check GDPR, CASL, or other email regulations?

No — one Act, one email at a time, on purpose. See the repo's rules.md for the exact refusal language.

Why does the reference material matter so much?

Because a finding that cites a provision you can't independently check is just an opinion with a citation-shaped label on it. reference/ carries the actual regulation and the actual FTC guide, verbatim — open a finding, open the cited section, check the words match. --selftest checks that mechanically too.

What does "AI-ASSISTED" mean, exactly?

It means the code couldn't decide and says so, instead of guessing. Subject-line deception is mostly a reasonable-recipient judgment call — one narrow, real structural pattern is checked automatically; everything else gets flagged for a human or a model to actually read.

Does this run automatically, or watch my inbox?

No. It's a manual check you run yourself, once, on one email you're about to send — export the draft, run one command, get pass/fail in under 10 seconds. It doesn't sit on a folder or an inbox and it doesn't run on a schedule. (The optional GitHub Action, listed under Go-beyond in the README, is the one opt-in exception — it runs the same offline checker as a PR check, and you wire that up yourself.)