Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fern/pages/webhooks/webhook-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ webhook_url = "https://your-subdomain.ngrok-free.app/webhooks"

webhook = client.webhooks.create(
url=webhook_url,
events=["message.received"], # add others (e.g. message.sent) as needed
client_id="webhook-demo-webhook" # Ensures idempotency
)

Expand Down
32 changes: 26 additions & 6 deletions fern/pages/webhooks/webhook-verification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ def webhook_handler():
except WebhookVerificationError as e:
return ('', 400)

# Do something with the message...
# handle by event type (msg is the verified payload)
if msg.get("event_type") == "message.received":
# process incoming email...
pass
elif msg.get("event_type") == "domain.verified":
# enable domain features...
pass

return ('', 204)

Expand Down Expand Up @@ -124,8 +130,12 @@ app.post(
const wh = new Webhook(secret);
const msg = wh.verify(payload, headers);

// Do something with the message...
console.log("Webhook verified:", msg);
// handle by event type (msg is the verified payload)
if (msg.event_type === "message.received") {
// process incoming email...
} else if (msg.event_type === "domain.verified") {
// enable domain features...
}

res.status(204).send();
} catch (err) {
Expand Down Expand Up @@ -180,7 +190,13 @@ def webhook_handler():
print(f"Verification failed: {e}")
return ('', 400)

# Do something with the message...
# handle by event type (msg is the verified payload)
if msg.get("event_type") == "message.received":
# process incoming email...
pass
elif msg.get("event_type") == "domain.verified":
# enable domain features...
pass

return ('', 204)

Expand Down Expand Up @@ -212,8 +228,12 @@ app.post(
const wh = new Webhook(secret);
const msg = wh.verify(payload, headers);

// Do something with the message...
console.log("Webhook verified:", msg);
// handle by event type (msg is the verified payload)
if (msg.event_type === "message.received") {
// process incoming email...
} else if (msg.event_type === "domain.verified") {
// enable domain features...
}

res.status(204).send();
} catch (err) {
Expand Down
26 changes: 22 additions & 4 deletions fern/pages/webhooks/webhooks-overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ This event-driven approach is more efficient and allows you to build fast, respo
- **Real-Time Speed:** Build conversational agents that can reply to incoming emails in seconds.
- **Efficiency:** Eliminates the need for constant polling, which saves you computational resources and simplifies your application logic.

## Available Events

AgentMail supports seven webhook event types. When creating a webhook, you can subscribe to specific events or receive all of them. See [Webhook Events](/events) for full payload details.

**Message events:**
- **`message.received`** — New email received and processed in one of your inboxes
- **`message.sent`** — Message successfully sent from your inbox
- **`message.delivered`** — Delivery confirmed by the recipient's mail server
- **`message.bounced`** — Message failed to deliver and bounced back
- **`message.complained`** — Recipient marked your message as spam
- **`message.rejected`** — Message rejected before send (validation or policy)

**Domain events:**
- **`domain.verified`** — Custom domain successfully verified

## The Webhook Workflow

The process is straightforward:
Expand All @@ -28,25 +43,28 @@ The process is straightforward:
<CodeBlocks>
```python
client.webhooks.create(
url="https://<your-ngrok-url>.ngrok-free.app/webhooks"
url="https://<your-ngrok-url>.ngrok-free.app/webhooks",
events=["message.received", "message.sent"],
)
```
```typescript
await client.webhooks.create({
url: "https://<your-ngrok-url>.ngrok-free.app/webhooks",
events: ["message.received", "message.sent"],
});
```
</CodeBlocks>
Specify which events to receive; omit `events` to subscribe to all event types.
</Step>
<Step title="3. AgentMail Sends Events">
When a new message is received in one of your inboxes, AgentMail will immediately send a `POST` request with a JSON payload to your registered URL.
When an event occurs (e.g. a new message is received, a message is delivered, or a domain is verified), AgentMail sends a `POST` request with a JSON payload to your registered URL.
</Step>

</Steps>

## Payload Structure

When AgentMail sends a webhook, the payload will have the following structure.
When AgentMail sends a webhook, the payload includes `event_type` and `event_id`, plus event-specific data. The example below shows a `message.received` payload; other events use different top-level objects (`send`, `delivery`, `bounce`, etc.). See [Webhook Events](/events) for each event's payload shape.

```json Webhook Payload
{
Expand Down Expand Up @@ -88,7 +106,7 @@ When AgentMail sends a webhook, the payload will have the following structure.

### Field Descriptions

- **`event_type`** (`string`): The name of the event. Currently, this will always be `message.received`.
- **`event_type`** (`string`): The event type (e.g. `message.received`, `message.sent`, `message.delivered`). Payload structure varies by event—see [Webhook Events](/events) for each event's shape.
- **`event_id`** (`string`): A unique identifier for this specific event delivery.
- **`message`** (`object`): A dictionary containing the full details of the received email message.
- **`from_`** (`array<string>`): The sender's email address. Note the trailing underscore to avoid conflict with the Python keyword.
Expand Down