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

Bundle Aws Ses Monitor Laravel Package

serendipity_hq/bundle-aws-ses-monitor

A Symfony bundle for monitoring Amazon SES: it consumes SNS bounce and complaint notifications, stores them via Doctrine, and fires events so you can suppress bad addresses, handle feedback, and protect your sender reputation.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the repo before the README. Check Resources/config/ (services + routing), the Entity/ folder (Email, Bounce, Complaint, Delivery, Topic, Subscription), the setup Command/, and the SwiftMailer filter class. That's the whole package in ~20 files; the value is the SNS/SES wiring and the state model, not the Symfony plumbing.

Reality check for a Laravel team: this is a Symfony bundle with Doctrine ORM entities. It will not drop into a Laravel app. Two viable paths:

  1. Sidecar service — a small Symfony app using this bundle, sharing the database (or exposing a suppression-check endpoint). Least code, but you now run two PHP apps.
  2. Port it — ~300 lines in Laravel: an SNS webhook controller, a MessageSending listener, a ses_suppressions table, and an Artisan command that creates the SNS topic/subscription and calls SetIdentityNotificationTopic. For a Laravel-only team this is almost always cheaper than path 1.

Minimal Symfony setup (if you go sidecar): composer require serendipity_hq/bundle-aws-ses-monitor, enable the bundle in config/bundles.php, import the bundle routing under a public prefix, configure serendipity_hq_aws_ses_monitor with AWS key/secret/region and db_driver: orm, then doctrine:schema:update (or generate migrations). Run the setup command — it creates the SNS topic, subscribes your endpoint, and attaches the topic to the SES identity for Bounce/Complaint/Delivery. It prints the endpoint and subscription ARN, which is your confirmation that it worked.

First use case: bounce suppression. Send to a known-bouncing address, confirm the Email row flips to bounced, then assert the next send to that address is refused by the SwiftMailer filter. That single loop exercises webhook → parsing → persistence → filter, which is the entire package.

Implementation Patterns

Suppression check at send time. In Symfony, register the bundle's SwiftMailer plugin and it blocks automatically. In Laravel, a MessageSending listener that looks up the recipient and returns false — same effect, but you own the query, so keep it fast (below).

Webhook as the ingestion path. One POST endpoint, branch on x-amz-sns-message-type:

  • SubscriptionConfirmation → GET the SubscribeURL (you must actually visit it; just returning 200 does nothing).
  • Notification → json_decode the Message string, switch on notificationType (Bounce / Complaint / Delivery), and persist. The Message is a nested JSON string, not an object — this trips everyone once.

Correlate on message ID. SES notifications carry mail.messageId. Persist the SES MessageId you get back from the send (or the X-SES-MESSAGE-ID header) so bounces attach to a recipient instead of landing in an orphan pool. Without this the bundle receives everything and matches nothing.

Project the hot path. A Doctrine/DB round-trip on every outgoing mail is wasteful. Mirror is_bounced / is_complained into Redis or a narrow indexed suppressions(email) table, fed by the webhook handler. The full entity graph stays for audit; the send path hits the projection.

Env-scoped topic names. Configure topic names as ses-monitor-{env}. A staging deploy that grabs prod's SES notification config is a genuinely bad afternoon.

Run setup in deploy, guarded. Fire the setup command once per environment behind a config flag; it's designed to be re-runnable but will happily rewrite SES notification config on every deploy otherwise.

Re-check inside queued jobs. Laravel Mailables queued before a bounce arrives will still send. Check suppression in the MessageSending listener and at the top of the job/Mailable build().

Operational commands. You will need suppression:remove <email> (complaint appeals are real) and a bulk importer for historical bounces from CloudWatch/S3 logs.

Gotchas and Tips

  • Doctrine only. No Eloquent, no repository abstraction. Bridge via DB::table() against the mapped tables, or port the entities.
  • The webhook route must be public. No auth middleware, no CSRF, HTTPS only. Behind auth you get a 302 on the subscription POST and a subscription that never delivers events — with no error surfaced in SES.
  • Same region everywhere. SES identity, SNS topic, and the bundle's AWS config must match. A mismatch yields a silent, empty event stream.
  • SetIdentityNotificationTopic is a full replace per (identity, notification type). If Terraform or another app also manages SES notifications, you will fight each other on every deploy. Make one system the owner.
  • IAM scope: sns:CreateTopic, sns:Subscribe, sns:ListSubscriptionsByTopic, ses:SetIdentityNotificationTopic, ses:GetIdentityNotificationAttributes. Miss the last one and the setup command dies mid-run, leaving an orphan topic and no subscription.
  • Delivery notifications are high volume. If enabled, plan retention (prune job, partition, or a TTL on the projection) or the table grows without bound. Often the right answer is to subscribe only to Bounce + Complaint.
  • The filter is send-time only. It does not touch already-queued mail, and it does not apply to any transport that isn't SwiftMailer — Laravel's mailer, API-based senders, and notification channels bypass it entirely.
  • Suppressions are permanent by design. One complaint can black-hole a transactional address forever. Ship an override before you need it.
  • Complaints need real setup. SES only emits complaint notifications with the feedback loop / complaint notifications configured on the identity; sandbox behavior differs from production.
  • Debug by logging the raw body first. SNS posts Content-Type: text/plain, some proxies mangle it, and getContent() can only be read once. Dump the body before any parsing, and replay captured Bounce/Complaint/Delivery fixtures locally instead of poking AWS.
  • SNS signature verification needs egress to sns.<region>.amazonaws.com for the signing certificate. Don't hard-cache certs indefinitely; key rotation will reject valid messages.
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.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections