Skip to content

Quickstart

Fromenance is a communication provenance platform: you register each outbound message at send time and customers can ask whether a message is real. This quickstart takes an empty tenant to a Verified reply in about 15 minutes.

At the end you will have an API key, a verified domain, a working redirect rule, one registered communication with its footer, and a reply in your inbox that says the message matches a communication you registered.

You need: an owner or admin login to app.fromenance.com, DNS access for your domain, admin access to your mail platform, and Node 18+ or Bun. Replace northfieldbank.example with your domain and northfield with your tenant slug throughout.

  1. Create an API key.

    In the admin app open Settings, API keys, and mint a key named quickstart with mode test and scope register. The secret is shown once. Test keys (fr_test_) write to a sandbox partition and never mail a real customer: sandbox replies go to the fraud contact address set in Settings, so set that to your own address now.

    You can also mint it from the API with an existing admin key:

    Terminal window
    curl -X POST https://api.fromenance.com/v1/api-keys \
    -H "Authorization: Bearer fr_live_..." \
    -H "Content-Type: application/json" \
    -d '{"name":"quickstart","mode":"test","scopes":["register"]}'

    Export the values the samples read:

    Terminal window
    export FROMENANCE_API_KEY=fr_test_...
    export FROMENANCE_TENANT_SECRET=... # the recipient hashing secret issued with your tenant
    export FROMENANCE_API_ORIGIN=https://api.fromenance.com
  2. Add your domain and publish the TXT record.

    Terminal window
    curl -X POST https://api.fromenance.com/v1/domains \
    -H "Authorization: Bearer $FROMENANCE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"domain":"northfieldbank.example"}'

    The response includes txt_record. Publish it at your DNS provider:

    _fromenance.northfieldbank.example. TXT "v=fromenance1; t=northfield; k=<32 hex characters>"

    Fromenance polls DNS every minute for 24 hours. Force a check and confirm status is verified:

    Terminal window
    curl -X POST https://api.fromenance.com/v1/domains/dom_.../check \
    -H "Authorization: Bearer $FROMENANCE_API_KEY"

    The domain gets the roles trust and sender by default. trust lets forwards that authenticate for it through; sender lets it appear as a registered From address.

  3. Create the redirect rule.

    Create the mailbox or group verify@northfieldbank.example and add a rule that redirects (not forwards) everything it receives to your tenant inbox, northfield@verify.fromenance.com. The address is shown as verify_address on GET /v1/tenant.

    Redirect keeps the customer as the sender, so Fromenance can reply to them, and preserves the DKIM signature your platform adds. A forward rewrites the sender to verify@ and the reply would come back to you.

    Click by click guides: Google Workspace, Microsoft 365, Proofpoint, Mimecast, Cisco Secure Email.

  4. Send the forwarding test.

    Terminal window
    curl -X POST https://api.fromenance.com/v1/domains/dom_.../forwarding-test \
    -H "Authorization: Bearer $FROMENANCE_API_KEY"

    Fromenance emails verify@northfieldbank.example with a token. When the redirect delivers it to your tenant inbox with a DKIM or ARC pass for your domain, the rule is marked Working. Poll until status is working:

    Terminal window
    curl https://api.fromenance.com/v1/domains/dom_.../forwarding-test \
    -H "Authorization: Bearer $FROMENANCE_API_KEY"

    A failed result names the rejection reason. The common one is auth_failed, which means the rule forwarded instead of redirecting.

  5. Register the first communication.

    Install the SDK in your project (bun add @fromenance/sdk or npm install @fromenance/sdk) and run this sample. register() hashes the recipient with your tenant secret and fingerprints the body locally; the address and the body never leave your process.

    samples/quickstart.ts
    /**
    * Quickstart: register one rendered email with @fromenance/sdk and render the footer.
    *
    * Environment:
    * FROMENANCE_API_KEY fr_test_... from Settings, API keys (scope: register)
    * FROMENANCE_TENANT_SECRET the recipient hashing secret issued with your tenant
    * FROMENANCE_API_ORIGIN optional, defaults to https://api.fromenance.com
    *
    * The recipient address and the body are hashed in this process. Only the HMAC and the fingerprint are sent.
    */
    import { Fromenance } from "@fromenance/sdk";
    export async function main(): Promise<{ id: string; verifyCode: string; footer: string }> {
    const apiKey = process.env.FROMENANCE_API_KEY;
    const tenantSecret = process.env.FROMENANCE_TENANT_SECRET;
    if (!apiKey || !tenantSecret) throw new Error("Set FROMENANCE_API_KEY and FROMENANCE_TENANT_SECRET");
    const fr = new Fromenance({
    apiKey,
    tenantSecret,
    baseUrl: process.env.FROMENANCE_API_ORIGIN ?? "https://api.fromenance.com",
    });
    // The email exactly as your sending system rendered it, before the footer is added.
    const messageId = `<${crypto.randomUUID()}@northfieldbank.example>`;
    const email = {
    to: "jane.doe@example.com",
    from: "alerts@northfieldbank.example",
    messageId,
    subject: "A new device signed in to your account",
    html: `<p>Hi Jane,</p>
    <p>A new device signed in to your Northfield Bank account on September 24 at 10:42 AM.</p>
    <p>If this was you, no action is needed. If not, open the app and lock your card.</p>`,
    templateId: "new-device-alert-v2",
    sentAt: new Date(),
    };
    // One registration per recipient. The Idempotency-Key defaults to the message id, so a retry is harmless.
    const registration = await fr.register(email);
    // Paste this footer into the message before it goes out. The code appears twice on purpose.
    const footer = fr.footer(registration.verify_code, {
    institutionName: "Northfield Bank",
    verifyAddress: "verify@northfieldbank.example",
    verifyPageUrl: "https://northfieldbank.example/verify",
    });
    console.log(`registered ${registration.id} with code ${registration.verify_code}`);
    console.log(footer.text);
    return { id: registration.id, verifyCode: registration.verify_code, footer: footer.text };
    }
    if (process.argv[1]?.endsWith("quickstart.ts")) {
    main().catch((err) => {
    console.error(err);
    process.exit(1);
    });
    }
    Terminal window
    bun run samples/quickstart.ts
    # or: npx tsx samples/quickstart.ts
  6. Paste the footer into the message and send it to yourself.

    The SDK’s footer() renders this; with curl, fill in the code by hand. The code appears twice on purpose: once in the sentence and once as a bare reference, so one survives client rewrapping and screenshot OCR.

    Not sure this email is from Northfield Bank? Forward it to verify@northfieldbank.example
    or enter code KX73-PQ9G at northfieldbank.example/verify. Reference: KX73-PQ9G

    Send the email, with the footer, from alerts@northfieldbank.example to the address you registered (jane.doe@example.com in the sample; use one you control).

  7. Forward it and read the reply.

    From the recipient mailbox, forward the message to verify@northfieldbank.example. Within seconds a reply arrives from your reply identity (northfield@reply.fromenance.com until you set up branded replies). Its locked verdict block reads:

    Verified: this message matches a communication we registered and sent to you on September 24, 2026 at 2:42 PM UTC.

    Because this is a sandbox key, the reply goes to your fraud contact address rather than to the forwarding mailbox. The submission also appears in the admin app under Submissions with rule code+recipient+content and the verdict.created event fires on any webhook endpoint you have configured.