Delivery and retries
What counts as a successful delivery, how failed deliveries are retried, and how to see every attempt.
Each event becomes one delivery per matching webhook. Deliveries go through their own queue (flaresend-webhooks), so a slow or broken endpoint never holds up sending email.
What counts as success
Flaresend POSTs the event and waits up to 10 seconds for an answer.
| Your endpoint | Result |
|---|---|
Answers 200–299 | Success. The delivery is done. |
Answers anything else, including 3xx | Failed attempt. Redirects are not followed. |
| Doesn't answer within 10 seconds | Failed attempt, recorded as timeout after 10s. |
| Can't be reached (DNS, TLS, connection refused) | Failed attempt, with the error message recorded. |
The first 1,024 characters of your response body are stored with the attempt, so return something short and useful, like ok or an error message.
Retry schedule
After a failed attempt, the delivery waits and tries again:
| After attempt | Next try in |
|---|---|
| 1 | 30 seconds |
| 2 | 2 minutes |
| 3 | 10 minutes |
| 4 | 30 minutes |
| 5 | 1 hour |
| 6 | 3 hours |
| 7 | 6 hours |
| 8 | 12 hours |
The webhooks queue allows 8 retries. When they are used up, the delivery is marked failed and is not tried again. In total a delivery is tried for roughly a day before Flaresend gives up.
A delivery also ends as failed without being sent when the webhook was disabled or deleted before its turn. Its response body says webhook disabled or webhook deleted.
Duplicates and order
- The same event can reach you more than once, for example when your endpoint did the work but answered too slowly. Every attempt carries the same
Flaresend-Event-IdandFlaresend-Delivery-Idheaders. Store the event ID and skip ones you have seen. - Events are not guaranteed to arrive in order. A retried
email.sentcan arrive afteremail.delivered. UsecreatedAt, and the email's ownstatusfrom Retrieve an email when you need the current state.
The deliveries log
Every delivery and its last attempt are stored. See them on the webhook's page in the dashboard, or list them with the API:
const { data, nextCursor } = await flaresend.webhooks.deliveries('wh_01K6B1X9Q2M7T4V8N3R5P6W1ZA', { limit: 50 });
for (const d of data) {
console.log(d.eventType, d.status, d.attempt, d.responseCode, d.nextAttemptAt);
}Each record has:
| Field | Meaning |
|---|---|
status | pending (waiting for its first try or a retry), success or failed |
attempt | How many attempts have been made |
responseCode | The HTTP status of the last attempt, or null if there was no response |
responseBody | Up to 1,024 characters of the last response, or the error |
nextAttemptAt | When the next retry is due, while pending |
completedAt | When it succeeded or finally failed |
See List deliveries.
Send a test event
Use Send test event on the webhook's page, or:
const { deliveryId } = await flaresend.webhooks.test('wh_01K6B1X9Q2M7T4V8N3R5P6W1ZA');The test event is an email.delivered event with made-up values and "test": true in data. It goes to this one webhook even if the webhook doesn't subscribe to email.delivered, and it is signed, retried and logged like a real delivery. The webhook must be enabled, or the call fails with 409 webhook_disabled.
When your endpoint is down
Nothing is lost while your endpoint is down for less than about a day: pending deliveries keep retrying on the schedule above. After a longer outage, read what you missed from the events API instead of webhooks:
const { data } = await flaresend.events.list({ since: '2026-09-26T00:00:00Z', type: 'email.bounced' });See List events.