Webhook events
Learn what events Lettermint can send and how to handle them. Subscribe to the events you need when creating a webhook.
Common envelope
All webhook events share a consistent envelope:
Code
Fields:
- id: Unique identifier (UUIDv4) for the specific delivery event. Useful for idempotency.
- event: Event type.
- timestamp: ISO timestamp when the event occurred.
- context: Scope and source IDs for the event.
- data: Event-specific fields (documented per event below).
The context object has these fields:
- scope: Source scope. The value is
team,project, orroute. - team_id: Team ID for the event.
- project_id: Project ID, or
nullfor a team event. - route_id: Route ID, or
nullfor a team or project event.
Use context when one webhook receives events from multiple projects or routes.
Common message data fields:
- message_id: The unique identifier for the message.
- subject: The email subject line.
- metadata: Custom metadata attached to the message (if any).
- tag: The tag assigned to categorize the message (e.g., "newsletter", "order-confirmation"). Will be
nullif no tag was assigned. - tags: The complete list of singular and structured tags. The reserved
__lettermint_tagitem is the singular-tag projection.
The tags field is additive. It does not replace tag. If your webhook data transfer objects reject unknown fields, update them to permit tags.
These fields apply to message.* events. Suppression events use the suppression-specific fields documented below.
Event payloads
message.created
Message accepted for processing.
Example payload:
Code
message.sent
Message sent to recipient server.
Code
message.delivered
Message successfully delivered.
Code
message.auto_replied
Recipient mail server sent an automatic reply such as an out-of-office or vacation response. Auto replies are informational and do not change the message delivery status.
Code
Fields:
auto_reply.sender: Address that sent the automatic reply, when availableauto_reply.recipient: Bounce/feedback recipient address that received the automatic replyauto_reply.subject: Subject of the automatic reply, when availableauto_reply.body.text: Plain-text automatic reply body, when availableauto_reply.body.html: HTML automatic reply body, when available
message.hard_bounced
Permanent delivery failure (e.g., user does not exist).
Code
message.soft_bounced
Temporary delivery failure (e.g., mailbox full, transient error).
Code
message.spam_complaint
Recipient reported the message as spam.
Code
message.failed
The message could not be delivered due to a terminal processing or delivery failure.
Code
When an enforced TLS policy cannot be satisfied, reason_code is enforced_tls_failed and response.status_code is 550. Temporary connection and TLS errors continue through normal retries and are reported as message.soft_bounced while retryable.
message.suppressed
Lettermint skipped one recipient because a suppression matched the message.
Code
Lettermint sends one message.suppressed event for each blocked recipient. The reason and suppression_id fields identify the primary match. The matching_suppression_ids and matching_reasons arrays contain all matching records and reasons.
For disposable recipient suppression, reason is disposable_email and suppression_id is null. Lettermint does not store a suppression record for this route setting.
message.policy_rejected
Message rejected by policy (spam or content filter). This occurs when a message's spam score exceeds the configured threshold before delivery.
Code
Fields:
reason: Human-readable rejection reason (e.g., "spam content", "policy violation")score: The spam score that triggered the rejectionspam_symbols: Array of spam rule matches with name, score, options, and description
message.unsubscribed
Recipient unsubscribed from the mailing list.
Code
message.opened
Recipient opened the email. Requires open tracking to be enabled on the route.
This event is only available for routes with open tracking enabled. By default, webhooks receive human-countable opens and supported Apple or Proton privacy opens. Security scanners, previews, and generic bots require Include machine events.
Code
Fields:
opened_at: Timestamp when the open was detectedfirst_open: Whether this is the first time this recipient opened the emailfirst_observed_open: Whether this is the first human or supported privacy open observationdevice_type: Device category:desktop,mobile, ortabletclient_type: Client category:browser,email_client, etc.client_name: Specific client name:Chrome,Safari,Outlook, etc.user_agent: The user agent string from the requestbot.detected: Whether the open appears to be from an automated source (bot, proxy, scanner)bot.probability: Confidence score (0-100) that this is a bot interactionbot.classification: Source classification such asgenuine,privacy_proxy,security_scanner, orgeneric_botbot.proxy_type: Known proxy family when applicable, otherwisenullbot.reason_codes: Machine-readable reasons behind the classificationbot.machine: Whether this open was classified as machine, privacy, or security activitybot.counts_for_metrics: Whether this open contributes to human-open metricsbot.counts_for_status: Whether this event can change the message statusbot.webhook_eligible: Whether this event is eligible for a default webhooktracking_event_id: Stable ID for this tracking observation. Use it withclassification_revisionto process later updates.classification_revision: Revision number for this tracking event. A higher number replaces a lower number for the sametracking_event_id.engagement: Optional engagement conclusion. It separates observed, human, privacy, and inferred engagement. It can also containprivacy_fetch_at,engagement_confirmed_at, andhuman_open_time_known.
See Bot Detection Field Values for the stable classification contract and guidance for open diagnostic strings.
message.clicked
Recipient clicked a link in the email. Requires click tracking to be enabled on the route.
This event is only available for routes with click tracking enabled. By default, webhooks receive only human-countable clicks; machine, link preview, and security scanner clicks require Include machine events.
Code
Fields:
clicked_at: Timestamp when the click occurreddestination_url: The original destination URL that was clickedlink_index: Zero-based index of the link in the email (order of appearance)anchor_text: The visible text of the clicked linkfirst_click: Whether this is the first time this recipient clicked any link in this emaildevice_type: Device category:desktop,mobile, ortabletclient_type: Client category:browser,email_client, etc.client_name: Specific client name:Chrome,Safari,Outlook, etc.user_agent: The user agent string from the requestbot.detected: Whether the click appears to be from an automated source (security scanner, link preview bot)bot.probability: Confidence score (0-100) that this is a bot interactionbot.classification: Source classification such asgenuine,privacy_proxy,security_scanner, orgeneric_botbot.proxy_type: Known proxy family when applicable, otherwisenullbot.reason_codes: Machine-readable reasons behind the classificationbot.machine: Whether this click was classified as machine, preview, or security activitybot.counts_for_metrics: Whether this click contributes to human-click metricsbot.counts_for_status: Whether this event can change the message statusbot.webhook_eligible: Whether this event is eligible for a default webhooktracking_event_id: Stable ID for this click observation. The click has an ID that is different from the ID of an earlier privacy open.classification_revision: Revision number for this click. A new click starts at revision1.engagement: Optional engagement conclusion and evidence times. It is present when the click confirms an earlier privacy open.
Privacy inference revisions
A supported privacy fetch first creates a message.opened event at revision 1. If a later high-confidence click confirms human engagement, Lettermint sends revision 2 of that open event. The revised open keeps the same tracking_event_id and the same privacy-fetch time. Its engagement.engagement_confirmed_at value contains the click time. Its engagement.human_open_time_known value is false.
The related message.clicked event has its own tracking_event_id and starts at revision 1. Store the highest classification_revision for each tracking_event_id.
See Bot Detection Field Values for the stable classification contract and guidance for open diagnostic strings.
Suppression events
Suppression events are emitted once per non-global suppression entry. Lettermint sends them to enabled, subscribed team, project, and route webhooks that cover the affected transactional and broadcast routes.
Global-scope suppressions do not emit webhook events. Inbound routes do not support suppression events.
Delivery fans out according to the suppression scope:
| Suppression scope | Webhook recipients |
|---|---|
route | Webhooks attached to that route |
project | Webhooks attached to routes in that project |
team | Webhooks attached to routes in that team |
Duplicate additions do not emit another event. A removal event is emitted only when the suppression is actually deleted, so a removal that remains pending for manual review does not emit suppression.removed.
suppression.added
A suppression entry affecting the webhook's route was added. The envelope timestamp is the suppression's creation time.
Code
suppression.removed
A suppression entry affecting the webhook's route was deleted. The original suppression fields remain in the payload. The envelope timestamp is the removal time.
Code
Suppression event data fields:
suppression_id: Unique identifier for the suppression entry.type: Suppressed value type:email,domain, orextension.value: The suppressed email address, domain, or extension.reason: Why the entry was created:manual,hard_bounce,spam_complaint, orunsubscribe.applies_to:allfor transactional and broadcast mail, orbroadcastfor broadcast mail only.
The webhook context identifies the team, project, or route for the suppression.
Inbound events
message.inbound
Sent after an inbound route receives, parses, and queues an email. The event contains SMTP envelope data, parsed message headers and bodies, attachment references, spam diagnostics, and the original raw message.
If the spam score is equal to or higher than the route threshold, Lettermint quarantines the message. It does not send this event. An authorized user can process the message from the dashboard or Team API. Lettermint then sends the event to all current webhook targets that match the original route. The event keeps the original receive time, spam result, and attachment delivery form.
This event is only available for messages from inbound routes. A team or project webhook receives the event when its scope includes the inbound route. See Inbound Mail to create a route, or Process inbound email for handling guidance.
A route webhook can select inbound and outbound routes together. In this case, it can subscribe to inbound and outbound event groups. Each selected route sends only the events that its route type supports.
Example payload:
Code
Event envelope
| Field | Type | Description |
|---|---|---|
id | string | Webhook delivery ID. Lettermint reuses it when retrying the same delivery. |
event | "message.inbound" | Event type. |
timestamp | string | ISO 8601 time when Lettermint created the event. |
data | object | Parsed inbound message. |
The top-level id identifies this webhook delivery. data.message_id identifies the inbound message itself.
Message data
| Field | Type | Description |
|---|---|---|
route | string | Inbound route slug. |
message_id | string | Stable UUID for the inbound message. |
envelope | object | SMTP connection and envelope data. This is separate from visible message headers. |
from | object | Parsed visible From address. |
to | array | Parsed visible To addresses. The array can be empty. |
cc | array | Parsed visible Cc addresses. The array can be empty. |
recipient | string | null | Primary SMTP envelope recipient. Use this field for application routing. |
subaddress | string | null | Plus tag from recipient, without the +. |
reply_to | string | null | Parsed Reply-To address. |
subject | string | null | Parsed message subject. |
date | string | ISO 8601 time when Lettermint received the message. |
body.text | string | null | Plain-text body. |
body.html | string | null | HTML body. Sanitize it before rendering. |
tag | string | null | Valid tag parsed from X-LM-Tag or X-Tag. |
tags | array | Complete singular and structured tag list. The array can be empty. |
headers | array | Parsed non-standard headers as name and value pairs. Standard address, subject, and date headers are represented by dedicated fields. |
authentication_results | object | Structured DKIM, DMARC, and SPF results produced during inbound scanning. |
attachments | array | Attachment objects in exactly one of the delivery shapes below. The array can be empty. |
raw | object | Always-present signed reference to the original .eml message. |
is_spam | boolean | Whether the configured route threshold classified the message as spam. |
spam_score | number | null | Score produced by inbound spam scanning. |
spam_symbols | array | null | Diagnostic scanner symbols. Use authentication_results for stable authentication decisions. |
Each address in from, to, and cc contains email, nullable name, and nullable subaddress. Envelope fields remote_ip, remote_hostname, helo, and mail_from are strings or null when the SMTP session did not provide a usable value.
authentication_results always contains authserv_id, an RFC-style header string, and structured dkim, dmarc, and spf objects. Each method has a result. Related domain, selector, and policy fields can be null. A result can be pass, fail, softfail, neutral, temperror, permerror, none, or unknown.
Attachment delivery shapes
Every attachment contains nullable filename, a content_type string, nullable integer size, and nullable content_id. The remaining fields depend on the route's Attachments setting:
| Mode | Present fields | Absent fields |
|---|---|---|
| Inline | content with Base64-encoded bytes | url, expires_at |
| Signed URL | url, expires_at | content |
The raw object always contains url and expires_at. Attachment and raw-message signed URLs expire after 28 days. See Attachments and raw email for configuration and secure processing guidance.
Verify the webhook signature before trusting these fields. See Signed webhooks for complete examples.
Test events
webhook.test
Special event that can be triggered from the Dashboard for connectivity testing.
Code
Next steps
- Signed Webhooks: Verify the authenticity of webhook payloads.
- Quick introduction: Learn about all the events Lettermint sends.