Skip to content
Advertisement

SwiftMailer silently ignores errors when messages fail delivery

When run from inside controller and when in-memory spooling is configured via spool: { type: memory } swiftmailer seems to work like this:

  • whenever from within controller mailer->send($message) is called -> save message in memory
  • when controller has finished work and symfony Kernel is about to shutdown (somwehere in event kernel.terminate or smth) – check messages saved in memory and _actually submit them to SMTP-server

However this last step seems to silently ignore any errors which may be thrown when peforming submitting of the message to the SMTP server.

I discovered that errors are silently swallowed when I was setting up SMTP from Amazon SES, and have made wrong configuration:

mailer_transport: smtp
# For illustration I put WRONG port 9999, which means that this should trigger
# error (correct port would be 587)
mailer_port: 9999
mailer_encryption: tls
mailer_host: email-smtp.us-east-1.amazonaws.com
mailer_user: SES_USER_KEY
mailer_password: SES_USER_SECRET 

Now, if I attempt to send email using wrong configuration from a symfony Command, just as expected I get Swift_TransportException and error is NOT silently ignored. (From my observations it seems that symfony commands do NOT use memory-spooling and attempt to send messages immediately)

Below is sample of the command (so you’re sure I am doing it right).

protected function execute(InputInterface $input, OutputInterface $output) {
     $email = $input->getArgument('email');
     $content = $this->getHelper('dialog')->ask($output, 'Please input content:');

     $emsg = Swift_Message::newInstance();
     $emsg->setTo($email);
     $emsg->setFrom('d@my-ses-verified-domain.com');
     $emsg->setSubject('This is subject');
     $emsg->setBody($content);

     $this->getContainer()->get('mailer')->send($emsg);
}

And here’s command output when the exception Swift_TransportException is thrown:

ubuntu@localhost:~/my-app$ console acme:email:send existing@email.com
We are going to send email to :existing@email.com

Please input content:asdf adf


[Swift_TransportException]                                                                                    
Connection could not be established with host email-smtp.us-east-1.amazonaws.com [Connection timed out #110]  

Hovewer if I attempt to send email from controller, then I see no error messages. Basically this means that in case there’s an error (misconfiguration or network error or SMTP server down), all the emails I sent will silently disappear without any trace (no exception thrown, no error loggged in dev.log neither in prod.log).

How can I force Swiftmailer to leave trace of failed delivery attempt?

Advertisement

Answer

You might be able to force the delivery of the emails by flushing the spooler manually, which should then allow you to catch the exception and log as needed. I found an example in the docs here (modified for the context of a controller)…

https://symfony.com/doc/2.6/cookbook/console/sending_emails.html

$message = new Swift_Message();

// ... prepare the message

$mailer = $this->get('mailer');

$mailer->send($message);

// now manually flush the queue
$spool = $mailer->getTransport()->getSpool();
$transport = $this->get('swiftmailer.transport.real');

$spool->flushQueue($transport);

While the example was originally for use in the console environment, I see no reason why it wouldn’t be valid in a controller.

Edit:

Another way to log the exception would be to utilize the Swiftmailer event system. This involves creating a custom plugin (as a service would be best) that implements Swift_Events_TransportExceptionListener, then registering it with the mailer.

For example, the custom plugin class:

namespace AcmeDemoBundleSwiftPlugin;

use SymfonyBridgeMonologLogger;

class SwiftExceptionLoggerPlugin implements Swift_Events_TransportExceptionListener
{
    private $logger;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    /**
     * Invoked as a TransportException is thrown in the Transport system.
     *
     * @param Swift_Events_TransportExceptionEvent $evt
     */
    public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt)
    {
        $e = $evt->getException();
        $message = $e->getMessage();
        $this->logger->err(sprintf("Swiftmailer Exception: %s", $message));
    }
}

Then add this as a service along with the swiftmailer.default.plugin tag. That will automatically register it as a Swiftmailer plugin…

<service id="acme.demo.swift_plugin.swift_exception_logger_plugin" class="AcmeDemoBundleSwiftPluginSwiftExceptionLoggerPlugin">
    <tag name="swiftmailer.default.plugin" />
    <argument type="service" id="logger" />
</service>

This will log the exception message in the standard log area for dev/prod. However, if the timeout takes very long to occur I think it still may not log correctly if the user clicks away or closes their browser/tab, etc. Perhaps the above along with setting a lower timeout value for Swiftmailer via the mailer_timeout parameter.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement