Маркетинговий блюпринт (email + Telegram)
0. П’ять рішень, що перекривають усе інше
Section titled “0. П’ять рішень, що перекривають усе інше”- Telegram стартує першим; email пропускає цикл 2026. Сьогодні 2026-06-06; пік вступної кампанії — кінець липня — серпень (~7 тижнів). Warm-up холодного домену займає 4–6 тижнів, і нічого (домен, ESP-акаунт, DNS, hygiene) не провіжнено. Email фізично не може бути безпечно готовим до revenue в 2026. ~5k Telegram-користувачів вже API-enforced opt-in, проходять broadcast за ~3 хвилини й дають ~3× email-revenue на одного отримувача. Цикл 2026 = Telegram для revenue; email = побудова чистого згоданого списку до 2027. (SRE-критик high; Finance-критик critical.)
- Жодного сенду до legal sign-off; прибрати «транзакційний re-permission» bypass. Re-permission-email — це комерційна комунікація. Фікція з lawful basis — найбільша поодинока експозиція програми. (Deliverability/право-критик — центральний дефект.)
- Engagement-події fail-closed. Prod-lake-house не існує. Поки його нема, engagement-події дропаються+рахуються, ніколи не пишуться у спільний RDS. (SRE-критик critical.)
- Один канонічний consent ledger, keyed by
channel_identity_id. Видалити конкуруючі схеми. (Deliverability/право-критик critical; Data-model-критик.) - Витягнути реальний ARPPU + intent-розподіл до того, як писати ще код. Кожна revenue-цифра тримається на недокументованому placeholder. (Finance-критик critical.)
ЧАСТИНА I — ТЕХНІЧНА ІНФРАСТРУКТУРА
Section titled “ЧАСТИНА I — ТЕХНІЧНА ІНФРАСТРУКТУРА”1. Форма системи та дві storage-площини
Section titled “1. Форма системи та дві storage-площини”abitly-marketing — це NestJS + BullMQ воркер на ECS Fargate (патерн як у Abitly API). Він володіє маркетинговою оркестрацією та станом; він купує доставку email за swappable-транспортом і переюзає @abitlybot для Telegram. Дві storage-площини, ніколи не змішуються (ADR-0002):
| Площина | Живе в | Тримає | Обсяг |
|---|---|---|---|
| Операційний стан | схема marketing на спільному RDS (abitly_prod_db, db.t4g.small), обмежений пул max=5 | consent ledger, suppression, стан сенду, сегменти, кампанії, атрибуція, fact-packs, статус генерації | низький |
| Engagement / телеметрія | Lake-house (Lambda→Firehose→S3 Iceberg) — fail-closed, поки не задеплоєний у prod | кожен sent/delivered/open/click/bounce, AI-логи, відмови валідатора | високий, append-only |
1.1 Fail-closed engagement boundary (закриває SRE-critical #1)
Section titled “1.1 Fail-closed engagement boundary (закриває SRE-critical #1)”Lake-house тільки dev (abitly-analytics: «Прод — ❌ не задеплоєний»). Дизайн не має вдавати, що він існує:
- Прапор
LAKEHOUSE_PROD_READYгейтить engagement-шлях. Поки false, engagement-події дропаються й рахуються (лічильник у Valkey) і ніколи не падають у RDS як fallback. - Запуск (Telegram + suppression + consent + one-click unsub) не потребує lake-house. Deliverability/health вимірюється з native-сигналів самого каналу (Telegram 403/blocked rate; для email пізніше — SES SNS complaint/bounce events).
- Прийнятий trade-off: цикл 2026 стартує без open/click аналітичних дашбордів. Інструментуємо лише тонкі native-сигнали. Повна attribution-площина на lake-house гейтиться на тому, що prod-стек аналітики поїде.
2. Telegram broadcast stack (revenue-канал циклу 2026)
Section titled “2. Telegram broadcast stack (revenue-канал циклу 2026)”Побудований на наявному @abitlybot (aiogram 3, AWS ECS). Дві мови, два Redis-сховища → оркестратор командує, бот виконує через HTTP control-plane, ніколи спільну чергу (BullMQ — це TS-only Redis-протокол, який Python-бот не може споживати; sidecar-Redis бота навмисно ізольований).
abitly-marketing (NestJS, BullMQ on Valkey) mkt.campaign.plan → mkt.tg.materialize (grounded+validated) → mkt.tg.dispatch │ HTTP POST (mTLS/HMAC, internal VPC only) ▼@abitlybot control-plane (aiohttp :3001) BroadcastRunner (resumable state in telegram schema) → MessageSender (30/s global, 403-suppress, 429-retry) │ reciprocal webhooks (progress / consent / suppression) ▼ back to marketing + engagement → lake-house (fail-closed)Ключові рішення (збережені з компонентного дизайну):
- Переюз
MessageSender(30/s aiolimiter, concurrency 5, 429-retry, 403-drop). Додати вкладений маркетинговий бюджет ~20/s, щоб broadcast ніколи не голодом виморював транзакційні/support-відповіді — Telegram-аналог окремого email-сабдомену. 429 заморожує весь токен, тожretry_after > 120s→ abort + alert. - Resumable стан broadcast у Postgres-схемі бота
telegram(broadcast/broadcast_item), не в ефемерному sidecar-Redis — переживає ECS rolling deploy. At-most-once на контакт черезUNIQUE(broadcast_id, chat_id)+pending→sent. - Бот — тупий виконавець. Маркетинг pre-фільтрує по consent+suppression і шле готові
(chat_id, contact_id, payload). Жодних hot cross-schemamarketing↔telegramjoin (ADR-0002). - Deep-link-атрибуція через
m_<campaign>_<source>_<contactToken>(≤64 символів;contactToken— підписаний короткий токен, що резолвиться маркетингом, не сирий UUID). - Mini App — це checkout-поверхня (кнопка
web_app,startapp≤512 символів) для Premium/Subscription/Tokens через Monobank. Telegram Stars / Paid Broadcasts НЕ використовуються.
Закриття знахідок критиків:
- Ризик spam-report (Bot ToS 5.2b обмежує весь токен). Хвиля 1 йде лише на найнедавніше активних користувачів бота, веде з реальною цінністю (free-tokens carrot), робить inline-opt-out найпомітнішою дією і моніторить 403/blocked як real-time spam-проксі — сплеск 403 передує report-driven обмеженню → auto-pause. Давно сплячі Start-користувачі НЕ входять у хвилю 1. (Deliverability-критик high.)
- Ізоляція репутації залежить від runtime-розділення. Маркетинговий Telegram-воркер працює в окремому процесі від
@abitlybot; маркетинговий 429 паузить лише маркетинговий бюджет. Верифіковано на деплої, не припускається. (Orchestration-ризик.) - Запас одного ECS-таску бота (256/512, desired_count=1). Переглянути розмір таску до того, як phase-2 паралельні broadcast зіткнуться з 07:00 APScheduler cron. (Telegram open item — task-size review гейтиться перед масштабуванням.)
- Мережевий шлях.
:3001— internal-VPC-only з mTLS чи HMAC shared-secret; ніколи публічний endpoint (поверхня forged-broadcast).
3. Email-доставка та deliverability (будуємо під 2027, провіжнимо зараз)
Section titled “3. Email-доставка та deliverability (будуємо під 2027, провіжнимо зараз)”Email відкладено для revenue в 2026, але провіжниться під час off-peak, щоб бути прогрітим до весни 2027. Механіка deliverability коректна; виправлення стосуються lawful basis, build-vs-buy і незалежності від lake-house.
3.1 Спершу ВЕРИФІКУВАТИ транзакційний baseline (закриває Deliverability-critical #2)
Section titled “3.1 Спершу ВЕРИФІКУВАТИ транзакційний baseline (закриває Deliverability-critical #2)”До публікації будь-якого DNS твердження про ізоляцію треба верифікувати, а не припускати. Транзакційний SMTP-провайдер і From-домен недокументовані (email-analytics: provider = TODO). Дія: прочитати /abitly/prod/backend/SMTP_HOST + SMTP_FROM; виконати dig TXT abitly.org, dig TXT _dmarc.abitly.org, перевірити наявні DKIM-селектори; підтвердити, що транзакційний email НЕ шлеться вже як @abitly.org з apex SPF, з яким новий ESP include зіткнеться або переб’є ліміт 10 lookup. Задокументувати реального транзакційного відправника й закрити TODO.
3.2 Домен і DNS (механіка незмінна)
Section titled “3.2 Домен і DNS (механіка незмінна)”- Маркетинг шле з нового холодного сабдомену
news.abitly.org(From),bounce.news.abitly.org(custom MAIL FROM, SPF-all),link.news.abitly.org(tracking, відкладено — див. нижче). Apex і транзакційна репутація ніколи не зачіпаються. - SES Easy DKIM (3 CNAME,
d=вирівняно наnews.) — але див. рішення по ESP. DMARC наnews.abitly.orgпіднімаєтьсяp=none → quarantine → reject, гейтиться на обов’язковому RUA-моніторингу (Postmark digest / dmarcian free tier). Усі записи DNS-only (grey cloud) у зоні Cloudflare.
3.3 ESP: Brevo на перший цикл, за swappable-транспортом (ПЕРЕГЛЯНУТО — закриває SRE-medium)
Section titled “3.3 ESP: Brevo на перший цикл, за swappable-транспортом (ПЕРЕГЛЯНУТО — закриває SRE-medium)”Компонентний дизайн рекомендував SES; SRE-критик коректно перевертає це для циклу 1. Рішення: Brevo (EU-native) для re-permission-запуску. Обґрунтування, переважене на engineer-hours, а не на ~$3/cycle різниці у вартості:
- Заголовкова перевага SES — native SES→Firehose у lake-house — moot, поки lake-house не в prod.
- Brevo купує GDPR-преференс-центр, hosted unsubscribe, consent UI і suppression з першого дня — найшвидший безпечний перший сенд для команди з однієї людини.
- Обидва сидять за тим самим портом
EmailTransport. Перенести доставку на SES пізніше, коли prod-lake-house зробить native event-шлях реальним, а обсяг виправдає in-house build. - НЕ будуй абстракцію на 3 адаптери зараз. Постав один Brevo-адаптер за інтерфейсом; додай SES при міграції. (Finance-критик high.)
- Без dedicated IP при ~30k сезонних (далеко нижче порога ~100–200k/міс). Додати seed-list inbox-placement тестування (Gmail/Outlook/Yahoo/Apple) на кожному кроці warm-up як випереджальний індикатор — opens MPP-роздуті, а complaints запізнюються. (Deliverability-критик medium.)
3.4 List hygiene з порогом engagement-recency (закриває Deliverability-medium)
Section titled “3.4 List hygiene з порогом engagement-recency (закриває Deliverability-medium)”- ZeroBounce-batch на 30k — але застосувати ту саму EU-residency прискіпливість, що й до ESP (підтвердити DPA + EU-processing ~30k email неповнолітніх, або взяти EU-верифікатор). Це також не launch-critical шлях: hygiene-ити холодний хвіст, а не сенд-1.
- Жорстке правило mailability для циклу 1: catch-all/unknown потребують позитивного свіжого сигналу активності (недавній логін / Telegram-активність / недавній
OfferApplicant) перед будь-яким сендом. Engagement-recency, а не лише статус ZeroBounce, — це поріг.
3.5 Зменшення blast-radius холодного сенду (закриває Deliverability-critical, ризик complaint)
Section titled “3.5 Зменшення blast-radius холодного сенду (закриває Deliverability-critical, ризик complaint)”Найбільш complaint-схильне повідомлення, яке програма колись пошле, НЕ має бути warm-up-payload на домені з нульовою репутацією:
- Запустити re-permission СПЕРШУ на уже згоданих каналах (Telegram Start, in-app/on-login web-банер, преференс-центр), щоб зменшити cold-email залишок.
- Cold-email лише на найнедавніше активних впізнавачів у крихітній (~500) засіяній першій когорті.
- Виміряти реальний complaint rate через SES/ESP real-time events за 72 год; вимагати <0.1% до розширення. Розглядати когорту 1 як експеримент із репутацією, не як кампанію.
- Day-1 circuit-breaker читає ESP-native real-time complaint/bounce events, НЕ Google Postmaster (порожній/із затримкою перший ~тиждень). (Deliverability+SRE: day-1 circuit-breaker читає порожню телеметрію.)
3.6 Загартування one-click unsubscribe (закриває Deliverability-high)
Section titled “3.6 Загартування one-click unsubscribe (закриває Deliverability-high)”- RFC 8058 one-click з email #1, миттєво honored, заголовки запечені в транспорт.
- Виключити POST-endpoint з Cloudflare Bot Fight Mode / WAF (автоматизований POST від Gmail/Yahoo, який отримує challenge = порушення на >2 дні). Протестувати автоматизований POST з реальних seed-акаунтів Gmail/Yahoo/Apple до сенду 1.
- Лишити stateless HMAC-токен, але додати rate-limiting per token/IP, логувати кожен unsub із IP-hash для детекції зловживань і використати versioned/rotatable secret, щоб витік був revocable без поломки вже надісланих лінків. Fail-open на suppress; масовий forged unsub має бути детектованим і operator-reversible.
3.7 Відкладено для циклу 1 (вирізи скоупу)
Section titled “3.7 Відкладено для циклу 1 (вирізи скоупу)”Branded click-tracking (link.news. і footgun із CF-проксі, який він несе), BIMI/VMC, in-house MJML CI-пайплайн (натомість Brevo-templating) і 3 Metabase-борди. Open rates все одно MPP-роздуті й де-пріоритизовані.
4. Дані, identity та сегментація
Section titled “4. Дані, identity та сегментація”4.1 Identity-модель (збережено)
Section titled “4.1 Identity-модель (збережено)”Контакт = BaseUser (D2). Канали висять на контакті як рядки channel_identity. Telegram-користувачі, які ніколи не злінкували web-акаунт, стають Telegram-only orphan-контактами (base_user_id = NULL). Пізнє лінкування на auth зливає orphan у email-rooted контакт, перенаправляючи рядки, keyed by channel_identity_id. Жодного probabilistic stitching — лінкування детерміноване на auth (GDPR-чисте, операбельне для bus-factor-1). Кількість orphan-vs-linked після першого sync відповідає на відкрите питання «5k TG ↔ email overlap».
4.2 Один канонічний consent ledger (закриває Consent-critical неузгодженість)
Section titled “4.2 Один канонічний consent ledger (закриває Consent-critical неузгодженість)”По доках існувало три несумісні consent-схеми. Канонічна модель, на яку посилаються ВСІ компоненти:
-- append-only ledger (GDPR Art.7 "demonstrate consent") — the legal recordCREATE TABLE marketing.consent_event ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, channel_identity_id bigint NOT NULL REFERENCES marketing.channel_identity(id), -- KEYED HERE, not contact_id channel text NOT NULL, -- email | telegram status text NOT NULL, -- granted | withdrawn | pending source text NOT NULL, -- re_permission | preference_center | start_deeplink | signup_transactional scope text[] NOT NULL DEFAULT '{}', consent_text_version text NOT NULL, -- exact wording shown (incl. lawful-basis blessed by legal) is_minor boolean, -- NEW: derived from birth year / NMT cohort (minors ADR) occurred_at timestamptz NOT NULL, -- ISO-8601 UTC created_at timestamptz NOT NULL DEFAULT now());-- fast projection, what the send pipeline's ConsentGuard readsCREATE TABLE marketing.consent_current ( channel_identity_id bigint PRIMARY KEY REFERENCES marketing.channel_identity(id), channel text NOT NULL, status text NOT NULL, scope text[] NOT NULL DEFAULT '{}', is_minor boolean, granted_at timestamptz, withdrawn_at timestamptz, updated_at timestamptz NOT NULL DEFAULT now());- Keyed by
channel_identity_id, щоб consent/suppression пережили late-link merge (contact_id-keying псується на merge — Data-model-критик). - Telegram-ідентичності засіюються як
granted(Start = API-enforced досяжність+згода). Email-ідентичності стартуютьpendingі перемикаються наgrantedлише через legally-blessed re-permission (НЕ self-declared транзакційний bypass). is_minor— нове (AI-safety-критик critical — див. §5.3 і ADR по неповнолітніх).
4.3 Suppression (збережено, redundant-by-design)
Section titled “4.3 Suppression (збережено, redundant-by-design)”Постійний, channel-scoped, остання лінія безпеки, що перевіряється останньою в send pipeline. Sinks: email hard-bounce/complaint/unsubscribe; Telegram 403-blocked / /stop; ZeroBounce invalid/spamtrap/abuse pre-seeded. Переживає ESP-swap (тримається окремо від власного списку ESP). Оптимізація hot-path (закриває SRE-high connection budget): per-send suppression-перевірка читає Valkey set/bloom filter (перебудований із durable-таблиці), не RDS round-trip на отримувача.
4.4 Сегменти (збережено)
Section titled “4.4 Сегменти (збережено)”Матеріалізовані snapshot (segment_membership keyed by computed_run_id), скомпільовані з декларативного rule_jsonb над денормалізованими read-model колонками — ніколи живі abitly join. Suppression + consent завжди ANDяться в скомпільований SQL, тож сегмент структурно не може включити незгоданий чи suppressed контакт. Таксономія = lifecycle stage (primary) × intent cluster × engagement × value (не RFM — single-cycle термінальна аудиторія). Див. матрицю сегмент→offer у Частині II §3.
4.5 Read-model sync (збережено, batch-first)
Section titled “4.5 Read-model sync (збережено, batch-first)”Watermarked paged SELECT по abitly.updatedAt під обмеженим пулом, поза send hot-path, тільки off-peak під час send-вікна. CDC (RDS logical replication чи API domain events) зарезервовано на коли L3-latency цього вимагатиме. Strapi-факти НЕ синхронізуються — fetch на рендері (ADR-0003). Open: верифікувати, що abitly-таблиці експонують надійний updatedAt; інакше нічний повний diff у тимчасову marketing staging-таблицю.
5. Пайплайн AI-персоналізації
Section titled “5. Пайплайн AI-персоналізації”Модель пише ТІЛЬКИ переконливу обгортку; УСІ факти — детерміновані merge-поля, валідовані проти структурованого input (ADR-0003). Fact-Pack (Zod-валідований immutable snapshot, окремі L2_SEGMENT / L3_CONTACT форми), placeholder-preferred рендеринг, surface-form валідатор, обмежений regenerate→fallback-to-approved-template цикл, Haiku-L2/Sonnet-L3 Batch-routing (~$30–50/cycle), prompt-caching, eval harness і template-level approval queue — усе збережено. Чотири нові gate закривають AI-safety-критика:
5.1 Advice-correctness gate (закриває AI-safety critical #1)
Section titled “5.1 Advice-correctness gate (закриває AI-safety critical #1)”Surface-валідатор доводить, що факт існує у pack, ніколи що факти коректно скомбіновані. Найгірший провал EdTech-для-підлітків — добре grounded, але оманлива порада («твій бал 169 вище прохідного 168.5 — ти вступиш»). Додати детермінований gate ВИЩЕ за surface-валідатор:
- Slot-provenance binding — число в тілі має трасуватися до конкретного слота, на який посилається його речення, а не просто десь існувати в pack.
- Verdict cross-check — якщо текст натякає «вище / вступиш», але
fit_vs_passing != 'above'→ REJECT. - Жорстка заборона predictive/causal admission-тверджень (вступиш / гарантовано / точно пройдеш / 100%) незалежно від grounding.
- Atomic offer triple — залочити
(offer, deadline, score), щоб модель не могла причепити неправильний дедлайн до правильного ЗВО.
5.2 Untrusted user-merge значення (закриває AI-safety critical #3)
Section titled “5.2 Untrusted user-merge значення (закриває AI-safety critical #3)”first_name user-контрольоване → вектор prompt-injection / XSS / abuse, що тривіально проходить валідатор (first_name == contact.first_name). Трактувати всі user-sourced merge-значення як untrusted: HTML/Telegram-HTML escape + charset allowlist (кирилиця/латиниця, дефіс — відкидати фігурні дужки, кутові дужки, URL, control-символи) на ОБОХ етапах — складання prompt і рендер. Обов’язкова sampled human review auto-approved L3-інстансів (зараз жодна людина не читає per-contact L3-текст).
5.3 Minors guardrail (закриває AI-safety critical #2 — також legal ADR)
Section titled “5.3 Minors guardrail (закриває AI-safety critical #2 — також legal ADR)”Аудиторія — 16–18 років; не всі 18. Додати is_minor у Fact-Pack + consent ledger (виведено з року народження / NMT-когорти). Для відомих неповнолітніх: жодного deadline-urgency high-pressure paywall framing, м’якший CTA, parental/refund disclosure у sender-ID блоці. Legal review AI-персоналізованого платного продажу неповнолітнім конкретно. Launch blocker, не TODO.
5.4 Fact-Pack source-data QA (закриває AI-safety high)
Section titled “5.4 Fact-Pack source-data QA (закриває AI-safety high)”Fact-Pack — це власна ground truth валідатора — ніщо не перевіряє вихідні факти. Додати plausibility-assertion перед генерацією: дедлайн у межах вступного вікна; прохідний бал у діапазоні NMT 100–200, позначений роком («прохідний 2025»); competitive_score перерахований і звірений із предметними балами; offer.university/speciality крос-консистентні. Pre-send freshness-перевірка §5.5 детектує зміну, не хибність — потрібні обидві.
5.5 Збережена safety-механіка
Section titled “5.5 Збережена safety-механіка”- Уся арифметика попередньо порахована детерміновано; модель ніколи не рахує/порівнює числа.
- Eval: використати іншу model family як judge, ніж як генератор (уникнути same-family змови), відкалібрувати пороги ≥4.0 проти реальних UA-teen-оцінених семплів до того, як їм довіряти, додати adversarial-зрізи для класів semantic-correctness + неповнолітні й розставити blind human spot-checks.
- Model-version pinning + canary-генерація на batch, що стверджує: placeholder-дисципліна не зрегресувала; протестований fallback-модель на retirement у пік сезону.
- Batch-API + 1-год prompt cache стек; задокументувати синхронний (full-price) fallback-шлях для time-critical L3, коли 24h Batch-latency зіткнеться з 3-денним дедлайном.
6. Engine оркестрації та безпеки
Section titled “6. Engine оркестрації та безпеки”Ядро виконання: кожне повідомлення кожного каналу проходить ОДИН упорядкований send pipeline. Безпека — це властивість шляху, не caller — властивість, що дозволяє одному інженеру спати.
6.1 Єдиний send pipeline (збережено, з новим rail)
Section titled “6.1 Єдиний send pipeline (збережено, з новим rail)”0 KillSwitch → 1 Suppression → 2 Consent → 3 FrequencyCap → 4 QuietHours →5 SendWindow → 6 AdviceCorrectness(L3) → 7 WarmupBudget(LAST)- Транспорти package-private для send-воркера — ніщо не може обійти rails.
- Warmup budget — ОСТАННІЙ, тож DROP/DEFER ранішими rails ніколи не споживає денні warm-up токени → cold-domain ramp рахує лише реальні сенди.
- AdviceCorrectness (новий rail 6, §5.1) виконується перед тим, як бюджет витрачено на оманливий L3.
- Fail-closed: якщо suppression/consent нечитабельні (RDS-blip), rails DEFER, ніколи не шлють.
6.2 Два режими сходяться на одному шляху
Section titled “6.2 Два режими сходяться на одному шляху”Batch Campaigns (D9 v1) заморожують snapshot сегмента (send_batch, idempotent/resumable); event-triggered proto-Journeys (дедлайн T-72h, L3) — це Campaign-of-one, keyed contact × journey × offer × deadline. Deadline-тригер Telegram-first (≈10× email CTR, миттєвий), email — fallback.
6.3 Idempotency, kill-switch, warm-up (збережено)
Section titled “6.3 Idempotency, kill-switch, warm-up (збережено)”jobId = campaign×contact×channel(або journey key) + UNIQUEidem_keyнаsend_logз claim-before-send (INSERT ... ON CONFLICT DO NOTHING) → re-runs/replays/crashes ніколи не дублюють сенд. Reaper звіряє застрягліclaimed-рядки.- Three-scope kill-switch (global / per-channel / per-campaign) як Valkey-прапори, перевіряються першими; rail 0 DEFERить, тож перемикання назад дренажить заморожену чергу без втрати стану. Auto-tripped complaint circuit-breaker’ами (0.10% warn / 0.30% hard) і
retry_after>120s. - Warm-up token bucket (per-second rate + per-day cap із таблиці
warmup_schedule) робить сам оркестратор ramp-контролером — без ручного pacing.
6.4 Дисципліна connection-budget (закриває SRE-high)
Section titled “6.4 Дисципліна connection-budget (закриває SRE-high)”- Маркетинговий пул
max=5; sync-воркер + send-воркери ділять його під бюджет. - Suppression/consent hot-path reads йдуть у Valkey-кеші, не RDS round-trips.
- Global free-connection-slot alarm на спільному інстансі (задокументований failure-mode «too many connections»). Розглянути PgBouncer, якщо з’явиться contention.
6.5 Алертинг розв’язаний з broadcaster (закриває SRE-high)
Section titled “6.5 Алертинг розв’язаний з broadcaster (закриває SRE-high)”Основні deliverability circuit-breaker алерти йдуть через CloudWatch Alarm → SNS → email/SMS, незалежно від @abitlybot (який і broadcaster, І один ECS-таск — алертинг через нього ділить failure-domain із тим, що він моніторить). Telegram-алерти — лише вторинна зручність.
7. Атрибуція та аналітика (радикально обрізано для циклу 1)
Section titled “7. Атрибуція та аналітика (радикально обрізано для циклу 1)”7.1 Цикл 1 — мінімальне чесне вимірювання
Section titled “7.1 Цикл 1 — мінімальне чесне вимірювання”- Stamp на джерелі:
send_id— join-ключ;(campaign_id, contact_id, send_id, variant_id, segment_id)їдуть каналом через deep-link (as_sid/as_cid, HMAC-signed) і custom args Brevo. - Операційний last-touch (D11) для «кому зарахувати» всередині маркетингованої когорти — збережено, але позначено як over-crediting owned-каналів (абітурієнт вступив би все одно).
- Telegram-конверсія = click/Mini-App entry, ніколи opens (нема read API; MPP-free).
- Точність атрибуції гейтиться на cross-repo зміні:
abitly-api-v2/payments/createмає протягнутиas_sid/as_cidу metadata Monobank-інвойсу → webhookconversion_purchased. Підтвердити ownership/timing як Phase-0 gate; якщо не приземлиться, прийняти fuzzy 7-day time-window last-touch для циклу 1 і не будувати deep-link attribution marts, що від нього залежать.
7.2 ВИРІЗАНО для циклу 1 (закриває Finance-high + AI-safety-medium)
Section titled “7.2 ВИРІЗАНО для циклу 1 (закриває Finance-high + AI-safety-medium)”Universal 10% holdout + CUPED + dbt program-lift marts. При ~145–245 покупках/cycle 10%-holdout бачить кілька конверсій — lift CI надто широкий, щоб діяти в межах одного термінального циклу, і він жертвує ~$200–400 рідкісного revenue, щоб виміряти revenue, якого ледь є. Фреймити holdout як метод циклу 2+, не вердикт циклу 1. Для near-term сигналу — простий контроль на єдиному найбільшому за обсягом flow (deadline-тригер) і опора на per-channel A/B (TG vs email deadline-тригер).
7.3 A/B (збережено, відкладено до фази 2)
Section titled “7.3 A/B (збережено, відкладено до фази 2)”Bayesian probability-to-beat (не frequentist NHST) — толерує peeking, операбельне для однієї людини. Порядок тестів: subject → preheader → CTA → send-time → sender. Найважливіший cold-start «тест» — re-permission copy + unsubscribe UX (guardrail 0.10% > open rate).
ЧАСТИНА II — БІЗНЕС / REVENUE-ПЛАН
Section titled “ЧАСТИНА II — БІЗНЕС / REVENUE-ПЛАН”1. Витягнути несучі цифри ДО того, як ще код
Section titled “1. Витягнути несучі цифри ДО того, як ще код”Два half-day запити гейтять go/no-go і перезапускають усю модель:
- Реальний Monobank ARPPU per SKU + realized free→paid conversion з наявних
/paymentsданих. Кожна revenue-цифра тримається на недокументованому placeholderARPPU=300 UAH (~$7.50). Якщо реальний ARPPU, скажімо, 150 UAH, base case вдвічі менший, а email build не захищається. - OfferApplicant intent-cluster розподіл по lifecycle stage. Усі розміри сегментів нижче — гіпотези, поки цього нема.
2. Монетизація (переюз Monobank; жодного білінгу не будуємо)
Section titled “2. Монетизація (переюз Monobank; жодного білінгу не будуємо)”| SKU | Тип | Психологія | Сезонний пік | Нотатка |
|---|---|---|---|---|
| Tokens | One-off consumable | Low-friction вхід, сигнал willingness-to-pay | Чер–Сер | «Передні двері» для невизначених |
| Premium | One-off | Імпульс на дедлайні; «обери прямо зараз» | Кін. лип–сер | Дефолтний hero offer; контекстний paywall ≈ 2× conv |
| Subscription | Recurring | Спокій, вищий LTV | Весна→cycle | Див. ризик |
Packaging ladder: Free → Tokens → Premium (контекстний paywall на дедлайні моніторованого offer) → Subscription. Re-permission carrot = free tokens / Premium-trial (дай цінність за згоду).
3. Матриця сегмент → offer (lifecycle × intent; розміри — гіпотези)
Section titled “3. Матриця сегмент → offer (lifecycle × intent; розміри — гіпотези)”| Сегмент (stage × intent) | Hero offer | Канал | Тригер / кут | Pers. |
|---|---|---|---|---|
| Active × медицина (висока конкуренція) | Premium → Subscription | TG+email | «дедлайн на [offer]; прохідний росте — перевір шанси» | L3 |
| Active × IT/інженерія | Premium | TG+email | «твій NMT-профіль vs прохідний [offer]» | L3 |
| Active × broad/невизначені | Tokens → Premium | «обери серед offers, які моніториш» | L2 | |
| Sleeping × any (найбільший) | Re-engage → Premium | email→TG | «сезон 2027 стартував; offers, які ти трекав, оновлено» | L2 |
| Sleeping × no intent | Активація (трекни 1-й offer) | «почни трекати — перевір шанси безкоштовно» | L2 | |
| NMT-prep (весна) | Tokens / Premium-trial | email+TG | «N днів до NMT; перевір готовність» | L2 |
| Enrolled (попередній цикл) | Studsearch | email+TG | «вітаємо зі вступом до [ЗВО] — лиши відгук / дивись insights» | L2 |
| No consent (всі, pre-C0) | Re-permission carrot | спершу згодані канали | «налаштуй свої alerts на 2027 + free tokens» | L2 |
4. Портфель кампаній (flows > newsletters), переупорядковано
Section titled “4. Портфель кампаній (flows > newsletters), переупорядковано”| # | Flow | Тригер | Канал | Пріоритет | Коли live |
|---|---|---|---|---|---|
| C0 | Re-permission + преференс-центр | старт циклу | згодані канали → cold-email хвіст | P0 (blocker) | Після legal sign-off |
| F1 | Deadline-тригер (моніторований offer) | дедлайн через N днів | TG-first | P0 — #1 RPR | Фаза 1 (Telegram) — до кінця липня |
| F2 | Welcome / активація | consent / новий Start | TG+email | P1 | Фаза 1 |
| F3 | Intent-nurture | calendar × intent | P1 | Фаза 2 | |
| F4 | Conversion push (пік) | кін. лип–сер | TG+email | P1 | Фаза 2 |
| F5 | Win-back / next-season | 60–120д неактивності | email+TG | P2 | Фаза 3 |
| F6 | Studsearch cross-sell | виявлено вступ | email+TG | P2 | Phase 0 dependency (див. ризик) |
| F7 | Off-season value | максимум раз/місяць | P3 | Фаза 4 |
5. Математика воронки (плануй на КОНСЕРВАТИВНУ колонку для циклу 1)
Section titled “5. Математика воронки (плануй на КОНСЕРВАТИВНУ колонку для циклу 1)”Email-воронка стекає п’ять «base-case» факторів; cold first-cycle перформанс кластеризується на консервативному кінці, а 28%-of-openers re-permission opt-in на 2-річно-stale списку — щедро (5–15% реалістично), стискаючи згоданий базис із ~1,630 до ~400–800.
Re-permission (C0, разово, консервативно): 30k raw → ~24k valid (hygiene) → ~90% delivered → ~18% open → ~15% opt-in серед openers ≈ ~580 згоданих email. Telegram: ~5k Start-нуто, ~90% reachable, inline grant.
Telegram per-send (base): delivered ~93% × CTR ~10% × conv ~6% = ~0.56% net purchase; RPR @300₴ ≈ 1.67₴ (~3× email).
Cycle-1 attributed revenue (з усіма flows, не лише broadcast):
| Сценарій | USD / cycle |
|---|---|
| Консервативний | ~$0.8k–$1.5k |
| Base | ~$1.8k–$4k |
| Оптимістичний (сильний L3 deadline flow) | ~$5k–$8k |
Telegram несе більшість. Цикл 1 — це інвестиційний цикл (чистий список + прогрітий домен + перші engagement-дані); реальне зростання — цикл 2+. Найбільші абсолютні важелі — ARPPU і розмір згоданого базису, не open rate.
6. Guardrails (circuit-breakers, не плитки дашборду)
Section titled “6. Guardrails (circuit-breakers, не плитки дашборду)”| Guardrail | Ціль | Hard limit | Дія |
|---|---|---|---|
| Complaint rate | <0.10% | 0.30% | auto-pause черги + alert |
| Bounce rate (post-hygiene) | <2% | — | hold warm-up advance |
| Unsubscribe rate | <0.5% | — | flag шаблону/сегмента |
| TG 403/blocked surge | — | — | real-time spam-проксі → pause хвилю |
TG retry_after | — | >120s | pause маркетингову чергу + abort |
7. Frequency caps — ВСТАНОВИТИ ЗАРАЗ, не TODO (закриває all-critic знахідку)
Section titled “7. Frequency caps — ВСТАНОВИТИ ЗАРАЗ, не TODO (закриває all-critic знахідку)”Fatigue названо #1 драйвером unsub, проте cap — TODO по чотирьох компонентах, що робить rail no-op. v1-значення, вирішені зараз: email ≤2/тиждень, Telegram ≤3/тиждень, єдиний cross-channel бюджет, keyed на contact = BaseUser, плюс quiet hours 22:00–08:00 Europe/Kyiv. (Per-contact timezone відкладено; прийнятно для майже-100% української аудиторії.)
8. Операційна модель bus-factor-1
Section titled “8. Операційна модель bus-factor-1”Кілька довговічних evergreen-flows, що переозброюються щоцикл > багато hand-crafted кампаній. Self-serve преференс-центр offload-ить тюнінг частоти. Swappable-ESP абстракція (один адаптер зараз). Одне approval на шаблон (D8); Telegram inline Approve/Reject життєздатний. Сезонний cadence залочений на NMT-цикл із навмисно тихим off-season для захисту прогрітого домену. Додати complaint/DSAR/erasure runbook і blocklist-remediation runbook (Spamhaus/SNDS delisting) до cold-сенду.
9. Поетапна дорожня карта
Section titled “9. Поетапна дорожня карта”| Фаза | Скоуп | Гейтиться на |
|---|---|---|
| 0 — Foundations | схема marketing (канонічний consent ledger + suppression + send_log), Valkey suppression cache, bot control-plane :3001, fail-closed engagement boundary, frequency caps встановлені, ARPPU+intent витягнуті, transactional-DNS baseline верифікований, рішення по enrollment-сигналу + payments-as_sid | Шлях legal sign-off визначений |
| 1 — Telegram revenue (2026) | TG re-permission (inline grant), F1 deadline-тригер (Telegram), F2 welcome, deep-link атрибуція, real-time 403 моніторинг | Legal sign-off; F6 enrollment-сигнал |
| 2 — Email list-build (до 2027) | Brevo + news. DNS + warm-up (consented-first, крихітна засіяна когорта, <0.1% complaint gate), преференс-центр (Brevo), one-click unsub загартований, L2 segment copy + approval queue | DNS baseline; ESP DPA |
| 3 — L3 + вимірювання | Email deadline-тригер, advice-correctness gate live, per-flow контроль, lake-house prod (якщо поїхав) → engagement+attribution marts | Lake-house prod deploy |
| 4 — Journeys v2 | event-triggered сценарії, send-time оптимізація, next-best-offer, universal holdout + lift (cycle 2 spend виправдовує) | Зрілі engagement-дані |
10. Що лишається vs що вирізано
Section titled “10. Що лишається vs що вирізано”Збережено (справді несуче для bus-factor-1): ізоляція транзакційної репутації (сабдомен + вкладений Telegram-бюджет); suppression-таблиця, що переживає ESP-swap; grounded-generation валідатор як compliance gate; єдиний упорядкований send-pipeline-as-chokepoint; resumable Telegram broadcast-стан; детермінована resolution identity; AI-вартість rightsized (~$30–50/cycle, похибка округлення).
Вирізано/відкладено для циклу 1: email як revenue-канал 2026; universal holdout + CUPED + lift marts; 3-адаптерна ESP-абстракція; in-house преференс-центр + MJML-пайплайн (купуємо Brevo); BIMI; branded click-tracking; 3 Metabase-борди; F1 із Фази 3 (перенесено на Фазу 1).
План на 90 днів
Section titled “План на 90 днів”- Тиждень 1 — Загейтити програму: витягнути реальний Monobank ARPPU per SKU + free→paid conversion і OfferApplicant intent-розподіл; перезапустити модель воронки. Відкрити legal review для re-permission lawful basis + позиції щодо неповнолітніх. Верифікувати транзакційний baseline (
dig abitly.org TXT/_dmarc/DKIM; прочитати SSMSMTP_HOST/SMTP_FROM) і закрити email-analytics TODO. - Тиждень 2 — Вирішити несучі залежності: підтвердити джерело сигналу «виявлено вступ» для Studsearch cross-sell (F6) і owner/timeline зміни
abitly-api/payments, щоб протягнутиas_sid/as_cid. Встановити frequency caps (email≤2/тиждень, TG≤3/тиждень, cross-channel наBaseUser) + quiet hours. Підняти схемуmarketingіз канонічним ledgerconsent_event+ проєкцієюconsent_current, keyed bychannel_identity_id. - Тиждень 3 — Побудувати read-model sync (batch, watermarked, bounded pool, off-peak) і виконати перший sync; відзвітувати orphan-vs-linked Telegram counts (відповідає на 5k↔email overlap). Підняти Valkey suppression/consent кеші й fail-closed engagement boundary (прапор
LAKEHOUSE_PROD_READY, drop+count). - Тиждень 4 — Побудувати bot control-plane
:3001(mTLS/HMAC, internal VPC) + reciprocal webhooks; таблиціtelegram.broadcast/broadcast_item+ resumable runner; вкладений 20/s маркетинговий limiter верифіковано окремо від транзакційного шляху. Зв’язати єдиний send pipeline з усіма rails (вкл. advice-correctness + minors guard). - Тиждень 5 — В очікуванні legal sign-off: побудувати Telegram re-permission flow (inline grant + carrot), deep-link
m_namespace + резолюцію токена, real-time 403/blocked spam-proxy монітор з auto-pause. CloudWatch→SNS алертинг, розв’язаний з ботом. - Тиждень 6 — На legal sign-off: послати Telegram re-permission спершу найнедавніше активним користувачам (НЕ давно сплячим); моніторити сплеск 403 і opt-out; ітерувати copy. Побудувати F2 welcome. Підняти AI L2/L3 пайплайн із grounded-валідатором + advice-correctness gate + escaped user-merge полями.
- Тижні 7–8 — Поставити F1 deadline-тригер на Telegram (#1 RPR flow) з pre-approved L3-шаблонами, Fact-Pack source-data QA, Mini App checkout CTA. Pre-approve усі шаблони циклу до липневої хвилі. Налаштувати per-flow контрольне вимірювання + last-touch атрибуцію (якщо зміна payments приземлилась).
- Тижні 9–10 — Провіжнити email для 2027: DNS сабдомену
news.(DNS-only), Brevo identity + Easy-DKIM-еквівалент, DMARCp=noneз обов’язковим RUA-моніторингом, CF Email Routing дляpostmaster@/abuse@, загартувати one-click unsub POST (CF-challenge exempt, rate-limit, rotatable secret) і протестувати з Gmail/Yahoo/Apple seeds. - Тижні 11–12 — Email list hygiene (ZeroBounce з EU DPA, engagement-recency поріг для catch-all/unknown). Запустити re-permission спершу на згоданих каналах, щоб зменшити cold-залишок; cold-email крихітну ~500 когорту найактивніших впізнавачів, вимагати <0.1% реального ESP complaint за 72 год до будь-якого розширення. Написати complaint/DSAR/erasure + blocklist-remediation runbooks. Притримати решту email warm-up, щоб бути inbox-ready до весни 2027; НЕ ганятися за email-revenue 2026.
Головні ризики
Section titled “Головні ризики”- Legal basis для re-permission-сенду необґрунтований і нерев’юнутий: re-permission-email — це комерційна комунікація, що вимагає ПОПЕРЕДНЬОЇ згоди за УЗ «Про рекламу» ст.14-3 + GDPR/ePrivacy, а аудиторія включає неповнолітніх. Сенд до задокументованого legal-висновку ризикує штрафами (загострено законопроєктом 8153) і назавжди палить cold-домен. Мітигація: ADR-0005 — жодного сенду до письмового sign-off; прибрати транзакційний bypass.
- Колізія сезонного вікна: revenue циклу 2026 залежить від того, що deadline-trigger flow буде live до кінця липня, але компонентні дизайни гейтили його на Фазу 3 за повним email-стеком, а email warm-up не встигне. Якщо F1 не на Telegram до середини липня, найцінніша когорта втрачена на рік. Мітигація: ADR-0004 + переупорядкування F1 на Фазу 1 на Telegram.
- Амплифікація shared-RDS SPoF: один необережний bulk-insert engagement-подій у схему
marketing(бо prod-lake-house не існує) або вичерпання connection-pool під час сплеску sync+send кладе Abitly API + Strapi + Studsearch разом. Мітигація: fail-closed engagement boundary (drop+count, ніколи RDS), Valkey-кешовані hot-path reads, bounded pool, global free-slot alarm. - Revenue побудований на невідомому ARPPU й over-built, mis-sequenced плані: кожна цифра тримається на placeholder
ARPPU=300 UAH, а email з’їдає ~80% build за ~15–25% base-case revenue. Якщо реальний ARPPU суттєво нижчий, чотиризначний цикл 1 не виправдовує email build. Мітигація: витягнути реальний ARPPU + intent-розподіл до ще коду; вирізати email/holdout/MJML скоуп для циклу 1. - Оманлива-але-grounded AI-порада неповнолітнім: fact-валідатор ловить вигадані факти, але не mis-combined реальні (напр. «твій 169 б’є прохідний 168.5 — ти вступиш»), найгірший EdTech-провал, відправлений unseen через L3 auto-approval. Мітигація: ADR-0009 advice-correctness gate + minors guardrail + sampled human review auto-approved L3 + escaped user-контрольовані merge-поля.
- Telegram whole-bot spam restriction: unsolicited перший broadcast на ~5k користувачів (деякі давно сплячі) може тригернути report-driven обмеження всього bot-токена за ToS 5.2(b), вбиваючи транзакційні/support і open-day reminders теж — rate-budget ізоляція дає нуль захисту проти цього. Мітигація: value-first хвиля лише на недавно активних, помітний opt-out, 403-surge auto-pause, незалежний kill-switch.
Архітектурні діаграми
Section titled “Архітектурні діаграми”End-to-end send pipeline з safety rails (єдиний chokepoint)
Section titled “End-to-end send pipeline з safety rails (єдиний chokepoint)”flowchart TD
Camp[Batch Campaign<br/>frozen snapshot] --> Ren
Trig[Deadline trigger<br/>T-72h, L3] --> Ren
Ren[render: grounded merge + AI wrapper<br/>+ ADR-3 validator] --> Q[q.send.email / q.send.telegram]
Q --> R0{0 KillSwitch}
R0 -- tripped --> DEF[DEFER: re-queue]
R0 -- ok --> R1{1 Suppression<br/>Valkey cache}
R1 -- hit --> DROP[DROP + emit skip event]
R1 -- ok --> R2{2 Consent<br/>consent_current}
R2 -- no --> DROP
R2 -- ok --> R3{3 Frequency cap<br/>email<=2/wk TG<=3/wk}
R3 -- over --> DROP
R3 -- ok --> R4{4 Quiet hours<br/>22-08 Kyiv}
R4 -- inside --> DEF
R4 -- ok --> R5{5 Send window}
R5 -- outside --> DEF
R5 -- ok --> R6{6 Advice correctness L3<br/>slot-provenance + verdict + minors}
R6 -- fail --> FB[fallback to approved template]
R6 -- ok --> R7{7 Warmup budget LAST<br/>token bucket}
R7 -- no token --> DEF
R7 -- token --> TX[Transport: Brevo / @abitlybot]
TX --> LH[(engagement event<br/>lake-house FAIL-CLOSED:<br/>drop+count if prod not ready)]
TX -- 403/hard-bounce --> SUP[write suppression immediately]
Топологія даних та sync (стан → RDS, події fail-closed → lake-house)
Section titled “Топологія даних та sync (стан → RDS, події fail-closed → lake-house)”flowchart LR
subgraph RDS[shared RDS db.t4g.small abitly_prod_db SPoF]
AB[(schema abitly<br/>read-only)]
PUB[(schema public<br/>Strapi)]
MK[(schema marketing<br/>bounded pool max=5)]
end
Strapi[Strapi facts<br/>HTTP at render] --> Ren2[Context Assembler]
AB -. watermarked paged SELECT<br/>off-peak, bounded pool .-> Sync[Read-model sync worker]
Sync --> MK
Ren2 --> MK
MK --> Orch[Orchestration BullMQ on Valkey]
Valkey[(Valkey abitly-prod-cache<br/>BullMQ + suppression/consent cache<br/>+ warmup token bucket)] --- Orch
Orch --> Brevo[Brevo ESP]
Orch -->|HTTP control-plane mTLS :3001| Bot[@abitlybot runner<br/>resumable state in telegram schema]
Brevo -. delivered/open/click .-> ENG{LAKEHOUSE_PROD_READY?}
Bot -. tg_sent/blocked/click .-> ENG
ENG -- no --> DROPC[drop + count<br/>NEVER to RDS]
ENG -- yes --> Fire[Firehose -> S3 Iceberg -> Athena -> dbt -> Metabase]
Brevo -. hard-bounce/complaint .-> MK
Bot -. 403/stop .-> MK
Mono[Monobank webhook<br/>as_sid/as_cid] --> MK