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.

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.

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.

  1. 01Strip HTML to text and drop tracking pixels
  2. 02Unwrap gateway links (Proofpoint v2 and v3, Microsoft Safe Links, Google redirects)
  3. 03Remove forward chrome: Forwarded message markers, From/Sent/To/Subject blocks, "wrote:" lines, quote markers, device signatures
  4. 04Remove our own footer block
  5. 05Cut at the signature delimiter
  6. 06Replace every URL with its bare registered domain
  7. 07Collapse whitespace and lowercase
  8. 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:

  1. Known fraudfirst, when an indicator is on your fraud list or the fingerprint matches a lure your analyst confirmed.
  2. 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.
  3. 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.
  4. Verifiedwith no code at all, when the nearest registration for that recipient inside the window is within the fingerprint threshold.
  5. 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.

  1. 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.

  2. 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.

  3. 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.