@Mail Domain Check

Symfony 7.4 | Mailer | Messenger | SMTP

Symfony Mailer Uses mailer.transports but No Email Arrives

In Symfony 7.4.14, the plural mailer.transports service and the TransportInterface alias are intentional. They do not, by themselves, prove that Symfony ignored MAILER_DSN. Preserve the framework wiring and find the first handoff that lacks evidence.

Source verdict: the plural service is deliberate

Symfony FrameworkBundle 7.4.14 defines mailer.transports as a Transports object, aliases mailer.default_transport to it, and aliases Symfony\Component\Mailer\Transport\TransportInterface to that default transport. There is no corresponding framework service named mailer.transport in this configuration.

The extension converts a single configured dsn into a named map:

$transports = $config['dsn']
    ? ['main' => $config['dsn']]
    : $config['transports'];

The Transports class chooses the first configured transport as its default and forwards send() to it. If the collection is actually empty, its constructor throws a LogicException; it does not silently accept a message into a zero-transport no-op.

Changing the alias to a guessed singular service or constructing Transport::fromDsn() inside application code can hide the real fault and bypass framework features. Do that only for a controlled isolation test, not as the first production fix.

Prove the effective configuration

  1. Run php bin/console debug:config framework mailer in the same environment and release that sends the message.
  2. Inspect mailer.transports, mailer.default_transport, and the TransportInterface alias with debug:container. Confirm the expected service graph; do not publish expanded credentials.
  3. Check which environment wins: deployed process variables, .env.local, environment-specific config, container secrets, and cached production configuration can differ from the shell where a developer tested.
  4. Clear and warm the intended environment cache after a configuration change. Record the release ID and a non-secret fingerprint of the expected DSN components rather than logging the password.
  5. Look for null://null, an environment-specific delivery disablement, framework.mailer.envelope recipient overrides, and custom service definitions that replace framework aliases.

Use the same PHP runtime, APP_ENV, release directory, and secret injection path as the failing application. A correct local debug:config result does not prove the production worker has the same configuration.

Separate synchronous transport from Messenger

MailerInterface
May dispatch SendEmailMessage to Messenger when a message bus and routing are configured. Returning from application code can mean the job was queued, not that SMTP ran.
TransportInterface
Sends synchronously and returns SentMessage. Symfony documents this as a debugging path because it exposes transport debug information.
mailer:test
Sends through the configured mailer transport while bypassing Messenger, which isolates the queue boundary from the transport boundary.
APP_ENV=prod php bin/console mailer:test <consented-test-recipient>

APP_ENV=prod php bin/console messenger:stats
APP_ENV=prod php bin/console messenger:failed:show

If mailer:test reaches the provider but the application path does not, inspect Messenger routing, the running consumer, failed-message storage, retry policy, and worker release. If neither path reaches the provider, stay at effective configuration, connection, TLS, authentication, or sender acceptance.

Define what "sent" proves

Symfony's documentation draws a hard boundary: the framework considers a synchronous send successful when the configured transport accepts the message for further delivery. That does not prove the final recipient server accepted it, and it does not prove inbox placement.

  1. Capture the application correlation ID and message type without storing the recipient, reset link, token, or body in analytics.
  2. For a synchronous send, retain the SentMessage ID and bounded transport debug output. For an asynchronous send, link the application event to the Messenger envelope and worker attempt.
  3. Find the matching provider event: accepted, delivered, delayed, bounced, rejected, suppressed, or complained.
  4. Preserve the recipient server's SMTP response or asynchronous DSN when available.
  5. Record inbox visibility separately from server acceptance.

No provider event means the fault is still between the application, queue, and configured transport. A provider acceptance event followed by a bounce belongs to the SMTP or recipient boundary. A delivered event with no visible message belongs to mailbox filtering or presentation, not Symfony's service alias.

What the public report establishes

Symfony issue 64924 reports version 7.4.14, a configured SMTP DSN, no exception, and no received email. Those observations establish a useful symptom but do not establish that mailer.transports contains zero transports. The exact 7.4.14 source contradicts that proposed mechanism. Reproduction still needs the effective container, sync or async path, provider event, and recipient-server evidence.

This page verifies framework behavior, not the reporter's private deployment. A different cause may still exist in that environment.

Primary references