Concepts
Fromenance is a communication provenance platform: it keeps a registry of what an institution sent and answers whether a message a customer holds is in it. This page defines every term the rest of the documentation uses.
By the end you will be able to read a verdict, its rule, and its signals in the admin app and know exactly why the answer came out the way it did.
Communication provenance
Section titled “Communication provenance”Communication provenance is the ability to state, from a record made at send time, whether a specific communication was sent to a specific recipient. SPF, DKIM, and DMARC authenticate a domain, not a message; a lookalike domain passes all three. Provenance answers the customer’s actual question: did my bank send this to me?
Registration
Section titled “Registration”A registration is one row in the registry: one message to one recipient. It carries a channel (email in v1), the recipient hash, the message id, the visible From address, the send time, optional subject and content fingerprints, link domains, your own template and campaign ids, the provider, and an expiry. It never carries the recipient address or the body.
Three paths create registrations: the API (ideally through @fromenance/sdk), ESP webhooks through an adapter, and a journal or BCC copy sent to journal-<slug>@ingest.fromenance.com. See Registering communications.
Registrations expire at the tenant retention setting (90 days by default) or at the expires_at you pass. Ids are prefixed ULIDs: com_ for communications, sub_ for submissions, ver_ for verdicts, src_ for sources, dom_ for domains, whe_ and whd_ for webhook endpoints and deliveries.
Verify code
Section titled “Verify code”Every registration is issued a verify code: 8 Crockford base32 symbols shown as XXXX-XXXX, for example KX73-PQ9G. The eighth symbol is a checksum (the sum of the first seven symbol values modulo 32). Extraction only accepts codes whose checksum validates, which keeps account numbers, order ids, and capitalised words from producing false matches. Lowercase, spaces, missing hyphens, and the Crockford aliases (O as 0, I and L as 1) are all read correctly, including from OCR.
The code is bound to the recipient. A real footer pasted into a different message, or a real message forwarded by someone it was not sent to, does not verify.
Content fingerprint
Section titled “Content fingerprint”The fingerprint is a 64 bit SimHash over word 3-gram shingles of the normalized visible text, plus a SHA-256 of the same text. Normalization strips HTML to text, unwraps gateway rewritten links (Proofpoint, Microsoft Defender, Google redirects), removes forward chrome (forwarded message markers, header blocks, “wrote:” lines, quote markers, device signatures), removes the Fromenance footer itself, cuts at a signature delimiter, replaces every URL with its registered domain, collapses whitespace, and lowercases. The same code runs in the SDK at send time and in the Worker at verify time, so both sides compute identical bits in Node, Bun, and Cloudflare Workers.
Two fingerprints match when their Hamming distance is 6 or less (match_threshold on the tenant, 0 to 20). A Gmail, Outlook, Apple Mail, or Proofpoint rewritten forward of the same message lands within that distance; an unrelated lure lands far away. The normalized text is never stored; only the hashes are.
Recipient hash
Section titled “Recipient hash”recipient_hash is hmac-sha256:<64 hex>: HMAC-SHA256 of the lowercased, trimmed address (display name and angle brackets removed) keyed with a secret issued per tenant. The SDK computes it locally; the API rejects anything that is not already a hash. When a customer forwards a message, Fromenance hashes their address with the same secret and compares. The registry never holds a raw recipient address; the column does not exist.
Submission
Section titled “Submission”A submission is one customer question: a forward to verify@ (entry forward) or a paste, .eml, screenshot, or typed code on the verify page (entry web). It records the code found, the recipient hash found, the fingerprint, whether original headers were present, DKIM, ARC, and trust results, and the extracted indicators. The same customer submitting the same message again increments count on the existing submission instead of creating a second one, and gets the same reply again.
Verdicts
Section titled “Verdicts”Every submission ends in one of three outcomes, with the exact rule that produced it.
| Outcome | Customer reads | Rule values |
|---|---|---|
| Verified | “matches a communication we registered and sent to you on <sent_at>” |
code+recipient, code+recipient+content, recipient+fingerprint |
| Not verified | “no registered communication matches this message” | no_match, code:replay, code:recipient_mismatch, code+recipient:content_mismatch, no_match:authoritative |
| Known fraud | “matches an impersonation attempt our fraud team has confirmed” | fraud_list:indicator, fraud_list:fingerprint |
The verdict block is locked in every reply template. It states only whether a registered communication matches, never vouches for the message beyond that, never quotes the suspicious message, and never repeats its links or its sender.
Matching rules
Section titled “Matching rules”The verdict function is pure: no I/O, no model, no randomness. It evaluates in this order.
- Known fraud. Any extracted indicator is on the tenant fraud list (
fraud_list:indicator), or the fingerprint matches a submission an analyst marked as fraud (fraud_list:fingerprint). - Code and recipient match. The code resolves to a registration whose recipient hash equals the submitter’s hash (or the hash of the innermost To header). Verified by
code+recipientwhen no fingerprint is available on one side,code+recipient+contentwhen both fingerprints are close. When both fingerprints exist and are far apart, the customer gets Not verified bycode+recipient:content_mismatchand the submission is flagged as a replay: a real footer was pasted into a different message and sent to the same person. - Code resolves, recipient does not. Not verified. Flagged as a replay (
code:replay) when the fingerprint is far or unavailable. A close fingerprint with the wrong recipient iscode:recipient_mismatch: a real message forwarded from a mailbox it was not sent to, not an attack. - No code. The nearest registration for the submitter within the registration window (90 days) and within the threshold is Verified by
recipient+fingerprint. Confidence degrades with distance. - Nothing matched. Not verified by
no_match, orno_match:authoritativewhen the tenant is in Authoritative Mode.
Each verdict carries signals: code_found, recipient_match, fingerprint_distance, headers_present, dkim.
Footer replay
Section titled “Footer replay”A footer replay is a valid code on the wrong message or from the wrong recipient. Attackers copy real footers to make lures look legitimate. Fromenance reports it to the customer as Not verified, sets replay: true on the verdict, sends the submission.replay_detected webhook event, and queues the indicators with high priority. Codes rotate per message, so a copied footer never verifies for anyone but its original recipient.
Authoritative Mode
Section titled “Authoritative Mode”By default Not verified means “no record”, because a coverage gap on your side must not read as fraud to your customer. Authoritative Mode changes the locked text to “we did not send this message” and the rule to no_match:authoritative. It is a per tenant flag that only a Fromenance operator can enable, after your coverage has been reviewed: the proposal is 95 percent coverage for 30 days. It is shown read only in Settings.
Coverage and sources
Section titled “Coverage and sources”Coverage is registered volume against the volume you state you send, per source per day. A sending source is one integration: the API, an ESP webhook adapter, or the journal address. Each source tracks last event, events per hour, signature failures, reservations against completions, and coverage gaps, and raises a silence alert when no event arrives within its configured expectation. Gaps are visible before a customer finds one.
Reserve flow
Section titled “Reserve flow”ESPs that cannot hand over the rendered body need the code before the send exists. POST /v1/communications/reserve issues a code bound to a recipient; you place it through the ESP’s merge field; the ESP’s send event completes the registration with the message id and send time. A reservation not completed within 24 hours (configurable 1 to 72) expires and counts as a coverage gap.
Trust model
Section titled “Trust model”A forward to <slug>@verify.fromenance.com is processed only when it authenticates for a domain you own: a DKIM signature that verifies for the domain, or an ARC chain whose last seal is by it, and that domain has a verified _fromenance.<domain> TXT record with your token and the trust role. Everything else is held for 24 hours and never answered. Details in Domains and mail.
Indicators and the fraud list
Section titled “Indicators and the fraud list”Indicators are URLs, domains, email addresses, phone numbers, and IPs extracted from every submission (low priority on Verified ones, so nothing is lost). The fraud list is the tenant’s block list. Entries come only from analyst action; nothing is added automatically from a single submission, because anyone can forward anything through your rule.
Campaigns
Section titled “Campaigns”Campaigns cluster submissions that share indicators, fingerprints within distance 10, or a phishing kit fingerprint. They are a Phase 2 feature of the intelligence tier. The campaigns table and the campaign_id on submissions exist in v1 so nothing has to be backfilled, and the admin app shows a placeholder.