diff --git a/apps/wiki/content/docs/guides/webhooks.mdx b/apps/wiki/content/docs/guides/webhooks.mdx index 90fb94f..bd503d9 100644 --- a/apps/wiki/content/docs/guides/webhooks.mdx +++ b/apps/wiki/content/docs/guides/webhooks.mdx @@ -4,6 +4,8 @@ description: Send real-time event data from Plunk to your own application using icon: Webhook --- +import {Tab, Tabs} from 'fumadocs-ui/components/tabs'; + Plunk can send real-time HTTP requests to your application when specific events occur, such as email bounces, spam complaints, or custom events. This is done by creating a [workflow](/concepts/workflows) that uses the **Webhook** step to forward event data to your own endpoint. ## How it works @@ -104,7 +106,13 @@ When using the default payload (no custom body configured), Plunk sends a JSON r "event": { "subject": "Welcome to Plunk", "from": "hello@example.com", - "bounceType": "Permanent" + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": "camp_abc123", + "sourceType": "CAMPAIGN", + "bounceType": "Permanent", + "bouncedAt": "2025-01-15T10:30:00.000Z" } } ``` @@ -113,16 +121,268 @@ The `event` field contains the data associated with the event that triggered the ### Event data by type -The `event` field varies depending on which event triggered the workflow: +#### Email events -| Event | Fields in `event` | -| ----------------- | ------------------------------------------------------------------------ | -| `email.sent` | `subject`, `from`, `messageId`, `templateId`, `campaignId`, `sourceType` | -| `email.open` | `subject`, `from`, `openedAt`, `isFirstOpen` | -| `email.click` | `subject`, `from`, `clickedAt`, `clicks`, `isFirstClick` | -| `email.bounce` | `subject`, `from`, `bounceType`, `bouncedAt` | -| `email.complaint` | `subject`, `from`, `complainedAt` | -| Custom events | Whatever data you passed when tracking the event | +Most email events share a common set of base fields: + +| Field | Description | +| ------------ | ------------------------------------------------------------------------------------------- | +| `subject` | The email subject line | +| `from` | The sender email address | +| `fromName` | The sender display name | +| `messageId` | The AWS SES message ID | +| `templateId` | The template ID, if the email was sent using a template (otherwise `null`) | +| `campaignId` | The campaign ID, if the email was part of a campaign (otherwise `null`) | +| `sourceType` | How the email was triggered: `TRANSACTIONAL`, `CAMPAIGN`, `WORKFLOW`, or `INBOUND` | + +In addition to these base fields, each event includes the following event-specific fields: + + + + + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": null, + "sourceType": "TRANSACTIONAL", + "sentAt": "2025-01-15T10:30:00.000Z" +} +``` + +| Field | Description | +| -------- | ------------------------ | +| `sentAt` | When the email was sent | + + + + + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": "camp_abc123", + "sourceType": "CAMPAIGN", + "deliveredAt": "2025-01-15T10:30:05.000Z" +} +``` + +| Field | Description | +| ------------- | ------------------------------- | +| `deliveredAt` | When the email was delivered | + + + + + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": null, + "sourceType": "TRANSACTIONAL", + "openedAt": "2025-01-15T11:00:00.000Z", + "opens": 1, + "isFirstOpen": true +} +``` + +| Field | Description | +| ------------- | ------------------------------------------------------------ | +| `openedAt` | When the email was first opened | +| `opens` | Total number of times this email has been opened | +| `isFirstOpen` | `true` if this is the first time the contact opened the email | + + + + + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": null, + "sourceType": "TRANSACTIONAL", + "link": "https://example.com/pricing", + "clickedAt": "2025-01-15T11:05:00.000Z", + "clicks": 1, + "isFirstClick": true +} +``` + +| Field | Description | +| -------------- | -------------------------------------------------------------- | +| `link` | The URL that was clicked | +| `clickedAt` | When the first click occurred | +| `clicks` | Total number of times links in this email have been clicked | +| `isFirstClick` | `true` if this is the first click from this contact on this email | + + + + + +Permanent bounce: + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": null, + "sourceType": "TRANSACTIONAL", + "bounceType": "Permanent", + "bouncedAt": "2025-01-15T10:31:00.000Z" +} +``` + +Transient (soft) bounce: + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": null, + "sourceType": "TRANSACTIONAL", + "bounceType": "Transient", + "transientBounce": true +} +``` + +| Field | Description | +| ---------------- | ----------------------------------------------------------------------------------------------- | +| `bounceType` | `Permanent` (hard bounce) or `Transient` (soft bounce, e.g. mailbox full or out-of-office) | +| `bouncedAt` | When the bounce occurred (permanent bounces only) | +| `transientBounce`| `true` for soft bounces — these do not count toward bounce rate and the contact stays subscribed | + + + Only `Permanent` bounces count toward your project's bounce rate and trigger automatic contact unsubscription. `Transient` bounces are tracked for visibility only. + + + + + + +```json +{ + "subject": "Welcome to Plunk", + "from": "hello@example.com", + "fromName": "Plunk Team", + "messageId": "ses-message-id", + "templateId": null, + "campaignId": null, + "sourceType": "TRANSACTIONAL", + "complainedAt": "2025-01-15T10:35:00.000Z" +} +``` + +| Field | Description | +| -------------- | ------------------------------------------------ | +| `complainedAt` | When the spam complaint was received | + + + + + +This event fires when an email is received at your verified domain. See [Receiving Emails](/guides/receiving-emails) for setup instructions. + +```json +{ + "messageId": "ses-message-id", + "from": "sender@example.com", + "fromHeader": "Jane Smith ", + "to": "support@yourdomain.com", + "subject": "Re: Your question", + "timestamp": "2025-01-15T10:30:00.000Z", + "recipients": ["support@yourdomain.com"], + "hasContent": true, + "spamVerdict": "PASS", + "virusVerdict": "PASS", + "spfVerdict": "PASS", + "dkimVerdict": "PASS", + "dmarcVerdict": "PASS", + "processingTimeMillis": 142 +} +``` + +| Field | Description | +| ---------------------- | ------------------------------------------------------------------- | +| `messageId` | The AWS SES message ID | +| `from` | The sender's email address | +| `fromHeader` | The full `From` header, including display name if present | +| `to` | The recipient address at your verified domain | +| `subject` | The email subject line | +| `timestamp` | When SES received the email | +| `recipients` | All recipient addresses in the envelope | +| `hasContent` | Whether the email body content is available | +| `spamVerdict` | SES spam check result: `PASS`, `FAIL`, `GRAY`, or `PROCESSING_FAILED` | +| `virusVerdict` | SES virus check result | +| `spfVerdict` | SPF authentication result | +| `dkimVerdict` | DKIM authentication result | +| `dmarcVerdict` | DMARC authentication result | +| `processingTimeMillis` | Time SES took to process the inbound email | + + + + + +#### Contact events + +`contact.subscribed` and `contact.unsubscribed` carry no event data by default. The `event` field will be an empty object `{}`. + +The exception is when an unsubscription is triggered automatically by an email bounce or complaint — in that case `event` includes a `reason` field: + +```json +{ + "reason": "bounce" +} +``` + +| Field | Value | +| -------- | -------------------------------------------------- | +| `reason` | `"bounce"` or `"complaint"` (when system-triggered) | + +#### Segment events + +Both `segment..entry` and `segment..exit` include: + +```json +{ + "segmentId": "seg_abc123", + "segmentName": "VIP Users" +} +``` + +| Field | Description | +| ------------- | ----------------------------- | +| `segmentId` | The ID of the segment | +| `segmentName` | The display name of the segment | + +#### Custom events + +Custom events tracked via the API include whatever data you passed in the `data` field when calling `track`. + +#### No event data + +For events that carry no data, the `event` field will be an empty object `{}`. ## Common use cases