Using Mandrill’s Webhooks with Symfony
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.
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:
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.
Prerequisites
Adding a new Webhook URL in your Mandrill account
To add a new webhook using the Mandrill web application:
- Navigate to Settings in your Mandrill account
- Click Webhooks from the top menu
- Click + Add a Webhook at the top of the page
- Choose the events you want to listen for.
- Add your webhook URL under Post To URL (e.g. https://acme.com/mandrill/hooks)
- Give your webhook an option description under Description
- 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:
- Navigate to Settings in your Mandrill account.
- Click Webhooks from the top menu.
- 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
- The Mandrill Webhook documentation;
- The Event Dispatcher component;
- The MandrillBundle repository.