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

Swiftmailerdbbundle Laravel Package

starstruck0207/swiftmailerdbbundle

Symfony bundle that integrates SwiftMailer with Doctrine so outgoing emails can be spooled to the database instead of memory or files. Helps queue and track mail for reliable delivery in apps already using Doctrine.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else, decide whether you should adopt this at all. The package is a fork of whiteoctober/swiftmailer-db-bundle, which has been unmaintained since the Symfony 2.x/3.x era. It depends on swiftmailer/swiftmailer, which is itself end-of-life. If you are on Symfony 4.4+, or can move to symfony/mailer with the Doctrine transport, do that instead — Symfony's symfony/doctrine-messenger transport solves the same problem (durable, DB-backed mail queue) and is maintained. Adopt this bundle only if you are pinned to a legacy Symfony app and need DB spooling with minimal churn.

Minimum steps to a working spool:

  1. Require the package and confirm what you actually got:

    composer require starstruck0207/swiftmailerdbbundle
    

    Then check the installed namespace and service IDs, because forks routinely rename them:

    composer show starstruck0207/swiftmailerdbbundle
    bin/console debug:container | grep -i spool
    

    Expect the namespace to still be WhiteOctober\SwiftMailerDBBundle, not something matching the Composer name.

  2. Register the bundle. On Symfony 2/3, add it to app/AppKernel.php:

    new WhiteOctober\SwiftMailerDBBundle\WhiteOctoberSwiftMailerDBBundle(),
    

    On Symfony 4+, config/bundles.php — but verify the bundle class actually supports Flex registration; many old bundles do not.

  3. Point SwiftMailer's spool at the bundle's DB spool service in config.yml:

    swiftmailer:
        spool:
            type: service
            id: white_october_swift_mailer_db.spool
    

    Confirm the ID from debug:container above. The transport (swiftmailer.transport) stays whatever you already use — SMTP, sendmail, etc. The spool only changes buffering, not delivery.

  4. Create the spool table. The bundle ships a Doctrine entity but no automatic schema management. Run:

    bin/console doctrine:schema:update --dump-sql
    

    Inspect the output for the spool table (typically swiftmailer_spool), then apply it. In production, write an explicit Doctrine migration rather than using schema:update.

  5. Smoke test end-to-end:

    $message = (new \Swift_Message('test'))->setFrom('[email protected]')->setTo('[email protected]')->setBody('hi');
    $mailer->send($message);
    

    Then verify the row landed:

    SELECT id, created_at, LENGTH(message) FROM swiftmailer_spool;
    

    And flush it:

    bin/console swiftmailer:spool:send --env=prod
    

First real use case: the canonical one — you have multiple web front-ends behind a load balancer and a single worker/cron host that is allowed to talk to your SMTP relay. The file spool cannot do this (each web node has its own queue on its own disk). The DB spool gives you one shared queue and one place to drain it.

Implementation Patterns

Cron-driven flush (the standard pattern). The web request only writes to the spool table; nothing is sent inline. A single cron entry or supervisor process drains it:

*/2 * * * * cd /var/www/app && bin/console swiftmailer:spool:send --env=prod --message-limit=200

Keep the interval shorter than your alerting tolerance, and the message limit low enough that one flush run does not exceed your SMTP provider's rate limit or the cron overlap window.

Dedicated worker command with failure handling. The stock swiftmailer:spool:send command is fire-and-forget: if your SMTP relay rejects a message, the spool row may already be gone. For anything important, wrap it in a custom command that reads rows, attempts delivery, and only deletes on success — or moves failures to a swiftmailer_spool_failed table:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $conn = $this->getDoctrine()->getConnection();
    $rows = $conn->fetchAllAssociative(
        'SELECT id, message FROM swiftmailer_spool ORDER BY id LIMIT 100'
    );

    foreach ($rows as $row) {
        try {
            $this->transport->send(unserialize($row['message']));
            $conn->delete('swiftmailer_spool', ['id' => $row['id']]);
        } catch (\Throwable $e) {
            $this->logger->error('spool send failed', ['id' => $row['id'], 'e' => $e]);
            $conn->update('swiftmailer_spool', ['attempts' => ...], ['id' => $row['id']]);
        }
    }
}

This is the most common reason people end up forking the bundle in the first place, so check the fork's spool class before assuming the stock behavior is safe.

Transactional mail inside a unit of work. Because the spool is a DB row, you can — and should — enqueue mail inside the same Doctrine transaction as the business write:

$em->transactional(function ($em) use ($mailer, $order) {
    $em->persist($order);
    $mailer->send($confirmationEmail);  // writes spool row in the same tx
});

If the transaction rolls back, the email is never queued. This is the single biggest argument for DB spooling over file spooling, and it works only if the spool's connection is the same entity manager — verify it is, and that white_october_swift_mailer_db.spool is wired to your doctrine.orm.default_entity_manager (not a separate DBAL connection to the same database).

Separate databases for app and queue. A refinement many teams use: configure the spool against a read/write connection to a different database than the business data. That way a mail storm cannot contend with OLTP queries for locks. This requires overriding the spool service definition (see extension points below).

Observability. Treat the spool table as a queue and instrument it:

  • Queue depth: SELECT COUNT(*) FROM swiftmailer_spool on a scrape interval, exported as a gauge.
  • Oldest message age: SELECT MIN(created_at) FROM swiftmailer_spool — alert if it exceeds your SLO.
  • Failure rate: count sends and exceptions in the flush command.

Bulk mail and notification fan-out. For newsletters, this bundle is a poor fit — you will be serializing thousands of full MIME messages (attachments included) into a table with no built-in batch semantics. Use it for transactional/one-off mail; use a proper bulk provider (SES, Mailgun batches) for marketing blasts.

Extension points worth knowing:

  • Override the bundle's spool service in services.yml to swap in your own Swift_Spool implementation (adds locking, retries, priority).
  • Override the entity to add attempts, priority, last_error, tenant_id columns. The bundle's entity is typically not final and has a configurable table name via parameters.
  • Add a Symfony event dispatcher hook (if the fork has one) to record metrics per message sent.

Gotchas and Tips

You are adopting an abandoned package with zero stars and zero dependents. There is no community to catch regressions, no security review, and no upstream fixes. Fork it into your own VCS, pin a commit hash, and treat it as your code. Do not expect upstream PRs to land.

Name and namespace will not match the Composer package. starstruck0207/swiftmailerdbbundle almost certainly still declares WhiteOctober\SwiftMailerDBBundle in its namespace and class names. If the fork changed the bundle class name but not the PSR-4 path (or vice versa), the bundle will fail to autoload with a confusing Class not found. Check composer.json autoload.psr-4 and the bundle class file before debugging anything else.

swiftmailer:spool:send deletes on read. Many spool implementations fetch a batch and delete it before/while sending. A mid-batch SMTP failure silently loses messages. If you need delivery guarantees, replace the flush command with your own (see Implementation Patterns) and make deletion conditional on transport success. This is a data-loss bug waiting to happen on the stock implementation.

Concurrency will double-send. Nothing in the default spool prevents two workers (two cron runs overlapping, or a manually run command alongside cron) from picking up the same rows. Mitigations, in order of preference:

  • Serialize flushing with a file lock (flock) or Symfony's Lock component.
  • Add FOR UPDATE SKIP LOCKED (MySQL 8+, Postgres) to your custom fetch query.
  • Run exactly one worker and let it fall behind rather than scale out.

Table growth is unbounded. Serialized MIME bodies with attachments can be hundreds of KB each. Add a retention policy: purge rows older than N days (a created_at index helps), and alert on table size. Consider storing only a pointer to a blob store if attachments are common.

The schema is your responsibility. There is no migration shipped with this bundle and no doctrine:migrations:diff integration out of the box. On a fresh environment your first $mailer->send() fails with a table-not-found error that looks like a configuration problem but is not. Make the table part of your deploy migrations.

Spool type vs. transport confusion. Setting swiftmailer.spool.type: service changes where messages are buffered, not how they are delivered. If mail never arrives, check in this order: (1) is the row being inserted? (2) does the flush command run? (3) does the transport config (swiftmailer.transport) reach the relay? All three must be true; a failure at any one is symptomatically "no mail."

--message-limit has an undocumented interaction with your relay's rate limits. Run the spool flush with a limit tuned to your SMTP provider's throughput (e.g. --message-limit=50 for a 1 msg/sec relay), or you will get throttled and then, per the delete-on-read issue, lose mail.

Disabling SwiftMailer's mailer in test. In config_test.yml, set the spool to memory so unit tests don't hit the DB at all:

swiftmailer:
    spool: { type: memory }

Otherwise tests silently write to a real queue and your CI suite sends real email when a flush runs.

Symfony 4+/5 incompatibility is likely. If the bundle's DependencyInjection extension uses ContainerBuilder APIs removed after Symfony 3.x, or the services.yml uses old-style service definitions without autowire/public support, you will get container compilation errors. Budget a half-day for a compatibility patch, and consider whether that effort is better spent migrating to symfony/mailer.

unserialize() on spool rows is a trust boundary. Anyone who can write to the spool table can put a PHP object payload in message. Restrict DB access accordingly, and if you rewrite the spool, prefer a serialization format that does not instantiate arbitrary classes.

Check whether the fork actually differs from upstream. With 0 stars and 0 dependents, this may be an unmodified mirror (someone's accidental publish) or a one-line patch. Diff it against whiteoctober/swiftmailer-db-bundle before assuming added value; if it is identical, you are better off depending on the original by name for at least some chance of finding documentation and Stack Overflow answers.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib