Using Mandrill’s Webhooks with Symfony

Quentin Ferrer
6 min readNov 10, 2020

Mandrill is a transactional email product designed to help applications or websites to send emails such as password reminders, account creations, callbacks, order notifications, etc.

Symfony provides Mailer and Swift Mailer components to help you send emails that support multiple email services including Mandrill.

What is a webhook?

A webhook is an event-driven system that calls the client when an event that interests them occurs. This is the opposite of polling, which consists of having the client continuously check if an event has occurred.

Webhooks VS Polling

Mandrill’s webhooks allow you to be notified when events occur on emails: send, bounced, soft-bounced, rejected, opened, delayed, unsubscribe, clicked and marked as spam.

In this case, instead of polling Mandrill every minute using a script to find out which events have been triggered on messages, you can just be notified directly by using webhooks.

How does webhook work?

A webhook is an HTTP callback. When something happens, the event is sent by using HTTP POST:

How a webhook works

Mandrill performs an HTTP callback to the public URL specified by a user. The body contains a JSON of webhook events:

{
"mandrill_events": [
{
"_id": "example",
"event": "send",
"msg": {
"sender": "john@acme.com",
"email": "info@acme.com",
"subject": "Password reset",
"state": "sent"
},
"ts": 1384954004
}
]
}

See the list of the keys included for message events on the official documentation.

Why using Mandrill’s webhooks?

Mandrill’s webhooks offer many possibilities. Here are some examples:

  • Reduce bounce rate: listen for bounces events, then remove invalid emails from your mailing lists.
  • Avoid spam complaint rate: listen for spam events, then delete the email that flagged your message as spam from your mailing lists.
  • Synchronize database in real-time: listen for send events, then mark your entity as sent in your database.
  • Log events of email: listen for one or more events, then log them to your preferred location by using a logger.

The MandrillBundle

The MandrillBundle provides services to handle Mandrill’s Webhooks:

  • a WebhookController controller to handle Mandrill's webhook requests;
  • a WebhookAuthentication service to verify the request to ensure that the data is coming from Mandrill and not a third-party pretending to be;
  • a WebhookHandler handler to let you perform a custom action when a message event occurs, by making use of the Event Dispatcher component;
  • a MessageEvents, the list of events to listen to suit the specific needs of your application;
  • a MessageEvent, an object that represents a message event data sent by Mandrill.
The Webhook flow

Prerequisites

Adding a new Webhook URL in your Mandrill account

To add a new webhook using the Mandrill web application:

  1. Navigate to Settings in your Mandrill account
  2. Click Webhooks from the top menu
  3. Click + Add a Webhook at the top of the page
  4. Choose the events you want to listen for.
  5. Add your webhook URL under Post To URL (e.g. https://acme.com/mandrill/hooks)
  6. Give your webhook an option description under Description
  7. Click Create Webhook

Installation

Downloading the bundle using composer

composer require qferr/mandrill-bundle

Note that the bundle supports Symfony 4/5 and PHP 7.2+.

Enable the bundle

Your bundle should be automatically enabled by Flex. In case you don’t use Flex, you’ll need to manually enable the bundle by adding the following line in the config/bundles.php file of your project:

<?php

return [
// ...
Qferrer\Symfony\MandrillBundle\QferrerMandrillBundle::class => ['all' => true],
];

Configuration

Configuring Environment Variables

Add your Mandrill Webhook key and URL inside the .env file:

# .env
MANDRILL_WEBHOOK_KEY=6LfoM_YUAAAAACd3tyP82gpjQRrjJar-AoHaCyVQ
MANDRILL_WEBHOOK_URL=https://acme.com/mandrill/hooks

The MANDRILL_WEBHOOK_KEY key is used to identify the webhook. You can retrieve or reset the key from your Webhooks page in your Mandrill account, in the Key column.

The MANDRILL_WEBHOOK_URL key is used to authenticate the request Mandrill. It’s the URL that you used to configure the webhook in Mandrill.

Configuring the bundle

By default, the authentication is enabled. Therefore, you need to specify the webhook key and webhook URL to verify the request signature:

# config/packages/mandrill.yaml
parameters:
mandrill_webhook_key: '%env(resolve:MANDRILL_WEBHOOK_KEY)%'
mandrill_webhook_url: '%env(resolve:MANDRILL_WEBHOOK_URL)%'
qferrer_mandrill:
webhooks:
key: "%mandrill_webhook_key%"
url: "%mandrill_webhook_key%"

This is not recommended but if you want to disable the authentication (e.g. in dev environment), set the authentication to false:

# config/packages/dev/mandrill.yaml
qferrer_mandrill:
webhooks:
auth: false

Mapping a URL to Webhook Controller

The WebhookController controller accepts Mandrill’s webhook request and returns the response to Mandrill.

# config/routes.yaml
mandrill_hooks:
path: /mandrill/hooks
controller: Qferrer\Symfony\MandrillBundle\Controller\WebhookController::handle
methods: POST|GET|HEAD

The controller accepts only POST requests and supports the Mandrill check they do to verify that the URL defined in the Mandrill app exists by using a HEAD request. Symfony silently transforms HEAD requests to GET.

Read more on the Mandrill documentation.

Usage

Creating a Listener

When a Mandrill’s webhook is triggered, it’s identified by a unique event name, which any number of listeners might be listening to. All events can be found in the constants of the MessageEvents class:

  • MessageEvents.SEND : The message is sent;
  • MessageEvents.DELAYED : The message is delayed;
  • MessageEvents.SOFT_BOUNCE : The message is soft-bounced;
  • MessageEvents.HARD_BOUNCE : The message is hard-bounced;
  • MessageEvents.SPAM : The message is marked as spam;
  • MessageEvents.REJECTED : The message is rejected;
  • MessageEvents.UNSUBSCRIBE : The message recipient unsubscribe;
  • MessageEvents.OPENED : The message is opened;
  • MessageEvents.CLICKED : The link in the message is clicked.

The following example shows an event subscriber that defines a method that listens to Mandrill’s events and logs the message event.

<?php

namespace App\EventListener;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Qferrer\Symfony\MandrillBundle\Event\MessageEvent;
use Qferrer\Symfony\MandrillBundle\MessageEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Class LoggerListener
*/
class LoggerListener implements EventSubscriberInterface
{
/**
*
@var LoggerInterface
*/
private $logger;

/**
* LoggerListener constructor.
*
*
@param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

/**
*
@inheritDoc
*/
public static function getSubscribedEvents()
{
return [
MessageEvents::SEND => 'log',
MessageEvents::REJECTED => 'log',
MessageEvents::HARD_BOUNCE => 'log',
MessageEvents::SOFT_BOUNCE => 'log',
MessageEvents::SPAM => 'log',
];
}

/**
* Log the event of an email
*
*
@param MessageEvent $event
*/
public function log(MessageEvent $event)
{
$message = $event->getMessage();

switch ($event->getName()) {
case MessageEvents::REJECTED:
case MessageEvents::HARD_BOUNCE:
$level = LogLevel::ERROR;
break;
case MessageEvents::SOFT_BOUNCE:
case MessageEvents::SPAM:
$level = LogLevel::WARNING;
break;
default:
$level = LogLevel::INFO;
}

$this->logger->log(
$level,
sprintf('Message "%s" has been handled.', $message['_id']),
$message
);
}
}

Testing and Debugging

Using Mandrill

Mandrill has built-in testing tools to send test events to any webhook URL. To test a webhook using the Mandrill web application:

  1. Navigate to Settings in your Mandrill account.
  2. Click Webhooks from the top menu.
  3. Click the send test button to send a batch of events to your webhook URL.

Your webhook URL must be public to allow Mandrill to send you test events. You should use third-party tools such as ngrok to create a secure public URL to a local server on your machine.

Using cURL

First, you need to disable the authentication in dev environment:

# config/packages/dev/mandrill.yaml
qferrer_mandrill:
webhooks:
auth: false

Then, execute the following command example to send a test event to your webhook URL:

curl --location --request POST 'localhost/mandrill/hooks' \
--form 'mandrill_events=[{"event": "send", "msg": {"_id": "testSend", "to": "john@acme.com", "status": "sent"}, "_id": "testSend", "ts": 1384954004}]'

Resources

--

--

Quentin Ferrer

I’m a french web developer. I develop in PHP with the popular Symfony framework.