Skip to content

Postmark

Fromenance is a communication provenance platform, and the Postmark adapter turns Delivery webhook events into registrations. With a server token on the source, Fromenance fetches the rendered body through Postmark’s Messages API, so registrations carry a full content fingerprint.

By the end you will have a Postmark source, the Delivery webhook configured with basic auth, the code carried as metadata, and events showing on the source’s health panel.

Terminal window
curl -X POST https://api.fromenance.com/v1/sending-sources \
-H "Authorization: Bearer fr_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"Postmark transactional","kind":"postmark","config":{"server_token":"..."},"expected_daily_volume":8000}'

The response includes webhook_url and secret. For Postmark the secret is a basic auth pair in the form fromenance:<password>, shown once. config.server_token is optional: a Postmark server API token lets Fromenance call GET /messages/outbound/{id}/details to fetch HtmlBody and TextBody for fingerprinting. It is encrypted at rest.

  1. Postmark, your Server, the Message Stream you send from, Webhooks, Add webhook.
  2. Webhook URL: the webhook_url from step 1.
  3. HTTP Auth: username fromenance, password the part after the colon in the secret.
  4. Events: tick Delivery. Other record types are acknowledged and ignored.
  5. Check sends a test payload; Postmark must show a 200.

What Postmark sends and how it is verified

Section titled “What Postmark sends and how it is verified”

Each webhook POST carries Authorization: Basic <base64 user:pass>. Fromenance decodes it and compares it with the source secret in constant time; a mismatch returns 401 and increments signature_failures.

From each Delivery record:

Registration field Postmark field
message_id MessageID
recipient (hashed immediately, never stored) Recipient
sent_at DeliveredAt, else ReceivedAt
verify code Metadata.fromenance_code
template_id Metadata.template_id, else Tag
campaign_id Metadata.campaign_id
content fingerprint fetched through the Messages API when config.server_token is set

Reserve a code before each send and pass it as metadata and in the template model:

import { ServerClient } from "postmark";
import { Fromenance } from "@fromenance/sdk";
const fr = new Fromenance({ apiKey: process.env.FROMENANCE_API_KEY!, tenantSecret: process.env.FROMENANCE_TENANT_SECRET! });
const postmark = new ServerClient(process.env.POSTMARK_SERVER_TOKEN!);
const reserved = await fr.reserve({ to: customer.email, template_id: "password-changed-v1", provider: "postmark" });
await postmark.sendEmailWithTemplate({
From: "alerts@northfieldbank.example",
To: customer.email,
TemplateAlias: "password-changed-v1",
TemplateModel: { first_name: customer.firstName, fromenance_code: reserved.verify_code },
Tag: "password-changed-v1",
Metadata: { fromenance_code: reserved.verify_code, template_id: "password-changed-v1" },
});

In the Postmark template footer use {{fromenance_code}} twice. Because the body is fetched, a Delivery event without a code still produces a registration with a fingerprint, and forwards of that message can match by recipient plus fingerprint; the customer just has no code to type on the verify page. Reserve so the footer carries one.

  • Send test event on the source runs a synthetic Delivery event through the adapter.
  • Send one real message. health.registered_today or health.completed increments within seconds of delivery.

silent is raised when no event arrives within silence_alert_minutes. A rising signature_failures means the basic auth pair in Postmark no longer matches; POST /v1/sending-sources/{id}/rotate-secret issues a new fromenance:<password> pair to paste into Postmark. A registration without a fingerprint while server_token is set means the token lacks access to the stream; Postmark returns 403 on the details call and Fromenance registers without the body.