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
- Run
php bin/console debug:config framework mailerin the same environment and release that sends the message. - Inspect
mailer.transports,mailer.default_transport, and theTransportInterfacealias withdebug:container. Confirm the expected service graph; do not publish expanded credentials. - 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. - 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.
- Look for
null://null, an environment-specific delivery disablement,framework.mailer.enveloperecipient 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
SendEmailMessageto 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.
- Capture the application correlation ID and message type without storing the recipient, reset link, token, or body in analytics.
- For a synchronous send, retain the
SentMessageID and bounded transport debug output. For an asynchronous send, link the application event to the Messenger envelope and worker attempt. - Find the matching provider event: accepted, delivered, delayed, bounced, rejected, suppressed, or complained.
- Preserve the recipient server's SMTP response or asynchronous DSN when available.
- 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
- Symfony Mailer documentation: transport setup, debugging, Messenger, and test command
- Symfony 7.4.14 FrameworkBundle mailer service definitions
- Symfony 7.4.14 DSN-to-transport configuration
- Symfony 7.4.14
Transportsdefault selection and send forwarding - Public symptom report and proposed alias explanation