Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Ses Webhooks Laravel Package

ankurk91/laravel-ses-webhooks

Laravel package for receiving and handling Amazon SES webhooks. It registers routes for bounce, complaint, and delivery notifications, dispatches events you can listen to, and lets you configure routes and middleware so your app can react to failed, rejected, or undelivered emails.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and let auto-discovery register the provider:
    composer require ankurk91/laravel-ses-webhooks
    
  2. Publish the config and inspect it before touching anything else:
    php artisan vendor:publish --tag=ses-webhooks-config
    
    The config is the whole package surface: route path, middleware, AWS region/credentials, and the SNS topic ARN that is allowed to call the endpoint.
  3. Set the environment values the config reads (SES_WEBHOOK_TOPIC_ARN, AWS region/keys, and the route path). Run php artisan config:clear after any change.
  4. In AWS, create an SNS topic, add an HTTPS subscription pointing at https://your-app.test/<configured-route>, and confirm it. The package handles the SubscriptionConfirmation message for you, so the subscription flips to confirmed on the first request.
  5. In SES, add the SNS topic as a notification destination for the event types you care about (Bounce, Complaint, Delivery, and optionally Open/Click).

Where to look first: config/ses-webhooks.php, then src/Http/Controllers (route + signature verification) and src/Events (the event classes dispatched per SES notification type). The README is thin and the repo is archived, so the source is the real documentation.

First use case: suppression. Listen for bounce and complaint events and mark the recipient as undeliverable:

Event::listen(SesBounce::class, function ($event) {
    User::where('email', $event->email)->update(['email_bounced_at' => now()]);
});

Start with bounces and complaints only; delivery notifications are high-volume and usually not worth persisting.

Implementation Patterns

Fan out from a single entry point. Instead of registering many listeners, listen for the package's generic "notification received" event, persist the raw payload, and dispatch your own domain jobs. This gives you an audit trail and lets you replay events later if a listener had a bug.

Always queue the work. The HTTP endpoint should only verify the signature, store the payload, and return 200 as fast as possible. SNS times out quickly and retries aggressively; slow listeners cause duplicate deliveries and eventual subscription disablement. Use ShouldQueue listeners or dispatch a job from the event.

Deduplicate on the SNS MessageId. SNS is at-least-once. Store the message ID with a unique index before processing, and skip if it already exists. Do not assume ordering.

Store the raw envelope, not just the parsed event. The SNS envelope has Type, MessageId, TopicArn, Timestamp, and a Message field that is itself a JSON string containing the SES notification. Keeping the raw body makes debugging trivial and lets you re-derive anything later.

Restrict by topic ARN. Set the topic ARN in config so a random caller can't POST well-formed SNS payloads to your endpoint. Signature verification prevents forgery, but the ARN check prevents cross-topic confusion when you have staging/production topics.

Multi-environment routing. For local development, expose your app with a tunnel (ngrok/Expose) and subscribe a separate SNS topic in a sandbox AWS account. SES only sends real notifications for real sends, so use SES's mailbox simulator addresses ([email protected], [email protected]) to trigger test events end to end.

Testing. Use Event::fake() and assert the package event is dispatched, but you'll need to bypass SNS signature verification in the test environment—bind a fake validator in the container or POST directly to the controller with the validator mocked. Keep one integration test that exercises real signature verification against a captured payload.

Correlate with your own sends. SES notifications carry the original message ID and any ses:configuration-set/message tags. If you send mail through SES directly, set a configuration set and pass your internal ID as a message tag so the webhook can be traced back to a specific user/notification.

Gotchas and Tips

  • The package is archived. Treat dependency constraints as frozen. It may not support the newest Laravel/PHP versions; check composer.json before upgrading your app, and be prepared to fork or replace it. For a small surface like this, a DIY controller using the AWS SDK's SNS message validator is a realistic alternative.
  • CSRF will bite you. The webhook route must be excluded from VerifyCsrfToken (or live in the api middleware group). If you see 419s in the SNS delivery logs, this is why.
  • Content-Type is not JSON. SNS posts text/plain; charset=UTF-8. Don't add middleware that insists on application/json or parses the body before the package can verify the raw payload—signature verification depends on the exact bytes.
  • Double-encoded payload. The SES event is a JSON string inside the SNS Message field. Decode twice; a single json_decode gives you a string, not an object.
  • Type matters. Handle SubscriptionConfirmation and UnsubscribeConfirmation explicitly; only Notification carries an SES event. If your handler assumes every request is a notification, subscription confirmation will throw.
  • Signature verification needs outbound HTTPS. The validator fetches the SNS signing certificate. In locked-down networks, allow egress to sns.*.amazonaws.com or pre-cache certificates, otherwise all requests fail verification.
  • Config caching. SES_WEBHOOK_TOPIC_ARN changes require php artisan config:clear (or a fresh deploy). A stale cached ARN silently rejects all webhooks.
  • Return 2xx fast, always. If your listener throws, catch it, log it, and still return 200—unless you want SNS to retry. A 5xx storm can get the subscription disabled.
  • SNS payload limit is 256 KB, and SES can batch multiple events per notification for high-volume topics. Loop over the events array rather than assuming one.
  • Debugging: enable SNS delivery status logging to CloudWatch and compare MessageIds against what your app stored. Most "webhooks aren't arriving" issues are an unconfirmed subscription or a URL that isn't publicly reachable over HTTPS.
  • Extension points are limited to config (route, middleware) and event listeners. There's no built-in storage model or retry mechanism, so a webhook_events table and a replay command are on you—build them on day one.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route