Почтовая инфраструктура

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