Почтовая инфраструктура
OutlookID
Самостоятельно размещаемый email-сервис, позволяющий одному приложению читать, отправлять и систематизировать почту в множестве почтовых ящиков Outlook через Microsoft Graph, отправляя подписанный вебхук на каждое новое письмо. Создан на Fastify, Drizzle ORM, PostgreSQL и очереди pg-boss.
Обзор
Подключение приложения ко многим почтовым ящикам Outlook пользователей означает обработку OAuth для каждого пользователя, ротацию токенов, обновление подписок Microsoft Graph и надёжную доставку каждого нового письма как события. OutlookID берёт на себя всю эту «сантехнику», чтобы команде продукта не пришлось — они подключаются один раз, и с тех пор получают подписанный вебхук на каждое новое сообщение.
Что я сделал
Спроектировал и построил всю систему с нуля — OAuth-потоки, шифрование токенов, жизненный цикл подписок Microsoft Graph, конвейер доставки вебхуков, схема базы данных и развёртывание Docker.
Написал интеграционные тесты против реальных временных экземпляров Postgres с помощью Testcontainers и модульные тесты для всей криптографической и токенной логики.
Написал README, спецификацию OpenAPI и руководство по развёртыванию — чтобы другая команда могла взять это и запустить без необходимости читать исходный код.
Архитектура
Нижестоящее приложение вызывает единственный эндпоинт для запуска OAuth-потока для пользователя. OutlookID обрабатывает перенаправление, обменивает код авторизации, шифрует токены и немедленно настраивает подписку Microsoft Graph на этот почтовый ящик. С тех пор Graph отправляет уведомления об изменениях в OutlookID, который их проверяет и ставит в очередь задание доставки. Система задач повторяет попытки с экспоненциальной задержкой до 6 попыток перед переходом в очередь dead-letter, которую операторы могут воспроизвести.
Под капотом
OAuth-токены шифруются в покое с помощью AES-256-GCM, используя случайный вектор инициализации для каждого токена — так что даже если кто-то вытащит базу данных, он получит шифртекст, а не рабочие учётные данные.
Параллельное обновление токена объединяется: если два запроса попадают в один почтовый ящик при истёкшем токене, они используют один цикл обновления вместо того, чтобы гоняться и аннулировать друг друга. Это предотвращает трудно отлаживаемую ошибку двойного обновления, которая появляется в мультитенантных системах.
Исходящие вебхуки подписываются заголовком HMAC в стиле Stripe — нижестоящее приложение может проверить, что каждая доставка подлинная и не была подделана, а экспоненциальная задержка с управлением dead-letter означает, что ни одно событие не теряется навсегда.
Библиотека MSAL от Microsoft не раскрывает refresh-токены в стандартном результате — поэтому мне пришлось десериализовать её внутренний кеш токенов для извлечения секрета по идентификатору аккаунта. Необходимый обходной путь, хорошо протестированный и задокументированный.
Чему я научился
Инфраструктурные библиотеки (вроде MSAL) имеют недокументированное поведение, которое проявляется только в крайних случаях — чтение исходников было единственным способом понять, что происходит с кешированием токенов.
Создание очереди dead-letter и эндпоинтов администратора для повторных попыток с самого начала — вместо их добавления после реального сбоя — было правильным решением. Наблюдаемость нужна до того, как она понадобится.
Технологии
Бэкенд
- Node.js 20+
- TypeScript 5
- Fastify 4
- Zod
- Pino
Данные и очередь
- PostgreSQL 14+
- Drizzle ORM
- drizzle-kit
- pg-boss 9
Интеграции
- @azure/msal-node 2
- @microsoft/microsoft-graph-client 3
- Google OAuth2
- Gmail API
- Node.js crypto
Инфраструктура и тестирование
- Docker
- Caddy
- Vitest
- Testcontainers
- nock
- pnpm