AWS SES Notifications via SNS: Bounces & Deliveries

AWS SES Event Notifications via SNS (Bounces, Complaints, Deliveries)

Amazon SES can publish bounce, complaint, and delivery notifications to an SNS topic which then posts to your HTTP(S) endpoint. This guide explains how to subscribe your Laravel endpoint, parse SNS envelopes, and forward normalized email.provider_event runs into SendPromptly.

You will learn how to confirm SNS subscriptions, parse nested SES JSON, test with the SES mailbox simulator, and forward canonical events. The primary keyword aws ses sns event notifications appears in the testing and configuration guidance.

SES notification options

Know where SES will publish notifications and at what scope.

Identity-level notifications vs event publishing

SES can be configured per-identity (domain/address) or publish events to an SNS topic for broader consumption.

Region/identity scoping considerations

SES notifications are scoped to a region and identity — ensure your SNS topic and subscriptions are in the expected region.

Micro checklist:

  • Confirm the SES identity and AWS region.
  • Decide between identity-level notifications and shared SNS topics.
  • Use distinct topics for dev/stage/prod.

Create SNS topic + subscribe your endpoint

Create an SNS topic, add an HTTPS subscription for your endpoint, and confirm the subscription flow.

HTTPS endpoint subscription + confirmation flow

SNS sends a SubscriptionConfirmation message — your endpoint must GET the provided SubscribeURL to confirm.

Separate topics reduce blast radius and make routing simpler.

Common gotcha: Forgetting to handle SubscriptionConfirmation prevents any notifications from arriving.

Configure SES to publish events

Enable bounce, complaint, and (optional) delivery notifications for the SES identity.

Enable bounce + complaint notifications

Configure SES to publish bounce/complaint events to the SNS topic you created.

Delivery notifications (optional)

Enable delivery notifications if you need delivery telemetry in your system.

Avoid double-notifying via multiple methods

Do not configure both SNS and direct email feedback at the same time for the same identity.

Micro checklist:

  • Enable required notification types in SES.
  • Point SES notifications to the SNS topic ARN.
  • Verify subscription confirmation is handled by your endpoint.

Understand notification contents

SNS envelopes contain a Message string that is itself JSON — parse carefully.

Bounce types (permanent vs transient)

Differentiate hard bounces from soft bounces; treat permanent bounces as candidates for suppression.

What fields you should extract for your canonical schema

Extract recipient, message-id, bounce type, and timestamp for your canonical email.provider_event payload.

Suggested diagram: Show SNS envelope → parse Message → normalize to email.provider_event.

Testing with SES mailbox simulator

SES provides simulator addresses to trigger success, bounce, and complaint flows.

Send to success@…, bounce@…, complaint@…

Use the simulator addresses to generate predictable notification types for testing.

What you should see arrive via SNS

Expect SNS Notification envelopes with Message bodies containing the SES notification payload.

Micro checklist:

  • Use SES simulator addresses for deterministic testing.
  • Confirm SNS Notification arrives at your endpoint and Message parses as JSON.

Forward to SendPromptly

Normalize SES notification contents into email.provider_event and forward to SendPromptly with an Idempotency-Key.

Canonical email.provider_event

Include provider, provider_event, occurred_at, recipient, message_id, and meta fields.

Correlation and Message Log strategy

Log message-id and a correlation id so you can trace the SNS attempt back to Message Log in SendPromptly.

Required code snippets

A) SNS receiver controller (no signature yet; signature comes in next guide)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

namespace App\Http\Controllers\Webhooks;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Http;
use App\Jobs\ProcessSesSnsNotification;

class AwsSnsSesController extends Controller
{
    public function handle(Request $request)
    {
        $envelope = $request->json()->all();
        $type = $envelope['Type'] ?? null;

        if ($type === 'SubscriptionConfirmation' && !empty($envelope['SubscribeURL'])) {
            // Confirm subscription
            Http::get($envelope['SubscribeURL'])->throw();
            return response()->noContent(204);
        }

        if ($type === 'Notification' && !empty($envelope['Message'])) {
            // SES content is inside Message as JSON string
            ProcessSesSnsNotification::dispatch($envelope);
            return response()->noContent(204);
        }

        return response()->json(['error' => 'unsupported message type'], 400);
    }
}

Test steps (curl + expected response)

Local parse smoke test (no real SNS signature)

1
2
3
4
5
6
7
8
9
curl -i -X POST "https://YOUR-APP.test/api/webhooks/aws/sns/ses" \
  -H "Content-Type: application/json" \
  -d '{
    "Type":"Notification",
    "MessageId":"11111111-2222-3333-4444-555555555555",
    "TopicArn":"arn:aws:sns:us-east-1:123456789012:ses-events",
    "Timestamp":"2026-02-14T12:00:00.000Z",
    "Message":"{\"notificationType\":\"Bounce\",\"bounce\":{\"bounceType\":\"Permanent\"}}"
  }'

Expected

  • HTTP/1.1 204 No Content
  • Job enqueued and normalized event forwarded (once you wire the job).

End-to-end test (real SES simulator)

  • Send through SES to mailbox simulator addresses (success/bounce/complaint) and confirm SNS posts arrive.

Run the simulator, then inspect results: you should see SES events as normalized runs. Open Sample Project · Open Message Log

Common failure modes

  1. SES identity notifications configured in the wrong AWS Region (nothing arrives).
  2. Subscription not confirmed (you ignored SubscriptionConfirmation).
  3. Not parsing nested JSON string in Message (you treat it as an object).
  4. Treating soft bounces as permanent (bounceType matters).
  5. Enabling multiple notification methods → duplicates for the same event.

Conclusion

  • SNS envelopes contain a JSON Message string — parse it, don’t assume nested object.
  • Confirm SubscriptionConfirmation to start receiving notifications.
  • Use SES mailbox simulator for deterministic tests and verify runs in Message Log.