SMTP клиент на Node.js — net и tls
Свой SMTP-клиент на чистых сокетах: TCP/TLS как взаимозаменяемая стратегия, конечный автомат поверх диалога с сервером.
Что это
SMTP (Simple Mail Transfer Protocol) — текстовый line-based протокол поверх TCP. Сервер шлёт коды (220, 250, 354), клиент отвечает командами (HELO, MAIL FROM, RCPT TO, DATA).
В ноде SMTP-клиент пишется на двух модулях:
net— обычный TCP (порт 25, plain SMTP).tls— для STARTTLS (587) и SMTPS (465).
API / Пример
const transports = {
tcp: require('net'),
tls: require('tls'),
};
class SmtpClient {
constructor({ host, port, secure }) {
this.host = host;
this.port = port;
this.transport = secure ? transports.tls : transports.tcp;
this.socket = null;
this.currentCommand = null;
}
connect {
return new Promise((resolve, reject) => {
this.socket = this.transport.connect(this.port, this.host);
this.socket.once('connect', resolve);
this.socket.once('error', reject);
this.socket.on('data', (chunk) => this.processChunk(chunk));
});
}
processChunk(chunk) { /* парсит коды ответа, диспатчит */ }
}
Принципы рефакторинга (по автору)
- TCP и TLS — стратегии в коллекции — общий интерфейс сокета.
- События не методами класса —
onDataподписывается вconfigureSocket, там же отписывается. - Promise не в свойствах — блокировка (
AsyncLock) — отдельная ответственность в utils. whileне нужен, достаточноifдля следующей команды.removeAllListenersпо имени события — снять подписки при upgrade сокета на TLS (STARTTLS).destroyчерезawait, не emit изнутри — иначе порядок completion ломается.- 30% кода — бизнес-логика, остальное — инфраструктура.
Производительность / Подводные камни
- STARTTLS — апгрейд plain сокета до TLS на лету (
tls.connect({ socket: plainSocket, ... })). Перед апгрейдом снять все слушатели data. - CRLF — SMTP использует
\r\n(как HTTP). - Многострочные ответы —
250-First line\r\n250 Last line\r\n(тире vs пробел). - Hostname вычислять один раз в конструкторе, не на каждое сообщение.
currentCommand— глобальный стейт — выносить в очередь команд, чтобы можно было параллелить (с pipelining).
🎓 Источники
- 🎓 Летняя школа 2022 созвон #13 — ревью SMTP (tcp и tls) · 2022-08-10
- Тезисы: коллекция стратегий tcp/tls с одинаковым интерфейсом; promise в свойствах — антипаттерн; события не методами класса; подписался → там и отпишись; rev STARTTLS — снять все listeners; ООП плохо подходит к асинхронности.
- Цитата: «События — не методы. Все callback должны быть внутри configure socket: где подписался, там и отписывайся на том же методе.»