vkalmuk/swiftmailerdbbundle
SwiftMailerDbBundle adds a database-backed spool to SwiftMailer, letting Symfony apps queue outgoing emails in the database instead of sending them at once. Messages persist until delivered, survive restarts, and can be retried, giving reliable, auditable mail delivery without an external queue serv...
This is a Symfony bundle (originally by White October, forked by vkalmuk), not a Laravel package — there is no service provider, no config/mail.php integration, and no Illuminate\Mail transport. Treat it as something you reach for only on a legacy Symfony 2.x/3.x codebase, or as a reference implementation you copy the idea from when building a DB-backed spool in Laravel (where the equivalent is simply the database queue driver plus a Mail::queue()).
Minimal steps in a Symfony app:
composer require vkalmuk/swiftmailerdbbundle — check the composer.json constraints first; it targets PHP 5.6–7.x and SwiftMailer ~5.4|~6.0, and SwiftMailer itself is end-of-life (replaced by Symfony Mailer). Confirm your PHP version still resolves.app/AppKernel.php (or config/bundles.php if the fork happens to be Symfony 4+ compatible — verify, don't assume).file/memory spool:
swiftmailer:
spool: { type: service, id: white_october.swiftmailer_db.spool }
auto_mapping or add the bundle's Resources/config/doctrine path manually in config.yml.doctrine:schema:update --force (or generate a migration) to create the spool table. Expect a message LONGBLOB/TEXT, a created_at, and a status flag — inspect the entity before migrating so you know what you're committing.swiftmailer:spool:send only if the bundle registers itself as a Swift_Spool implementation the native command understands. In practice this fork ships its own console command (something like white-october:swiftmailer:db:send); run bin/console list swiftmailer and use whatever it exposes.First use case to validate before anything else: queue one email, confirm it lands as a row, then run the send command and confirm the row is updated/deleted and the mail is delivered. If that loop doesn't close, stop — the package is not worth further investment.
Use it only when you need durable, inspectable mail state. The genuine value of a DB spool over a file spool is that queued mail is (a) queryable, (b) transactional with your app data, and (c) survives a server rebuild. If you just want async mail, the file spool with a cron is simpler and this bundle adds nothing.
Transaction-coupled spooling. The classic pattern: you register a user and queue a welcome email in the same DB transaction. If the user insert rolls back, the queued mail rolls back with it. Configure the spool to use the same EntityManager/connection as your domain writes; do not let it use a separate connection or you lose atomicity.
Cron flush. Don't send inline. Schedule the bundle's send command every minute:
* * * * * cd /var/www && bin/console white-october:swiftmailer:db:send --env=prod --time-limit=60
Use --time-limit (if supported) so a large backlog doesn't overlap with the next run; pair with a lockfile or flock.
Monitoring / admin integration. Because spooled messages are rows, you can build a simple "outbox" page: count by status, oldest unsent age, failed deliveries. That's the payoff — lean into it. A cron alert on COUNT(*) WHERE status = 'pending' AND created_at < NOW() - INTERVAL 15 MINUTE catching a stuck spool is worth more than any feature this bundle ships.
Retry/failure handling. There is very little built in. The pattern that works: wrap your send command in a supervisor loop, catch transport exceptions yourself, and increment a retry_count column on the entity (extend the entity or add a listener). Cap retries at 3 and mark failed for manual inspection.
Custom entity. Copy the bundled Email entity into your own bundle/app namespace and point the spool at the repository of that class. This is the cleanest extension point — you keep the bundle's spool logic but own the schema (add tenant_id, headers, indexes).
Laravel translation of the pattern (if you arrived here from a Laravel context): you do not need this package. Use Mail::to(...)->queue(new WelcomeMail) with QUEUE_CONNECTION=database, run php artisan queue:work, and php artisan queue:failed gives you the inspectable outbox for free. The only thing worth borrowing is the atomicity guarantee — use DB::afterCommit() or ->afterCommit() on the job so mail isn't queued when the surrounding transaction rolls back.
spool: service wiring is the sharp edge. If the service ID is wrong or the spool service isn't tagged/aliased correctly, SwiftMailer silently falls back to your configured transport and sends synchronously. Symptom: mail is delivered but no rows appear. Verify by asserting a row exists before running the send command, not after.swiftmailer:spool:send is the native file/memory command; it will not drain a custom DB spool. Running it against a DB spool either does nothing or throws. Always check bin/console list for the bundle's own command.message column stores the full MIME body, base64-inflated. Attachments will bloat the table fast. Prune sent messages aggressively (DELETE FROM spool WHERE sent_at < NOW() - INTERVAL 7 DAY) — the bundle will not do it for you.auto_mapping: true, the bundle's entity can collide with a same-named class or fail to register if the mapping path isn't picked up. Explicitly declare the bundle's mapping in config.yml and check doctrine:mapping:info lists it.->send(), roll back, and check whether a row exists.--time-limit / message cap conservative, and never run two flushers concurrently without a lock.queueMessage, a flushQueue, a getMessages) before using it, and consider copying it into your app as a first-class class you own rather than depending on the fork. The logic is trivial enough to reimplement in an afternoon, and you'll own the debug path.queueMessage to add tenant/priority tagging, and override flushQueue to add your own error handling and dead-letter behaviour. Everything else in the bundle is wiring.How can I help you explore Laravel packages today?