Приймаємо на електронну пошту Node.js

Електронна пошта як і www з'явилася на зорі Інтернету, і незважаючи на свою архаїчність продовжує утримувати позиції однієї з головних технологій мережі. Тим часом розробники не дуже-то її цінують і використовують в односторонньому порядку, вказуючи відправником noreply. І в першу чергу це пов'язано з трудомісткістю процесу обробки вхідної кореспонденції.
Тим часом, хвала ком'юніті Node.js з'явилися пакети, які дозволяють приймати пошту без болю і страждання – це smtp server і mailparser. Давайте я покажу, як в пару десятків рядків коду створити свій поштовий сервер з підтримкою SSL шифрування, фільтрацією спаму з допомогу spamassassin і іншими радощами.
Отримання листів
За отримання листів відповідає модуль smtp server. В його роботі немає нічого складного, єдине, що може змусити вас витратити кілька годин часу – це налаштування SSL, яка зроблена не надто очевидною (пізніше я розповім про це).
const fs = require('fs');
const {SMTPServer} = require('smtp server');

const smtp = new SMTPServer({
secure: false,
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem'),
onRcptTo,
onData,
});

// Валідація одержувача. Для кожної адреси функція викликається окремо.
function onRcptTo({address}, session, callback) {
if (address.starts('noreply@')) {
callback(new Error(`Address ${address} is not allowed receiver`));
}
else {
callback();
}
}

// Обробка даних листа
function onData(stream, session, callback) {
// Stream – Потік з даними листи. Callback викликається по закінченню парсингу.
// у цьому процесорі ми будемо парсити лист.
callback();
}

Налаштування
Взагалі налаштувань дещо більше, але я опишу основні, які знадобляться нам для реалізації невеликого сервера.

secure

Справа в тому що шифрування може використовуватись двома способами: при встановленні підключення (
secure: true
) або з переключенням на потік зашифрований за допомогою заголовка STARTTLS (
secure: false
). Якщо ви слухаєте 25-й порт, вкажіть
false
, 587-й (465-й) –
true
. Що б визначитися з портом раджу прочитати статтю mailgun про історію портів закріплених за поштовими протоколами.

key, cert

Ключ і сертифікат SSL. За замовчуванням smtp server використовує власний самопідписаний сертифікат, але я б не радив його використовувати, коли є let's Encrypt.

onRcptTo

Якщо в методі onRcptTo не був схвалений жоден адреса – onData вызыван не буде.
Для кожного листа буде генерирован звіт на стороні відправника. Яндекс генерує ось це:
This is the mail system at host yandex.ru.

I'm sorry to have to inform you that your message could not
be delivered to one or more recipients. It's attached below.

Please, do not reply to this message.

<noreply@hm.rumk.in>: host hm.rumk.in[159.203.137.17] said: 550 Mailbox
noreply@hm.rumk.in could not receive messages (in reply to RCPT TO command)

onMailFrom

Ця настройка дозволяє призначити обробник для адреси відправника для фільтрації по відправникам.

onData

Тут все просто, головне-викликати callback, щоб уникнути витоку пам'яті.

logger

Може бути
true
чи примірником логер підтримує інтерфейс bunyan.
Парсинг
З парсингом все простіше. Необхідно підключити парсер для листів
mailparser
:
const {MailParser} = require('mailparser');

і доопрацювати функцію
onData
:
function onData(stream, session, callback) {
const parser = new MailParser();
stream.pipe(parser);
parser.on('error', callback);
parser.on('end', (mail) => {
// Process mail body...
callback();
});
}

В результаті парсинга ви отримаєте об'єкт ось такого вигляду:
{
"html": "<div>Hi this is a test message. Notify me if you get it</div>\n",
"headers": {
"received": [
"from mxback5g.mail.yandex.net (mxback5g.mail.yandex.net [77.88.29.166]) by forward17p.cmail.yandex.net (Yandex) with ESMTP id 372CD212FE for <c28ec25d@hm.rumk.in>; Sat, 5 Nov 2016 06:22:23 +0300 (MSK)",
"from web20g.yandex.ru (web20g.yandex.ru [95.108.253.229]) by mxback5g.mail.yandex.net (nwsmtp/Yandex) with ESMTP id j2CjR0Q3Ek-MN2SfLo3; Sat, 05 Nov 2016 06:22:23 +0300",
"by web20g.yandex.ru with HTTP; Sat, 05 Nov 2016 06:22:23 +0300"
],
"з": "Some User <user@host>",
"to": "c28ec25d@hm.rumk.in",
"subject": "asdasd a",
"mime-version": "1.0",
"message-id": "<7119991478316143@web20g.yandex.ru>",
"x-mailer": "Yamail [ http://yandex.ru ] 5.0",
"date": "Sat, 05 Nov 2016 06:22:23 +0300",
"content-transfer-encoding": "7bit",
"content-type": "text/html"
},
"subject": "Test message",
"messageId": "7119991478316143@web20g.yandex.ru",
"priority": "normal",
"з": [
{
"address": "user@host",
"name": "Some User"
}
],
"to": [
{
"address": "c28ec25d@hm.rumk.in",
"name": ""
}
],
"date": "2016-11-05T03:22:23.000 Z",
"receivedDate": "2016-11-05T03:22:23.000 Z"
}

Так само ми можемо підключити модуль spamassassin для підрахунку індексу "спамовости"
spamScore
. Для цього знадобиться встановити spamassassin і модуль spamc-stream. Використовувати так само легко як і mailparser.
Для цього знадобиться встановити і запустити spamassassin:
# Debian/Ubuntu
$ sudo apt-get install spamassassin
# Fedora/CentOS
$ sudo yum install spamassassin

Spamassassin містить набір правил кожне з яких застосовується до листа, та, якщо правило спрацювало, то індекс збільшується. Коли індекс перевищує допустиме значення (зазвичай 5), лист визнається спамом. Так наприклад, індекс збільшиться, якщо лист містить тільки html-версію без текстової. Spamassassin це сервер, який направляється лист для аналізу. Smapc – клієнт для smapassassin. Ми будемо переписувати лист спочатку в spamassassin, а потім в парсер.
const SpamcStream = require('spamc-stream');
const spamc = new SpamcStream(); // Примірник клієнта

onData(stream, session, callback) {
const reporter = spamc.report();
let report;
const parser = new MailParser();

stream.pipe(reporter).pipe(mailparser);

reporter.on('report', (result) => {
report = result;
});

parser.on('end', (mail) => {
if (report.isSpam) {
// Save mail into spam directory 
}
else {
// Process mail body...
}
callback();
});

reporter.on('error', callback);
parser.on('error', callback);
}

Так само слід зазначити, що парсер листів вміє створювати потоки з атачментів, що дозволяє зручно і ефективно передавати їх у сховища BLOB' ів, ну або просто писати на диск.
Примітка
Якщо ви вирішите приймати пошту від необмеженого числа відправників, вам знадобиться реалізувати підтримку перевірки SPF і, бажано, DKIM. Але це матеріал для окремої статті.
Приклад
Подивитися, як це працює ви можете на тестовій сторінці. Надіславши лист на тимчасовий e-mail, ви побачите JSON-структуру готову для подальшої обробки. Повідомлення доставляються в реальному часі за WebSocket. Исходники самого прикладу викладені в репозиторії rumkin/hypemail.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.