FlaresendDocs

Event types

Every event a webhook can receive, when it fires, and what its data contains.

A webhook subscribes to a list of event types, or to "*" for all of them. These are the types you can pick:

TypeFires whenrecipient
email.queuedThe email was accepted and put on the send queuenull
email.sentCloudflare accepted the message for deliverynull
email.deliveredA recipient's mail server accepted itthe address
email.deferredA recipient's server asked to try again laterthe address
email.bouncedA recipient's server refused itthe address
email.complainedA recipient marked it as spamthe address
email.rejectedCloudflare refused to deliver it to a recipientthe address
email.failedFlaresend or Cloudflare could not send it at allnull or the address
email.openedThe open-tracking pixel loaded for the first timenull
email.clickedA tracked link was clickednull
email.canceledA scheduled email was cancelednull

Events that only appear on the email's timeline, and never go to webhooks: email.scheduled, email.retrying and email.test.

Delivery events (delivered, deferred, bounced, complained, rejected, and failed when it comes from Cloudflare) arrive once per recipient. An email to three people can produce three email.delivered events.

Common fields

Every payload has this shape:

Prop

Type

The rest of data depends on the type.

email.queued

Only the common fields. Scheduled emails don't fire email.queued when they are created; they get email.scheduled on the timeline instead.

{
  "id": "evt_01K6B2Y5A1T8R4M2V7N9Q3X5WC",
  "type": "email.queued",
  "createdAt": "2026-09-26T14:02:08.117Z",
  "data": {
    "emailId": "email_01K6B2Y4ZP9R3M7T8V5N2QXW4C",
    "recipient": null,
    "from": "hello@acme.com",
    "subject": "Welcome to Acme",
    "tags": { "kind": "welcome" }
  }
}

email.sent

data.messageId is the ID Cloudflare returned for the message.

{
  "id": "evt_01K6B2Y7C3M9Q2T5V8N4R6X1PB",
  "type": "email.sent",
  "createdAt": "2026-09-26T14:02:09.504Z",
  "data": {
    "messageId": "0101018f7d0c4d9a-msg-deadbeef",
    "emailId": "email_01K6B2Y4ZP9R3M7T8V5N2QXW4C",
    "recipient": null,
    "from": "hello@acme.com",
    "subject": "Welcome to Acme",
    "tags": { "kind": "welcome" }
  }
}

Delivery events

email.delivered, email.deferred, email.bounced, email.complained, email.rejected, and email.failed when Cloudflare reports it. These carry what Cloudflare sent in its delivery event:

Prop

Type

{
  "id": "evt_01K6B2ZC1W7Q3T9M4X8V2N5R6P",
  "type": "email.delivered",
  "createdAt": "2026-09-26T14:02:11.482Z",
  "data": {
    "delivery": {
      "status": "delivered",
      "provider": "gmail",
      "deliveryTimeMs": 1234,
      "smtpStatusCode": "250",
      "smtpEnhancedStatusCode": "2.0.0",
      "smtpResponse": "250 2.0.0 OK 1714820445 a1b2c3 - gsmtp"
    },
    "terminal": true,
    "emailId": "email_01K6B2Y4ZP9R3M7T8V5N2QXW4C",
    "recipient": "ada@example.com",
    "from": "hello@acme.com",
    "subject": "Welcome to Acme",
    "tags": { "kind": "welcome" }
  }
}

email.failed

There are two sources, and the payload tells you which:

  • From Flaresend, when the message could not be handed to Cloudflare: recipient is null and data has code and message. The code is either the error code Cloudflare's send call returned (for example E_SENDER_NOT_VERIFIED), or one Flaresend sets itself: queue_error (the email could not be put on the queue), payload_missing (the body was not in R2) or max_retries_exhausted (the send was retried the maximum number of times). The email's lastError holds the same values.
  • From Cloudflare, as a delivery event: recipient is set and data has delivery, failure and terminal, like the other delivery events.
{
  "id": "evt_01K6B2ZG5V1R8T4Q2M6X9N3P7F",
  "type": "email.failed",
  "createdAt": "2026-09-26T14:02:09.880Z",
  "data": {
    "code": "max_retries_exhausted",
    "message": "the send was retried the maximum number of times",
    "emailId": "email_01K6B2Y4ZP9R3M7T8V5N2QXW4C",
    "recipient": null,
    "from": "hello@acme.com",
    "subject": "Welcome to Acme",
    "tags": { "kind": "welcome" }
  }
}

A message that fails with Cloudflare's E_RECIPIENT_SUPPRESSED is sent as email.failed too, but the email's status becomes rejected, not failed.

email.opened

Needs open tracking. Fires once per email, the first time the pixel loads. Later opens don't fire again. data.userAgent is the User-Agent of the request that loaded the pixel, or null.

{
  "id": "evt_01K6B3B6H2Q9T5M1V8X4N7R3PG",
  "type": "email.opened",
  "createdAt": "2026-09-26T14:10:02.614Z",
  "data": {
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    "emailId": "email_01K6B2Y4ZP9R3M7T8V5N2QXW4C",
    "recipient": null,
    "from": "hello@acme.com",
    "subject": "Welcome to Acme",
    "tags": { "kind": "welcome" }
  }
}

Many mail clients and privacy proxies load images automatically, so an open doesn't prove a person read the email.

email.clicked

Needs click tracking. Fires on every click of a tracked link. data.url is the original link.

{
  "id": "evt_01K6B3C9J5T2Q8M4V1X7N3R6PH",
  "type": "email.clicked",
  "createdAt": "2026-09-26T14:10:31.052Z",
  "data": {
    "url": "https://acme.com/login",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    "emailId": "email_01K6B2Y4ZP9R3M7T8V5N2QXW4C",
    "recipient": null,
    "from": "hello@acme.com",
    "subject": "Welcome to Acme",
    "tags": { "kind": "welcome" }
  }
}

email.canceled

A scheduled email was canceled before it was sent. Only the common fields.

Types in TypeScript

@flaresend/client exports the payload type, and @flaresend/types exports the list of types:

import type { WebhookPayload } from '@flaresend/client';
import { WEBHOOK_EVENT_TYPES, type WebhookEventType } from '@flaresend/types';

WebhookPayload['data'] is typed with the common fields and an index signature for the rest, so narrow on type before reading type-specific fields.

On this page