Prompt
How do I design email events and webhooks?
Latest observation
A good way to design email events and webhooks is to treat them as two layers of the same event system:
- Internal events: what happened in your product
- Deliveries: how you notify people/systems about it via email or HTTP webhooks
Here’s a practical design approach.
1) Start with a clear event model
Define events around business facts, not technical actions.
Examples:
user.registeredorder.paidinvoice.failedsubscription.cancelled
Avoid overly specific or UI-driven events like:
button.clickedsendEmailNow
Each event should answer:
- What happened?
- To which object?
- When?
- What changed?
2) Give every event a stable schema
A webhook/email event payload should be predictable and versioned.
Example shape:
{
"id": "evt_123",
"type": "order.paid",
"version": "1",
"occurred_at": "2026-09-23T12:34:56Z",
"data": {
"order_id": "ord_456",
"amount": 4999,
"currency": "USD",
"customer_id": "cus_789"
}
}
Recommended fields:
id: unique event IDtype: event nameversion: schema versionoccurred_at: timestampdata: event-specific payload- optionally
actor,account_id,request_id,metadata
3) Separate event generation from delivery
Don’t directly “send email” or “fire webhook” from the code path that changes state.
Instead:
- Business action happens
- System records/emits an event
- Delivery workers pick it up
- Email/webhook is delivered asynchronously
Benefits:
- better reliability
- retry support
- no blocking user requests
- easier auditing
A common pattern is:
- write domain state
- write outbox event in same transaction
- background dispatcher sends it
4) Design webhooks as push notifications of events
Webhooks should be:
- event-driven
- retryable
- signed
- idempotent
Webhook request format
Use a JSON POST:
POST /webhooks
Content-Type: application/json
X-Event-Id: evt_123
X-Event-Type: order.paid
X-Event-Timestamp: 1727094896
X-Signature: t=1727094896,v1=...
Webhook body
Send the full event envelope, not just partial data.
Signing
Use HMAC with a shared secret:
- compute signature over timestamp + body
- let receivers verify authenticity
Retries
Retry on:
- network errors
- 5xx responses
- timeouts
Do not retry forever:
- exponential backoff
- max attempts
- dead-letter queue / failure state
Idempotency
Receivers may see the same webhook multiple times.
Include a stable event.id so they can dedupe.
5) Design email events differently from webhooks
Emails are usually notifications for humans, not machine integrations.
You may want two concepts:
- event: something happened
- notification template: what email to send for that event
Example:
- Event:
invoice.failed - Email template: “Your invoice payment failed”
Email event payload
Keep it template-friendly:
- recipient info
- subject data
- body variables
- links/CTAs
- locale
Example:
{
"type": "invoice.failed",
"recipient": {
"email": "user@example.com",
"name": "Ava"
},
"template": "invoice_failed_v2",
"variables": {
"invoice_number": "INV-1001",
"amount": "$49.99",
"pay_url": "https://example.com/pay/INV-1001"
}
}
Good email design principles
- use templates with variables
- support localization
- keep content generated server-side
- avoid huge payloads
- include unsubscribe/preferences where appropriate
6) Add event routing rules
Not every event should go to every channel.
Create rules like:
order.paid→ internal webhook for partnersinvoice.failed→ email customer + webhook billing systemuser.invited→ email onlysecurity.login_detected→ email + webhook to SIEM
This can be modeled as:
- event type
- audience
- channel(s)
- filters
Example rule:
{
"event_type": "invoice.failed",
"channels": ["email", "webhook"],
"conditions": {
"account.plan": "pro"
}
}
7) Include delivery state and observability
Track delivery status separately from event creation.
For each delivery:
- pending
- sent
- delivered
- failed
- retried
- bounced (email)
- acknowledged (webhook optional)
Useful logs/metrics:
- event produced count
- webhook success rate
- email bounce rate
- retry count
- latency
- dead-letter count
This makes support and debugging much easier.
8) Handle schema evolution carefully
Events live a long time. Plan for change.
Best practices:
- add fields, don’t remove or rename immediately
- version your schemas
- preserve backward compatibility
- document changes
- support both old and new versions during migration
A common approach:
type = order.paidversion = 1- introduce
version = 2if payload changes materially
9) Keep payloads small and useful
Don’t put everything in the event.
Good payload contents:
- identifiers
- event-specific facts
- enough data to act without extra lookup if possible
Avoid:
- full database records unless necessary
- secrets/tokens
- highly sensitive PII unless required
- huge nested objects
If consumers need more data, they can fetch it using the provided IDs.
10) Security checklist
For webhooks:
- sign requests
- use HTTPS only
- rotate secrets
- let receivers verify timestamp to prevent replay
- allow endpoint validation/test pings
For email events:
- validate recipients
- prevent leaking sensitive data
- respect opt-in/opt-out rules
- throttle abusive sends
11) A simple recommended architecture
Internal flow
- domain action
- persist state
- emit event to outbox
- dispatcher reads outbox
- route event to:
- email service
- webhook delivery service
Services
- Event service: stores and normalizes events
- Notification service: maps events to templates/rules
- Email sender: renders and sends emails
- Webhook dispatcher: sends signed POST requests with retries
12) A practical example
Event
subscription.cancelled
Webhook
Send to billing partner:
{
"id": "evt_901",
"type": "subscription.cancelled",
"version": 1,
"occurred_at": "2026-09-23T12:00:00Z",
"data": {
"subscription_id": "sub_123",
"customer_id": "cus_456",
"cancelled_at": "2026-09-23T12:00:00Z",
"reason": "user_requested"
}
}
Send to customer:
- Subject: “Your subscription has been cancelled”
- Template variables:
- plan name
- cancellation date
- reactivation link
13) Rule of thumb
If you’re unsure:
- Events = facts about the system
- Webhooks = machine-readable delivery of those facts
- Emails = human-readable notification of those facts
If you want, I can also give you:
- a database schema for events/webhooks/email deliveries, or
- a sample API design for creating and dispatching them.