Guide
How to prove the email you sent is the email that was approved
Every regulated email program has the same weak joint. The QA pass happens, the approval happens, and then the email keeps moving: a developer hotfixes a link, someone swaps a tracking pixel, the send platform rewraps URLs. When compliance later asks “is what went out what we approved?”, the honest answer at most shops is a screenshot, an email thread, and a shrug. Approval was an event, not a property of the file.
This guide covers how to bind an approval to the exact bytes of the email, how to capture the as-sent file honestly, and what an auditor wants to see.
What “approved” usually means
Ask where last month’s approval lives and you get pointed at a thread: a screenshot or a preview link, and a reply that says “good to go”. That proves a person agreed to something. It is weak evidence of what. A screenshot says nothing about the HTML, the link destinations or the alt text.
The email still has to be built, published and sent after approval, and it drifts in three ways:
- A last-minute edit in the ESP editor. The client sends one more change: a date in the legal line, a price. Someone makes it in the content editor so the send goes on time. It was never reviewed, and the approval now covers an email that no longer exists.
- A different template version published. The send picks up the wrong copy of the template: last round’s draft, or a duplicate made to test a fix. Everything looks plausible.
- A link or tracking change at send time. Tracking parameters are rewritten, or a link is pointed at a new landing page. The visible email is identical, so no screenshot shows it.
Bind the approval to the bytes
The fix is old cryptography applied to a new joint: at the moment of sign-off, compute a SHA-256 hash of each approved email’s exact HTML. The hash is a fingerprint of the bytes. Change one character anywhere, even inside an invisible attribute, and the fingerprint changes completely. Record the fingerprints with the approval and you’ve turned “we approved it” into something checkable forever.
shasum -a 256 launch-en.html
6ffaba7536c8f7f2406ca1e96d3ee66b9c0cd8a91db139c542dcd604a8342dd8 launch-en.html
To verify later, hash the file you’re holding and compare. Same fingerprint, same bytes, same email. Different fingerprint: something changed after approval, and no amount of “it looks the same” overrides that. Anyone can recompute it with tools that ship with the operating system.
What the hash covers, and what it does not
The hash covers everything written in the HTML: copy, inline styles, alt attributes, and every link and image URL with its parameters. Change utm_campaign on one button and the hash changes.
It does not cover what the HTML points to. Replace an image on the server under the same name, or add a redirect behind a link, and the hash is unchanged while subscribers see something else. So images and link destinations need their own checks. The email QA checklist covers both.
Why a screenshot cannot do this
Two emails with different links, alt text or hidden pre-headers produce the same screenshot. Nobody can recompute a screenshot or tie it to a file. A hash belongs to one file, and comparing two is a string match.
Capture the as-sent file honestly
The hard part is deciding which file counts as “the email”, because the approved template and the message in the inbox are not the same bytes.
To see what subscribers get, make a test send to a seed inbox, a mailbox the team controls, with the same send settings as the real deployment. Export the message source, not a screenshot: in Outlook on the web, View message source; in Gmail, Show original, then Download original for an .eml file. Never copy HTML out of the browser’s developer tools; that is the webmail client’s rewrite, not what was sent.
The source shows what the ESP added:
- An open-tracking pixel, often with a per-recipient id.
- Wrapped links that pass through a click-tracking domain before redirecting to the destination.
- A view-online link and personalization, resolved to real values.
- Unsubscribe headers such as List-Unsubscribe. They sit outside the HTML body, so no HTML hash covers them.
A comparison has to account for these. The received copy never matches the approved template, even when nothing went wrong, because the wrappers are bytes too; a whole .eml differs on every send because of its date and message id. Compare like with like: gate the send on the template bytes that went through QA, before personalization, then check the seed copy for what the ESP added, with every wrapped link landing on the approved destination and the unsubscribe headers present.
SendLint takes the seed copy as it is: drop the .eml or paste the message source, and it is unwrapped and its subject line read from the header. The link sheet follows every link to where it lands, with status, redirects and tracking parameters. The help center covers each input.
What an auditor asks for
Regulated teams, in financial services, insurance or pharma, get the same four questions, often months later:
- Who approved it? A named person, and whether a second person granted it.
- What exactly did they approve? Which emails, round and language, and what was waived.
- When? A timestamp, and whether it was still current on the send date.
- Can you prove the send matched? A check the auditor can run without taking anyone’s word.
A thread answers the first and third loosely, and the fourth not at all. A real approval record needs:
- The fingerprint (SHA-256) of every approved email in the round
- The round and language it covers, what was checked and what was waived
- Who requested and who approved, with timestamps
- A way for someone who wasn’t in the room to verify a file against it
- Revocation and supersession: if approval is withdrawn or replaced, the record must say so
How SendLint automates this
In SendLint, approval is earned, not clicked: a round can only be approved when it passes the client’s compliance policy (unresolved failures block, waivers are recorded, and an override needs a typed reason on permanent record). The approval then mints a certificate, with an id like SL-2026-9CQK-GE3P-EMB4, that freezes the SHA-256 of every approved email, the campaign and round, the check counts, the waivers, and who approved. Each language is signed off separately, with its own certificate. A two-party approval records who requested it and that a Reviewer granted it; a one-person approval is labeled self-approved. Beyond the QA team:
- A public verify page. At sendlint.com/verify, anyone can verify by certificate id, by an email’s SHA-256, or by choosing the .html file. On a certificate’s page they paste the final HTML for a byte-for-byte verdict. Hashing runs in the browser; nothing uploads. A difference in line endings alone (CRLF against LF) is reported as such.
- A clear status. The current approval shows VALID · CURRENT. A withdrawn one shows REVOKED · DO NOT SEND.
- Supersession, not deletion. A newer round’s approval replaces the old certificate and says so: the old page reads REPLACED BY A NEWER SIGN-OFF and names its successor. History stays inspectable.
- A pipeline gate. A send pipeline can hash the outgoing HTML and ask the API whether that exact file is on a standing certificate. Changed bytes, superseded approval, or a revoked round all come back as “don’t send” with the reason.
The compliance PDF carries the hash list and verify URL, so the signed paper binds to exact bytes. The auditor’s four questions get one link.
A worked example: Harbourline HL2316
Harbourline is a fictional retailer; HL2316 is its sample campaign, and the hashes here are illustrative. Round 2 of the English launch email is approved, and the certificate records:
Approved HTML · SHA-256
b7b4f525d9941d6707009f68de483e215acca0e669966016d5f0714a01f333a6
Two days later the client asks, in a separate email, to move the offer end date in the legal line by a day. The developer changes that one line in the ESP editor. Before scheduling, QA hashes the final template:
3d8ed4f24f70a8824813d31884025ccf086a82392ec2ee33f31b6ec112b30a1d hl2316-launch-en.html
One changed line, a different fingerprint. On the verify page, that hash returns “No certificate records this hash. Treat the email as unverified.” Pasting the HTML into the round 2 certificate’s own page returns “Does not match this certificate”. The pipeline gate would return not_found and stop the send.
The change may be correct, so the team updates the deck and runs the email as round 3. SendLint checks it against the approved assets again, and round 3 is approved. The round 2 certificate now reads REPLACED BY A NEWER SIGN-OFF and points to round 3, which shows VALID · CURRENT. Open a real Harbourline certificate from the verify page and walk the full sample campaign in the free demo.
The pipeline gate in one request
Authorization: Bearer <api key>
→ verified: true · status: valid # send it
→ verified: false · status: not_found # never approved, or edited after
→ verified: false · status: superseded # a newer approval replaced it
→ verified: false · status: revoked # approval was withdrawn
Wire that into the last step before the send (Salesforce Marketing Cloud can do it with an SSJS activity; any CI system can do it with two lines) and “the wrong version went out” stops being a category of incident. The SFMC pre-send verification guide walks through the Marketing Cloud setup step by step.
Frequently asked questions
- How can you prove a sent email matches what was approved?
- Compute a SHA-256 hash of each approved email's exact HTML at sign-off and record it with the approval. Later, hash the file you are holding and compare: the same fingerprint means the same bytes, and a different fingerprint means something changed after approval.
- What should an email approval record contain?
- The SHA-256 fingerprint of every approved email, the round and language it covers, what was checked and waived, who approved and when, a way for an outsider to verify a file, and any revocation or supersession.
- Does a SHA-256 hash of the HTML cover images and links?
- It covers the image and link URLs written in the HTML, not what they return. An image replaced on the server under the same name, or a link redirected after approval, leaves the hash unchanged, so images and link destinations need their own checks.
- Why does my test send never match the approved hash?
- The ESP adds bytes at send time: a tracking pixel, wrapped links, a resolved view-online link, and headers such as List-Unsubscribe. Gate the send on the template bytes you approved, and check the received copy for what the ESP added.
- What is a SendLint certificate?
- A record minted when a round passes the client's compliance policy and is approved. It freezes the SHA-256 of every approved email, the round, the language, the check counts, the waivers and who approved, and it backs a public verify page and an API pipeline gate.
- What happens to a certificate when a newer round is approved?
- The newer approval supersedes it. The old certificate is kept, and its verify page says it was replaced by a newer sign-off and names the successor.
Keep reading
- Pre-send verification in Salesforce Marketing CloudWhat to check in the test send (wrapped links, subject line per version, unsubscribe, dynamic content), then a Script Activity that hashes the approved HTML and stops the automation if a byte changed after sign-off.
- The email QA checklist: every check before you sendEvery check a bank-grade email QA pass covers, why each exists, and which ones humans consistently miss.
- A Litmus alternative for email QA: verify the email, not just preview itRendering previews and verification are different jobs. An honest side-by-side of which tool fits which one, with both price lists.