Event Tracking via SNS Webhooks
Relevant source files
The following files were used as context for generating this wiki page:
Event Tracking via SNS Webhooks is a core system within Send Again designed to monitor the lifecycle of sent emails. By integrating Amazon Simple Notification Service (SNS) with Amazon SES, the platform captures real-time data on delivery status, recipient engagement (opens and clicks), and deliverability issues such as bounces or complaints. Sources: README.md:14, src/app/api/webhooks/sns/route.ts:1-10
The system exposes a public HTTP endpoint that processes asynchronous notifications from AWS. These events are normalized and persisted in the application database, enabling features like per-campaign analytics, history logs, and automated list hygiene. Sources: src/app/api/webhooks/sns/route.ts:74-148, src/app/app/page.tsx:1150-1310
Architecture and Data Flow
The tracking architecture relies on a webhook listener that handles incoming POST requests from Amazon SNS. The flow begins when Amazon SES detects an event, which it publishes to a configured SNS Topic. SNS then pushes this notification to the api/webhooks/sns endpoint. Sources: README.md:144-148, src/app/api/webhooks/sns/route.ts:71-74
Webhook Processing Flow
The following diagram illustrates the sequence from an SNS notification to database persistence:
Event Types and Normalization
The system maps raw AWS SES event keys to standardized internal types. This normalization ensures consistency across the dashboard and API reports. Sources: src/app/api/webhooks/sns/route.ts:13-24, src/lib/openapi.ts:101-118
| SES Event Key | Internal Type | Description |
|---|---|---|
Send | Send | Email was successfully sent to the mail server. |
Delivery | Delivery | The recipient's mail server accepted the message. |
Open | Open | The recipient opened the email (pixel tracked). |
Click | Click | The recipient clicked a link within the email. |
Bounce | Bounce | The message could not be delivered (Hard/Soft). |
Complaint | Complaint | The recipient marked the message as spam. |
Reject | Reject | SES rejected the send request (e.g., malware). |
RenderingFailure | RenderingFailure | Template variables could not be resolved. |
Subscription | Subscription | Recipient updated subscription preferences. |
Sources: src/app/api/webhooks/sns/route.ts:13-24, src/app/app/page.tsx:392-414
Data Extraction and Detail Mapping
When a Notification is received, the system extracts a detail string specific to the event type to provide additional context in the history logs. Sources: src/app/api/webhooks/sns/route.ts:103-145
- Bounce: Combines
bounceTypeandbounceSubType(e.g.,Permanent/General). - Complaint: Extracts the
complaintFeedbackType. - Click: Captures the specific
link(URL) that was clicked. - Open: Records the
userAgentof the recipient. - DeliveryDelay: Concatenates
delayTypeandexpirationTime. - RenderingFailure: Logs the
templateNameand the specificerrorMessage.
Sources: src/app/api/webhooks/sns/route.ts:107-142
Logic for Payload Extraction
The application utilizes the EVENT_PAYLOAD_KEY map to find the correct data sub-object within the SNS message. Sources: src/app/api/webhooks/sns/route.ts:13-24
function getEventPayload(
message: Record<string, unknown>,
eventType: string
): Record<string, unknown> | undefined {
const payloadKey = EVENT_PAYLOAD_KEY[eventType] ?? eventType;
return asRecord(message[payloadKey]) ?? asRecord(message[payloadKey.toLowerCase()]);
}
Sources: src/app/api/webhooks/sns/route.ts:61-66
Persistence Schema
Events are stored in the email_events table via the insertEvent function. Each event is linked to a specific send via the messageId. Sources: src/lib/db.ts:983-997
Integration and Configuration
For the webhook to function, the environment must be configured with an SES Configuration Set that routes events to an SNS Topic. Sources: README.md:144-148
- SNS Topic: Create a topic in the AWS Console.
- SES Configuration Set: Create a set (default:
email-tracking-config-set) and add an SNS destination. Sources: src/lib/db.ts:474-480 - Endpoint: Add an HTTPS subscription in SNS pointing to
https://[APP_URL]/api/webhooks/sns. - Verification: The route automatically handles the
SubscriptionConfirmationmessage type by fetching theSubscribeURL. Sources: src/app/api/webhooks/sns/route.ts:80-92
Implementation in Route Handler
The POST handler in the SNS route manages both confirmation and notification logic:
Sources: src/app/api/webhooks/sns/route.ts:74-150
export async function POST(req: NextRequest) {
const body = await req.text();
const payload = JSON.parse(body);
const messageType = req.headers.get("x-amz-sns-message-type");
if (messageType === "SubscriptionConfirmation") {
const subscribeUrl = payload.SubscribeURL;
await fetch(subscribeUrl); // Confirms with AWS
return NextResponse.json({ status: "subscribed" });
}
// ... handle Notification
}
Sources: src/app/api/webhooks/sns/route.ts:74-92
Summary
The Event Tracking system provides the necessary feedback loop for email operations. By leveraging SNS webhooks, Send Again can asynchronously update delivery metrics without polling SES. This enables the platform to provide real-time performance analytics, handle unsubscribes automatically, and maintain high deliverability by reacting to bounces and complaints immediately. Sources: src/app/app/page.tsx:1150-1200, README.md:14-25