Есть два способа настроить вебхуки в Mailgun. Это можно сделать либо через панель инструментов Mailgun, либо через API. Более простой способ сделать это через панель инструментов. После входа в панель управления на панели навигации появится ссылка Веб-перехватчики.

На странице веб-перехватчиков перечислены различные типы событий, для которых вы можете получать данные о событиях. Нажимая кнопку + перед каждым событием, вы можете указать URL-адрес, на который будут отправляться данные события.

Обработка данных

Чтобы обрабатывать данные о событиях, отправленные на наш URL-адрес веб-перехватчика, мы должны знать, как эти данные выглядят в первую очередь. Параметры, отправляемые по POST, доступны в Документации по API. Мы можем пойти еще дальше и подтвердить это, используя тестовый URL-адрес веб-перехватчика, который будет регистрировать данные из Mailgun. Мы можем использовать Mailgun’s Postbin или requestb.in. Эти службы будут генерировать уникальную конечную точку, которую мы можем использовать на панели инструментов Mailgun для получения образцов данных о событиях. Я рекомендую requestbin, потому что он предоставляет больше деталей, таких как заголовки запроса. Эти заголовки важны, потому что вы легко упустите тот факт, что Mailgun отправляет некоторые данные, используя тип контента application/x-www-form-urlencoded, а некоторые — multipart/form-data. Отсутствие этой маленькой детали меняет все, что касается того, как вы получаете данные о событиях.

Давайте продолжим, создадим тестовую конечную точку и посмотрим, как выглядят данные события по сравнению с тем, что есть в документации.

  • Посетите requestb.in и создайте корзину.
  • Скопируйте URL-адрес и получите доступ к разделу Webhooks на панели инструментов Mailgun.
  • Вставьте URL-адрес в поле ввода и нажмите ссылку Проверить веб-перехватчик. Это отправит образец данных о событии на URL-адрес.
  • Повторите это для всех интересующих вас событий.
  • Обновите страницу requestbin, чтобы просмотреть отправленные данные события.

Если вы внимательно посмотрите на данные requestbin, вы заметите, что я сказал о некоторых данных, отправляемых как multipart/form-data.

Теперь, когда мы знаем параметры для каждого типа событий и тип содержимого, в котором они могут поступать, легко написать код, который будет обрабатывать отправленные данные. Вот простой код, который выводит сведения об жалобах и отброшенных электронных письмах. (Я использую multer для обработки multipart/form-data)

const express = require('express')
    , bodyParser = require('body-parser')
    , multer = require('multer')
    ;
const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.listen(process.env.PORT || 3000);
app.post('/webhook', multer().none(), function(req, res) {
  const email = req.body.recipient;
  const event = req.body.event;
  if (event == 'complained') {
    console.log(`${email} complained about your mail`);
  }
  else if (event == 'dropped') {
    console.log(`Mail to ${email} dropped. ${event.description}`);
  }
  else if (event == 'bounced') {
    console.log(`Error ${event.code}: Mail to ${email} bounced. ${event.error}`);
  }
  res.end();
});

Обеспечение безопасности

Ничто не мешает любому, кто знает URL-адрес нашего веб-перехватчика, создать ложные данные о событии и отправить их по URL-адресу. К счастью, Mailgun подписывает каждый отправленный запрос и публикует следующие параметры:

  • отметка времени (количество секунд, прошедших с 1 января 1970 г.)
  • токен (Случайно сгенерированная строка длиной 50)
  • подпись (шестнадцатеричная строка, сгенерированная алгоритмом HMAC)

Верифицировать токен;

  • Объедините значения timestamp и token.
  • Закодируйте полученную строку с помощью HMAC, используя ключ API Mailgun в качестве ключа и Sha256 в качестве алгоритма.
  • Результат должен совпадать с подписью.

Вот как это выглядит в Node.js:

const value = event_data_timestamp+event_data_token;
const hash = crypto.createHmac('sha256', apikey)
                   .update(value)
                   .digest('hex');
if (hash !== event_data_signature) {
  console.log('Invalid signature');
  return;
}

Если мы добавим это к нашему исходному примеру кода, у нас будет что-то вроде этого:

const express = require('express')
    , crypto = require('crypto')
    , multer = require('multer')
    , bodyParser = require('body-parser')
    ;
const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.listen(process.env.PORT || 3000);
app.get('/webhook', multer().none(), function(req, res) {
  // Validate signature
  const value = req.body.timestamp+req.body.token;
  const hash = crypto.createHmac('sha256', 
                            process.env.API_KEY)
                   .update(value)
                   .digest('hex');
  if (hash !== req.body.signature) {
    console.log('Invalid signature');
    return res.end();
  }
  // Log status of event
  const email = req.body.recipient;
  const event = req.body.event;
  if (event == 'complained') {
    console.log(`${email} complained about your mail`);
  }
  else if (event == 'dropped') {
    console.log(`Mail to ${email} dropped. ${event.description}`);
  }
  else if (event == 'bounced') {
    console.log(`Error ${event.code}: Mail to ${email} bounced. ${event.error}`);
  }
  res.end();
});

Мы можем даже увеличить это и:

  1. Для каждого запроса сверяйтесь с кэшем токенов, чтобы предотвратить использование одного и того же токена. Каждый токен будет храниться там. Это предотвратит повторные атаки.
  2. Убедитесь, что отметка времени не слишком далека от текущего времени.

Масштабируемость

Если вы отправляете много писем и ожидаете много событий, размещать скрипт веб-перехватчика на сервере, который не может масштабироваться автоматически, — плохая идея. Даже если вы не ожидаете большого количества событий, неожиданные вещи могут привести к всплеску событий. Наличие сервера, который может автоматически масштабироваться, действительно полезно для таких случаев.

Введите Бессерверные вычисления. Проще говоря, идея состоит в том, что вы можете делегировать выполнение своего кода и все, что связано с провайдером. Поскольку несколько экземпляров вашего кода могут выполняться параллельно, и вы можете на лету настраивать вычислительные ресурсы, такие как ОЗУ и время выполнения, он обладает высокой масштабируемостью. Вы также платите за потребляемые ресурсы и время выполнения, поэтому это может быть очень дешево.

Есть несколько провайдеров бессерверных вычислений. Я использую и рекомендую Облачные функции Google из-за простоты настройки HTTP-функций. Функция HTTP — это блок кода, обернутый как функция, которая может быть запущена при посещении URL-адреса. Это именно то, что нам нужно в качестве нашего вебхука.

Чтобы создать эту функцию, нам нужно написать функцию JavaScript, которая будет экспортирована как модуль Node.js. Функция принимает специфичные для HTTP аргументы: request и response.

exports.webhook = function(request, response) {
  // Handle event data here
  response.send({status:"ok"});
}

На основе запроса content-type тело запроса автоматически передается и доступно в параметре body объекта запроса.

exports.webhook = function(request, response) {
  let event = request.body.event; // delivered
  // Handle event data here
  // ...
  response.send({status:"ok"});
}

Однако это не работает для типа контента multipart/form-data. И, как мы уже знаем, Mailgun отправляет некоторые данные как multipart/form-data. Мы можем подключить такую ​​библиотеку, как Multer, используя require().

const multer = require('multer');
exports.webhook = function(request, response) {
    parser(request, response, function(){
    console.log(request.body); // Our event data
    // Handle event data here
    // ...
    response.send({status:"ok"});
    });
}

Однако нам нужно убедиться, что зависимость указана в файле package.json.

{
  "dependencies": {
    "multer": "^1.3.0"
  }
}

Далее мы можем опубликовать функцию в Cloud Functions. Самый простой способ сделать это — сделать это с панели инструментов Cloud Functions.

  • Зайдите в свою Google Cloud Console (если у вас еще нет учетной записи, создайте ее).
  • Включите облачные функции на панели инструментов.
  • Нажмите Создать функцию.
  • Введите имя для своей функции (например, mailgun-webhook).
  • В разделе триггера выберите Триггер HTTP. Обратите внимание на URL-адрес, который будет вашим URL-адресом веб-перехватчика.
  • Скопируйте код обработки данных событий в раздел index.js функции Cloud.
  • Скопируйте содержимое файла package.json и вставьте в раздел package.json.
  • Выберите или создайте сегмент Stage. Стадийное ведро — это просто место, где размещается код. Здесь можно использовать что угодно.
  • В поле «Выполняемая функция» введите имя вашей функции (например, webhook).
  • Сохранять.

Теперь вы можете использовать URL-адрес функции в Mailgun в качестве URL-адреса веб-перехватчика.

Вывод

Работать с вебхуками Mailgun легко. Существует множество способов использования данных о событиях для обогащения ваших приложений за пределами Mailgun. Если, например, вы разрешаете своим пользователям отправлять электронные письма из вашего Интернета по какой-либо причине, и вы используете Mailgun, вы можете использовать это для предоставления им аналитики. Или, может быть, вы хотите отправить свою электронную аналитику на другую платформу. Или, может быть, вы хотите получать уведомления о сбоях в вашей учетной записи Slack. А может даже и не это. Возможно, вам просто нужна более подробная аналитика, чем та, что доступна на панели инструментов Mailgun. Каким бы ни был вариант использования, данные о событии доступны для вас.

В качестве примера из реальной жизни посмотрите источник файла веб-хука Suet.