How to Send Email from a Webhook Without Duplicates or Feedback Loops
Build a webhook-triggered email workflow with signature verification, durable event admission, an outbox, bounded retries and clear delivery states.
TL;DR
- A webhook can trigger an email, but your application should verify the event and decide whether a message is appropriate.
- Persist accepted events before acknowledging them, and admit one send operation per business event and recipient.
- Use a durable outbox for provider calls, with bounded retries and reconciliation after ambiguous failures.
- Keep incoming business webhooks separate from email-provider delivery webhooks to avoid loops and confusing state changes.
There are two webhook directions
In the first direction, another system tells your application that something happened: an order was paid, an appointment changed or a report finished. Your application may respond by sending an email through an email API.
In the second direction, the email platform reports what happened to a message your application already submitted. MailBlastr's delivery webhook documentation describes these outbound provider events. A delivered or bounced event is feedback about a send; it is not automatically an instruction to send another message.
Draw those two routes separately before writing a handler. A generic endpoint that treats every event as “send an email” can create duplicate notifications, accept untrusted requests or trigger a loop when its own delivery event arrives.
Begin with one explicit business rule
Consider a fictional shop sending a receipt after a confirmed payment. The rule should identify the relevant event type, the authoritative order, the recipient and the conditions that make a receipt appropriate. A raw webhook body containing an email address is not enough.
For example, the handler can use a verified provider event to look up the order within the correct tenant, check its payment state and obtain the receipt address from that trusted record. If the event is unrelated, already handled or inconsistent with the current order, the application records the outcome without submitting another receipt.
This makes the email a consequence of a business decision. It also gives support a useful answer to “why was this sent?” beyond “a webhook arrived.”
Verify authenticity using the source provider's scheme
Read the actual source provider's signature documentation. Schemes differ in their headers, signed payload, timestamp rules and key rotation behavior. Preserve the raw request bytes when the scheme signs the original body; parsing and reserializing JSON can change the bytes being verified.
Reject invalid signatures and apply the documented replay protection. Keep signing secrets server-side, with separate secrets where environments or tenants require separation. Do not accept a request merely because its JSON contains a familiar event name or a provider-looking identifier.
If the source is MailBlastr, use its webhook verification guide for the supported signatures. That guide is not a substitute for another service's scheme. A payment provider's webhook must be verified according to that provider's documentation before it can safely influence a receipt workflow.
Persist first, then acknowledge
After verification and basic validation, write an inbox record for the source event. Include its provider, stable event identifier, tenant, event type, received time and processing state. Store only the payload fields needed for processing and investigation, with appropriate protection for personal data.
Enforce a unique constraint on the event identity within its proper scope. An already-recorded event can be acknowledged without creating another business operation. Do not rely on a process-local set of identifiers because it disappears on restart and is not shared across workers.
Return the successful acknowledgement after the event is durably accepted. If the database write fails, do not acknowledge a success that the application cannot later process. Keep the HTTP handler short enough for the source provider's timeout, while leaving the slower email work to a worker.
Admit the send in a transaction
The worker should evaluate the business rule and create an outbox entry as part of the same durable state transition. Give that entry a stable operation key representing the intended email, such as a particular receipt for a particular order and recipient.
The source event identifier and send operation identifier are related but not always identical. Two different provider events may describe the same completed order. Deduplicating only the transport event can therefore still create two receipts. The business-level uniqueness rule prevents that second kind of duplication.
| Record | Question it answers | Example identity |
|---|---|---|
| Incoming event | Have we accepted this provider event? | Provider, tenant and event identifier |
| Business operation | Should this receipt exist once? | Order and receipt purpose |
| Outbox attempt | Have we submitted or retried the send? | Stable send operation plus attempt number |
| Provider message | Which accepted message are events about? | Email provider and message identifier |
Keep these records linked so an incident can be followed from the original event to the final observed delivery state.
Send through the email API with bounded retries
The outbox dispatcher renders the intended message and submits it through the MailBlastr send-email API. Use the documented request format and an idempotency key appropriate to that operation. The idempotency guide explains the provider's retention window and repeated-request behavior.
A timeout is ambiguous: the provider may have accepted the message even if your application did not receive the response. Retry according to the provider's supported mechanism rather than inventing a new operation immediately. Keep the content consistent with the idempotency key when retrying an uncertain request.
Stop retrying permanent validation errors until they are corrected. Back off on retryable failures and make exhausted attempts visible to an operator. Your own durable operation record remains necessary after a provider's deduplication window expires; otherwise an old event replay can become a new email.
Use delivery events to update evidence
When MailBlastr reports a delivered, bounced or other subscribed event, verify it and associate it with the known provider message. Keep the event history and apply state-aware updates. Network delays can change arrival order, so a late event should not blindly overwrite a more relevant state.
MailBlastr retries failed webhook deliveries a bounded number of times. A receiver can see duplicates, and an event can remain unsuccessful after retries are exhausted. Monitor that condition and reconcile important message states through the API instead of assuming the event feed is complete.
Avoid a feedback rule such as “on every email-delivered event, send a confirmation email.” That new email creates its own delivery event. If a notification about a delivery outcome is actually required, give it a distinct purpose, event filter and uniqueness rule that prevents recursive sends.
Keep recipients and content under application control
A webhook-triggered system can become an open mail relay if it accepts an arbitrary destination, subject and body from an unauthenticated request. Even authenticated events should be constrained to the supported business actions and tenant resources.
Resolve the recipient through the authorized record and apply the relevant preferences, suppression rules and business state at dispatch. Escape untrusted values in templates. A customer-provided name or order note should be treated as data, not as permission to insert arbitrary markup or change the message's destination.
Keep API keys out of browser code and public logs. Use separate environments and authorized test recipients while testing. Logging a request body wholesale is rarely necessary to prove that event admission and sending worked.
Test the failure sequence deliberately
Start with one valid event and verify that one appropriate email operation is admitted. Send the identical event again, then send a different event describing the same business outcome. Confirm that both deduplication layers behave as intended.
Next test an invalid signature, wrong tenant, missing order, canceled order, changed recipient and a database failure before acknowledgement. Simulate a provider timeout after possible acceptance and an outbox worker restart. Finally, deliver feedback events out of order and exhaust their retries in a controlled test.
Record expected outcomes before running the cases. A test is useful when it checks the business state and send count, not merely whether an endpoint returned 200. Keep failed or skipped operations visible with a reason so support can distinguish correct suppression from a broken pipeline.
Frequently asked questions
Can a webhook send an email directly?It can call an endpoint that sends email, but a reliable application usually verifies and durably accepts the event before a worker performs the send. This avoids tying the whole operation to one short HTTP request.
Is provider idempotency enough to prevent duplicate emails forever?No. Provider keys have documented scope and retention. Keep a durable business-level record of the intended operation so older replays and different events describing the same outcome do not create duplicates.
Should an email-delivered event trigger a new confirmation email?Only if there is a specific, bounded business requirement. A generic rule can create a loop. Use distinct event types, purposes and uniqueness constraints.
Does a webhook success response prove the email was delivered?No. It means the receiver acknowledged that webhook request. Event admission, API acceptance, recipient-server delivery and human reading are separate observations.