Transactional delivery
A transactional message (a receipt, a shipping notice, a status change) must be reliable and auditable. The pattern is the same for SMS and email.
-
Send by key with a stable idempotency key
const { data } = await fabric.messages.send("order.shipped", {to: "+233201234567",data: { name: "Ama", ref: "GH-4821" },reference: "order:GH-4821", // your business identifieridempotencyKey: "order-shipped:GH-4821", // the logical operation});Derive the idempotency key from the operation, not the attempt — if your job runs twice, the same key returns the original delivery instead of sending again.
-
Record the delivery id
Store
data.idagainst your order. It is deterministic per operation, so a replay reconciles to the same record. -
Advance from the webhook, not the response
// in your verified webhook handlerif (event.type === "message.delivered") {await markNotified(event.data.deliveryId ?? event.data.messageId, event.data.key);}The send response means “accepted”, not “delivered”. Move your workflow forward on the terminal webhook event.
-
Reconcile
Compare four facts: your
reference, the Fabric deliveryid, the terminal event, and the wallet ledger entry. Agreement across all four is your proof of delivery and cost.
Guardrails
Section titled “Guardrails”- Use the transactional class so the message is not held by promotional quiet hours.
- Set
maxCoston the send if a runaway render could otherwise exceed an acceptable cost. - Handle
message.failed/message.undeliveredexplicitly — retry through your own workflow with a new idempotency key only when a retry is genuinely warranted.