How it works
A registry, a trust check, and a verdict function you can read
This is the page for the person who wants to know exactly how Fromenance does communication provenance before they let it near customer mail. Short version: we match what a customer sends us against a registry of what you actually sent, using a checksummed verify code, a hash of the recipient's address, and a fingerprint of the text, and there is no model anywhere on the path. The answer is a lookup, not a probability, which is why your compliance team can stand behind it.
Trust model
Before we read a single forwarded message
We first establish that it came through your infrastructure. Four things make that true.
One TXT record anchors your tenant
_fromenance.yourdomain.com TXT "v=fromenance1; t=<slug>; k=<token>". We poll DNS every 60 seconds until it resolves, re-check it daily, and pause inbound for that domain if the record disappears. That is your whole DNS footprint.
The forwarding hop has to authenticate
We process mail to <slug>@verify.fromenance.com only when it carries a DKIM signature that verifies for a domain you own, or an ARC chain whose last seal is by such a domain, and that domain's TXT token matches. We run DKIM, ARC, and SPF ourselves, inside the Worker, on the raw MIME.
Everything else is held, not answered
A message that fails any part of the trust check is kept for 24 hours as rejected inbound, where our operators can see why (usually an onboarding mistake). It is never processed, never replied to, and then deleted.
Redirect, not forward
The rule on verify@yourdomain.com redirects to your tenant inbox, so the customer stays the sender and the original headers survive. We have guides for Google Workspace, Microsoft 365, Proofpoint, Mimecast, Cisco Secure Email, and the setup wizard sends a test and refuses to go live until the rule is right.
Registration
What a registration carries
Your application computes everything sensitive on your side, with the normalization code we publish. We never receive a raw recipient address or a message body.
POST /v1/communications
{
"channel": "email",
"recipient_hash": "hmac-sha256:9f2c...",
"message_id": "<c1b2@jtmerlin.com>",
"from_address": "alerts@jtmerlin.com",
"sent_at": "2026-09-24T14:42:10Z",
"content_fingerprint": { "simhash": "0x9a4d...", "sha256": "e3b0..." },
"link_domains": ["jtmerlin.com"],
"template_id": "fraud-alert-v3",
"provider": "api"
}
200 OK
{ "id": "com_01J...", "verify_code": "KX73-PQ9G", "expires_at": "2026-12-23T14:42:10Z" }What comes back is a verify code that belongs to this registration and this recipient. You put it in the footer. Codes are unique within your tenant, change with every message, and expire with your retention window, 90 days by default.
If your ESP cannot hand us the rendered body, you reserve the code before you send, drop it into the template with the ESP's merge field, and the ESP's send event completes the registration. A reservation that is never completed within 24 hours expires, and we report it to you as a coverage gap.
Every creating call takes an idempotency key we keep for 24 hours, so a retry never double registers. Rate limits are 600 registrations per minute per key and 6,000 batch items per minute.
Extraction
What we pull out, strongest signal first
Deterministic, in this order, on every submission from either entry point.
1. The verify code
Eight Crockford base32 symbols, shown as XXXX-XXXX. The eighth is a checksum, so the extractor only accepts codes that validate, even out of OCR text. A code that leads to a registration is the strongest signal we have.
2. The recipient
The customer's address from the redirect envelope, plus the innermost To header of the quoted message when it is there. We hash both with your tenant secret and compare them to the hash on the registration. The address itself never reaches us on the API path.
3. The content fingerprint
The quoted body goes through the same normalization you ran at registration and becomes a 64 bit SimHash. A match is Hamming distance 6 or less, a threshold you can tune per tenant and that we validate in the pilot.
4. Headers
Only there when the customer forwarded as an attachment or their client kept them. When they are, we verify DKIM for your sending domains against the original message.
5. Links and indicators
Every URL, domain, phone number, email address, and QR payload, with your own domains skipped. Stored on the submission, used now for Known fraud matching, clustered later.
Normalization
How both sides end up with the same fingerprint
You run this at registration and we run the same code at verification, so a forwarded copy lands next to the original. This is the pipeline.
- 01Strip HTML to text and drop tracking pixels
- 02Unwrap gateway links (Proofpoint v2 and v3, Microsoft Safe Links, Google redirects)
- 03Remove forward chrome: Forwarded message markers, From/Sent/To/Subject blocks, "wrote:" lines, quote markers, device signatures
- 04Remove our own footer block
- 05Cut at the signature delimiter
- 06Replace every URL with its bare registered domain
- 07Collapse whitespace and lowercase
- 08SimHash over word 3-gram shingles with FNV-1a 64, identical bits in Node, Bun, and Workers
The verdict
The verdict function
It is a pure function: no I/O, no model, no randomness. It implements the matching rules table and a test covers every row, including replay. In order:
- Known fraudfirst, when an indicator is on your fraud list or the fingerprint matches a lure your analyst confirmed.
- Verifiedwhen the code leads to a registration and the recipient matches, unless both fingerprints exist and are far apart, in which case the customer gets Not verified and we flag a replay.
- Not verifiedwhen the code is real but the recipient does not match. Flagged as a replay when the fingerprint is far or missing; a close fingerprint from the wrong mailbox is someone forwarding from a second account, not an attack.
- Verifiedwith no code at all, when the nearest registration for that recipient inside the window is within the fingerprint threshold.
- Not verifiedotherwise, worded as "we did not send this" only when you are in Authoritative Mode.
Pipeline
What happens to a forward, in three hops
One Hono API on Cloudflare Workers fronts everything. Queues keep the mail pipeline off the request path, so a burst of forwards never slows down registration. Neon Postgres is the system of record, D1 is the edge mirror, R2 holds the raw messages.
Step 1: Ingest
Resend hands us the raw MIME through a Svix signed webhook. We copy it to R2 under your tenant prefix, encrypted, and put it on the queue.
Step 2: Match
Parse, authenticate, extract, look the code up in the edge mirror, compare the recipient hash, compute the fingerprint, run the verdict function.
Step 3: Reply
Render your template for that verdict with the locked block and timestamp, send it from your reply identity, fire your webhooks, and queue the intelligence job.
FAQ
Questions we get on the first call
- Why does a registry beat a classifier?
- Because they answer different questions. A classifier estimates whether a message looks like phishing. A registry knows whether you sent this message to this person. The first is a probability that better writing can beat; the second is a lookup an attacker cannot insert themselves into.
- What survives a forward?
- The verify code almost always does, which is why it is our primary signal and why it appears twice in the footer. The recipient survives when the rule redirects rather than forwards. The body survives well enough that Gmail, Outlook, Apple Mail, and Proofpoint rewritten forwards of the same message land within Hamming distance 6 of the original. Headers survive only in attachment forwards.
- What if an attacker copies our verify footer?
- They will, and it helps you. The copied code belongs to one registration and one recipient. Pasted into a different message it fails the fingerprint; sent to a different person it fails the recipient. The customer gets Not verified and we flag a replay with high priority, which means the lure was delivered to your fraud team by its own target.
- Is there any machine learning in the verdict?
- No. Extraction and matching are regex, MIME parsing, a hash comparison, and a SimHash distance. The only model in the system reads screenshots, and we treat what it produces as untrusted text that feeds the same deterministic extractors.
- How fast is it?
- Our targets are under 10 seconds at p95 from inbound webhook to reply sent, under 3 seconds for a pasted message on the web page, and under 150 ms for a registration. Code lookups are served from a D1 edge mirror with Neon behind it.
- What does the customer see if we have not registered the message?
- Not verified, worded as "no registered communication matches this message. That does not by itself mean it is fraudulent, but treat it with caution." We never say a message is fraudulent unless one of your analysts confirmed it, and never say "we did not send this" unless you are in Authoritative Mode.
Start with one stream and a 60 to 90 day pilot.
Pick your fraud alerts or your transaction alerts, the messages your customers already squint at. At the end you get a written report: how many people asked, what they were told, which campaigns turned up, and what your existing feeds missed.