1
Mint an org-scoped key with the right scopes
Create an
ak_ key in the Mailr app under your workspace name → Manage account → API keys (Owner-only). Give it events:write
and/or import:write and/or webhooks:inbound as needed — or leave it unscoped (unscoped keys pass every
check). A key scoped to only import:write cannot post events, and vice versa.2
Verify the key resolves to the right org
org_id / org_name is the tenant you expect. A 403 here means either the key is missing the
webhooks:inbound scope that /api/v1/me requires, or it isn’t tied to a billing owner. If you scoped the
key to events:write only, add webhooks:inbound (or leave the key unscoped) to use this check.3
Bind your automations
Send at least one live event so your
event_type appears in the app’s binding picker (dry-run events write nothing, so they don’t register the event type),
then set an automation’s trigger to Custom API and bind it to that event_type (and object_type if
you want to pin one). Nothing mails until a binding exists — an unbound event returns
matched_automations: 0.4
Dry-run your real payload
POST your actual event with
"dry_run": true and confirm the outcome is reason: "queued" for the
automation you expect. Dry run exercises the full binding/filter/address/dedup path but writes nothing.5
Get your addresses and property keys right
Ensure every reserved key is spelled exactly (
street, city, state, zip, client_name,
client_email, client_phone, amount, status, mailr_lead_id) and every other key is cf_-prefixed
— a mis-spelled reserved key is a 422. Confirm each mailable record has street plus city or zip,
or it reports no_address.6
Subscribe your lead endpoint and verify signatures
POST /api/v1/webhooks/subscribe, store the signing_secret, and implement HMAC verification over
"<X-Mailr-Timestamp>.<rawBody>" with a 300-second freshness window (see
Receive leads). Use the raw request body, not re-serialized
JSON.7
Close the loop guard
On any event your automation derives from a Mailr-delivered lead, echo
mailr_lead_id into
record.properties.mailr_lead_id. Without it, a lead Mailr sourced can start a campaign back at its own
address — an infinite echo. Confirm such an event reports reason: "loop_guard".8
Handle limits, retries, and replay
Read
Retry-After on 429/503 and back off. Treat 4xx as terminal (fix the request); treat
429/5xx as retryable. Wire stable idempotency keys (dedup_id, deal_id) so that your gap-recovery
plan is simply replay the window — there is no reconciliation sweep.Once all eight pass against a real key using
dry_run and a low-volume test automation, flip your integration
to full production traffic. Because every write is idempotent, a cautious first run that replays its first
window is safe.