Phase 13: A/B testing, API keys, API sending & MJML (v0.15.0) #69

Merged
vmanne-poc merged 14 commits from phase-13 into main 2026-10-26 09:14:15 -04:00
Owner

Summary

Phase 13 (v0.15.0): A/B testing, project API keys, API sending providers, and MJML.

  • A/B testing (#20): variants B–D (subject, preview text, content), a 1–50 % test group, automatic winner by opens or clicks after a wait, or a manual pick; the remainder gets the winner. Per-variant report section, variant test sends.
  • Project API keys (#29): owner-created, shown once, SHA-256 hashed, revocable, max 50 active, per-key rate limit; Authorization: Bearer postak_… on the project's contacts, groups, segments, campaigns and read-only senders/templates routes. API keys section in Project settings.
  • API sending providers (#49): Amazon SES API v2 (in-house SigV4), SendGrid, Mailgun, Postmark over fetch, no SDKs. Write-only credentials, provider tracking off where possible.
  • MJML (#50): MJML campaigns and templates ({{ content }} slot), compiled in a sandboxed worker with size and time limits; 4 MB JSON limit on campaign/template routes for Bearer requests, nginx rate limit on those routes.
  • Two additive migrations: 20261022100000_ab_testing, 20261022110000_api_keys. One new dependency: mjml@5.4.1 (exact, MIT; npm audit 0 before and after, permissive licences only).
  • Release commit: CHANGELOG [0.15.0], README, backend and frontend version 0.15.0.

Tests

  • Backend unit: 29 suites, 366 passed, 0 failed.
  • Migration checks (legacy adoption and fresh reset): all 11 migrations applied, both drift checks clean.
  • Backend e2e: 68 tests, 67 passed, 1 failed. The failure is the known-flaky rate-limits sign-in and form submissions per client address (expected 429, got 401 at the 4th login: bcrypt logins outlast the 1.5 s window on the loaded machine). Unchanged from main; Phase 13 doesn't touch auth or the throttler.
  • Frontend (tsc + vite build + Vitest): 25 files, 159 passed, 0 failed.
  • Docker images build; nginx -t passes.
  • Against the Phase 12.5 baseline (145 unit, 63 e2e, 121 frontend): +221 unit, +5 e2e, +38 frontend.

Smoke (dev stack, port 3000)

55 of 55 checks passed, 0 failed. Live dev DB backed up (pg_dump -Fc) and migrated with migrate deploy; drift check empty, migrate status up to date. Covered: API key create/list/use/cross-project 404/403/401/revoke, A/B auto-winner with the remainder sent exactly once and a 409 on a later manual pick, MJML preview/template/send (pixel, tracked links, unsubscribe, preheader, clean text part), a SendGrid sender against a local mock (one request per contact, provider tracking off), the request size and nginx rate limits, and 11 headless Edge pages with 0 JS errors.

Not verified: real delivery through Amazon SES API, SendGrid, Mailgun or Postmark (no credentials), and MJML rendering in real mail clients (checked as HTML in Mailpit).

Closes #20
Closes #29
Closes #49
Closes #50

## Summary Phase 13 (v0.15.0): A/B testing, project API keys, API sending providers, and MJML. - **A/B testing (#20):** variants B–D (subject, preview text, content), a 1–50 % test group, automatic winner by opens or clicks after a wait, or a manual pick; the remainder gets the winner. Per-variant report section, variant test sends. - **Project API keys (#29):** owner-created, shown once, SHA-256 hashed, revocable, max 50 active, per-key rate limit; `Authorization: Bearer postak_…` on the project's contacts, groups, segments, campaigns and read-only senders/templates routes. API keys section in Project settings. - **API sending providers (#49):** Amazon SES API v2 (in-house SigV4), SendGrid, Mailgun, Postmark over `fetch`, no SDKs. Write-only credentials, provider tracking off where possible. - **MJML (#50):** MJML campaigns and templates (`{{ content }}` slot), compiled in a sandboxed worker with size and time limits; 4 MB JSON limit on campaign/template routes for Bearer requests, nginx rate limit on those routes. - Two additive migrations: `20261022100000_ab_testing`, `20261022110000_api_keys`. One new dependency: `mjml@5.4.1` (exact, MIT; `npm audit` 0 before and after, permissive licences only). - Release commit: CHANGELOG `[0.15.0]`, README, backend and frontend version 0.15.0. ## Tests - Backend unit: **29 suites, 366 passed, 0 failed**. - Migration checks (legacy adoption and fresh reset): all 11 migrations applied, both drift checks clean. - Backend e2e: **68 tests, 67 passed, 1 failed**. The failure is the known-flaky `rate-limits sign-in and form submissions per client address` (`expected 429, got 401` at the 4th login: bcrypt logins outlast the 1.5 s window on the loaded machine). Unchanged from `main`; Phase 13 doesn't touch auth or the throttler. - Frontend (tsc + vite build + Vitest): **25 files, 159 passed, 0 failed**. - Docker images build; `nginx -t` passes. - Against the Phase 12.5 baseline (145 unit, 63 e2e, 121 frontend): +221 unit, +5 e2e, +38 frontend. ## Smoke (dev stack, port 3000) **55 of 55 checks passed, 0 failed.** Live dev DB backed up (`pg_dump -Fc`) and migrated with `migrate deploy`; drift check empty, `migrate status` up to date. Covered: API key create/list/use/cross-project 404/403/401/revoke, A/B auto-winner with the remainder sent exactly once and a 409 on a later manual pick, MJML preview/template/send (pixel, tracked links, unsubscribe, preheader, clean text part), a SendGrid sender against a local mock (one request per contact, provider tracking off), the request size and nginx rate limits, and 11 headless Edge pages with 0 JS errors. Not verified: real delivery through Amazon SES API, SendGrid, Mailgun or Postmark (no credentials), and MJML rendering in real mail clients (checked as HTML in Mailpit). Closes #20 Closes #29 Closes #49 Closes #50
vmanne-poc deleted branch phase-13 2026-10-26 09:14:15 -04:00
Sign in to join this conversation.
No reviewers
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
vmanne-poc/Postak!69
No description provided.