Перейти до вмісту

Email service (email-octopus)

ПолеЗначення
ПродуктAbitly
Типworker / email-API
Статус🟢 prod (Lambda живий; lifecycle-ланцюжок кошика живий з боку бекенда — 179 CHECKOUT#-станів у DynamoDB і ростуть, нагадування шлються; звірено 18.07)
Власник@Vladbandurin

Email-платформа Abitly: broadcast-кампанії as-code через власний SES-движок, welcome-серія новим реєстраціям, lifecycle-нагадування покинутого кошика (2 листи: +2 год / +12 год, персональні resume-лінки оплати), транзакційні листи, сегментація всієї бази з prod-БД. Сервіс stateless — власної Postgres-БД немає; стан (send-ledger, suppression, чекаути, оплати) у DynamoDB abitly-email-state, розклад у EventBridge Scheduler. Аналітика — PostHog (проєкт 221580): email_sent/delivered/opened/clicked, A/B тем.

Рішення 16–17.07.2026 (Б3, варіант c): SES-only + власна сегментація. EmailOctopus більше не використовується для розсилок і автоматизацій: welcome-автоматизацію EO зупинено 16.07 (замінена власною SES-серією), кампанії й suppression живуть у своєму движку, міграції лістів в EO не буде. EO-клієнт у коді лишається тільки як legacy-шлях upsert-у контактів.

Не плутати з Marketing (blueprint) — то запланований сервіс AI-персоналізованих кампаній (email — цикл 2027); email-service — жива інфраструктура сьогоднішніх листів. Загальна мапа каналу → Email-маркетинг.

Фази 1–7 — з CLAUDE.md §10 репо сервісу (джерело правди); дельта 16–18.07 — робота циклу вступної кампанії 2026.

ФазаЩоСтан
1–4Core · routes/services · templates+SES · deploy (Lambda + Terraform)
5Campaign automation: конвенція campaigns/<slug>/, движок на SES, інфра deliverability (DynamoDB, SNS-вебхуки, unsubscribe RFC 8058, S3+CloudFront)
6Lifecycle (кошик) + PostHog A/B✅ · каденс змінено на +2 год / +12 год (було +1/+24/+72)
7Go-live + орг-інфра▶ у процесі — docs/implementation-plan.md у репо

Дельта 16–18.07 (цикл кампаній 2026):

ЩоСтан
Scheduled-broadcast движок (prepare → human launch → worker у Lambda)🟢 prod із 17.07
Welcome-серія на SES замість EO-автоматизації🟢 prod (PR #21 у dev; ≥100 активних розкладів у Scheduler)
12 кампаній as-code у campaigns/ (webinar-промо, abandoned-checkout, nmt-done, welcome-2026 та ін.); відправлено 1 406 листів 16–17.07🟢
Сегментація всієї бази: config/segments.json (10 взаємовиключних сегментів, драбина «перший збіг виграє»), npm run segments зі звіркою колізій між кампаніямиу PR #23
Deliverability-інструменти: Google Postmaster Tools верифіковано 17.07 (акаунт abitly.team@abitly.org), spam-rate гард у campaign:send (Postmaster v2 API, передпольотний), 10% holdout-механікакод у PR #23; Postmaster-гард чекає OAuth-креденшели (кроки в docs/go-live-checklist.md)
Дослідження email-стратегії: 25 тверджень перевірено змагально, 10 підтверджено / 15 спростованоdocs/email-marketing-strategy-2026-07.md + CEO-бриф у репо

⚠️ Ключове обмеження з дослідження: розсилка ≥5 000 листів/добу на Gmail назавжди класифікує домен як bulk sender (незворотно). Сегменти 14 612 «обрали спеціальності» і 6 089 «давні НМТ» перетинають поріг одним сендом — рішення про них приймається один раз.

Репозиторій та рантайм

Section titled “Репозиторій та рантайм”
Репоhttps://github.com/abitly-org/email-octopus (внутрішня назва abitly-email-service)
СтекNode 20 · TypeScript 5 · Fastify 5 · Zod · Vitest
ХостингAWS Lambda (eu-central-1) + API Gateway · DynamoDB · EventBridge Scheduler · SES configuration set
Спосіб деплоюручний AWS_PROFILE=yangon-admin npm run lambda:deploy з робочої машини — CI-деплою немає
Публічні URLhttps://email.api.abitly.org (GET /health)
Гілки⚠️ гілки main у репо немає (всупереч CLAUDE.md самого репо) — робоча і реліз-гілка = dev, деплой з неї. Основна незмерджена робота — PR #23 (feature/sending-automation-phase3dev)
  • Залежить від: AWS SES (eu-central-1, production access, ліміт 50k листів/день), DynamoDB + EventBridge Scheduler, PostHog EU (221580), прод-Postgres read-only (сегментація, через SSM-тунель зі скриптів). Legacy: EmailOctopus Connect API (лише contact upsert).
  • Від нього залежать: APIEmailServiceClient (src/email-service/email-service.client.ts) викликає його на реєстрації (syncContact), старті оплати (checkoutStarted → планує нагадування) і success-вебхуку Monobank (checkoutCompleted → скасовує їх). Клієнт fire-and-forget: ніколи не кидає помилок і no-op, якщо EMAIL_SERVICE_URL не заданий — падіння email-сервісу не ламає оплату/реєстрацію. Ланцюжок у prod живий (179 CHECKOUT#-станів, 18.07).

Лише назви. Повний індекс: environments. Prod-параметри сервісу — SSM /abitly/email/* (eu-central-1).

ЗміннаДеПризначення
API_SECRETemail-service (SSM /abitly/email/API_SECRET)автентифікація вхідних викликів
DYNAMODB_TABLE / SES_CONFIGURATION_SET / SCHEDULER_*email-servicebroadcast-движок + lifecycle
LIFECYCLE_REMINDER_OFFSETSemail-service (SSM)каденс нагадувань; у prod = 2 кроки (+2 год/+12 год)
POSTHOG_API_KEYemail-serviceаналітика подій, проєкт 221580
EMAILOCTOPUS_*email-servicelegacy contact upsert
EMAIL_SERVICE_URL / EMAIL_SERVICE_API_KEYapi-v2база сервісу + ключ (= API_SECRET); задані — ланцюжок живий
GOOGLE_OAUTH_CLIENT_ID/SECRET + POSTMASTER_REFRESH_TOKENлокальні скрипти (не Lambda)spam-rate гард із Postmaster; ще не видані

Ключові ендпоінти / структура

Section titled “Ключові ендпоінти / структура”
GET /health # liveness (публічний)
POST /api/contacts # реєстрація → welcome-серія (SES) + legacy EO-upsert
PUT /api/contacts/:email # оновлення полів/тегів
DELETE /api/contacts/:email # видалення (GDPR / self-delete)
POST /api/lifecycle/checkout-started # планує нагадування +2h/+12h (email, checkoutId, resumeUrl, amount)
POST /api/lifecycle/checkout-completed# скасовує нагадування після оплати (markPaid)
POST /api/campaigns/:slug/prepare|launch|send # broadcast-движок (prepare → human launch → worker)
/api/transactional, /analytics, /unsubscribe, /webhooks/ses
campaigns/ # 12 кампаній as-code: body.hbs + body.txt.hbs + campaign.json + copy.md
config/segments.json # драбина сегментації всієї бази (10 сегментів, редагується руками)

Движок кампаній: per-step копірайт у campaign.json variables.steps[], send-ledger (SEND#), suppression (SUPPRESS#), markPaid-guard (PAID#, оплаченим не шле), чесна ціна в промо (promoForAmount). Гарди відправки в campaign:send: SES HEALTHY → квота → hard-bounce (PostHog) → spam rate (Postmaster, передпольотний). Скаффолдінг нової кампанії — скіл write-campaign у репо сервісу.

Merge у dev не деплоїть — після мержа запустити AWS_PROFILE=yangon-admin npm run lambda:deploy вручну; smoke: GET /health + dry-run нагадувань (docs/deploy-checklist.md, docs/go-live-checklist.md у репо). ⚠️ campaigns/ пакується всередину бандла — нова кампанія дає 404 до деплою. Відкат — повторний deploy попереднього коміту.

CloudWatch (Lambda execution logs, eu-central-1) + PostHog 221580 (події email_*, дашборди кампаній) + Google Postmaster Tools (spam rate Gmail; дані з’являться на більших обсягах). Правило команди: кожна відправка — спершу на тестову пошту, потім dryRun, потім live.

  • «Нагадування не працюють» → перевір CHECKOUT# у DynamoDB і розклади в Scheduler-групі abitly-campaigns; ланцюжок у prod живий з 16–17.07. Історичний контекст (до wiring) → Email-маркетинг.
  • Ламані лінки у plain-text листах → історичний баг рендера .txt.hbs (HTML-escape); виправлено у PR #17 (noEscape + декод ентіті).
  • PostHog-аналітика → з 17.07 події йдуть у орг-проєкт 221580 (дашборди й bounce-гард працюють по ньому). Якщо подій не видно — перевір /abitly/email/POSTHOG_API_KEY.
  • Нова кампанія 404 на prodcampaigns/ їде всередині Lambda-бандла; треба deploy.
  • 503/504 при великому чанку → API Gateway ріже на 30с, а Lambda дошле; ledger — джерело правди, не відповідь скрипта; чанк ≤60.
  • TODO: у якому акаунті живе EmailOctopus (логін/біллінг) — зафіксувати (актуально, поки живий legacy contact upsert).