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.
1. Create the source
Section titled “1. Create the source”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.
2. Configure the webhook at Postmark
Section titled “2. Configure the webhook at Postmark”- Postmark, your Server, the Message Stream you send from, Webhooks, Add webhook.
- Webhook URL: the
webhook_urlfrom step 1. - HTTP Auth: username
fromenance, password the part after the colon in the secret. - Events: tick Delivery. Other record types are acknowledged and ignored.
- 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 |
3. Place the code
Section titled “3. Place the code”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.
4. Confirm
Section titled “4. Confirm”- Send test event on the source runs a synthetic Delivery event through the adapter.
- Send one real message.
health.registered_todayorhealth.completedincrements within seconds of delivery.
Source health
Section titled “Source health”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.