can-spam-auditorClone 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.
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.
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.
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.
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.
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`
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.
Same audit engine either way — the difference is whether code actually runs.
Gets you the real, deterministic checks.
git clone https://github.com/NFTYoginis/can-spam-auditorpython3 audit.py --selftest — or --judge-mode to run the 3 adversarial checks this build was actually attacked with, in under a second.python3 audit.py your-email.emlGets you a specialist that explains findings in plain language.
identity.md / rules.md / examples.md / reference/ into its instructions).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.
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.
No — one Act, one email at a time, on purpose. See the repo's rules.md for the exact refusal language.
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.
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.
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.)