Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Lands the §9.4 operability tier of postal/mailAudit.md as one PR on top of the §9.3 stack (PR #101). Seven items, all independent of each other but sharing the same notifications surface. #15 Postal send_limit fuse — set per-hour cap (already applied LIVE in DB: servers.send_limit = 500). A runaway loop now hits Postal's own throttle long before flooding recipient ISPs into a rate-limit penalty. #16 noreply@sase.tr decommission — change default fromAddress in both email.service.ts and config/configuration.ts to destek@sase.tr. `noreply@` had no inbound route so user replies bounced; `destek@` lands in the SnappyMail destek inbox. Overridable via POSTAL_FROM_ADDRESS env per workflow that genuinely shouldn't accept replies. #21 Welcome CTA fallback — flip the {{else}} branch in novu-welcome.html from https://sase.tr to https://sase.tr/dashboard (the actual onboarding entry, not the marketing page). Already pushed live to Novu Mongo too. #17 Open-pixel embed — new buildTrackPixelUrl() in novu.ts; injected trackPixel payload into welcome/trial-ending/win-back/referral×3/ payment×2 NovuService methods + lifecycle-email.processor. Auth flows (email-verification, password-reset) deliberately skip the pixel. Templates updated with {{#if trackPixel}}<img ...>{{/if}} just before the footer; 8 templates touched, 2 (auth) skipped. Novu Mongo updated. #18 Signed-URL exp / replay-resistance — track.sase.tr Worker /c endpoint now expects `e=<unix-ms>` + `s=HMAC(MID|TARGET|EXP)`. Expired signatures return 410. Legacy signatures (no `e=`) still accepted while in-flight mail with old links drains; remove that branch ~30 days post-deploy. buildTrackedUrl() now mints exp=now+30d. #19 D1 retention cron — Cloudflare Cron Trigger added to mailtrack worker (`17 4 * * *` UTC, after Europe/Istanbul cron settles). `scheduled` handler DELETEs events older than RETENTION_DAYS (default 90). Both code and the cron schedule are LIVE on the production worker. #20 Lifecycle sent-flag idempotency — new `lifecycle_email_sent` table (migration 0012) keyed (user_id, workflow). Replaces the 1-day endDate window's at-most-once trick that lost cohorts on skipped days. Cron now LEFT JOINs and writes the row immediately after each successful trigger. Historical seed in scripts/backfill-lifecycle-sent.sql (19 trial-ending + 5 win-back users — generated from postal-server-1.messages) so the first post-deploy cron doesn't re-send to users we already mailed. ## Live infrastructure (deploy-independent) - Postal MariaDB: `UPDATE servers SET send_limit = 500`. - Cloudflare Worker mailtrack redeployed with new /c logic + scheduled handler. - Cloudflare Worker: cron `17 4 * * *` registered on production env. - Cloudflare Worker: RETENTION_DAYS=90 plain_text binding. - Novu Mongo: 16 messagetemplates updated with pixel + Welcome /dashboard. ## Companion deploy steps post-merge 1. `pnpm db:generate` to refresh drizzle snapshots for 0011 + 0012. 2. Run `scripts/backfill-lifecycle-sent.sql` against prod + dev BEFORE the first cron tick post-deploy. 3. Apply host-side novu-patches/apply-headers-patch.sh again if Novu container rolled (idempotent). ## Verification curl /c?…e=<future>… ⇒ 302 new-style signature accepted curl /c?…(no e)… ⇒ 302 legacy signature still accepted (drain) curl /c?…e=<past>… ⇒ 410 expired signature rejected curl /c?…s=bad… ⇒ 403 bad signature rejected CF API schedules ⇒ `17 4 * * *` live on mailtrack worker. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
97 lines
1.9 KiB
JSON
97 lines
1.9 KiB
JSON
{
|
|
"version": "7",
|
|
"dialect": "postgresql",
|
|
"entries": [
|
|
{
|
|
"idx": 0,
|
|
"version": "7",
|
|
"when": 1778535325959,
|
|
"tag": "0000_brief_guardian",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 1,
|
|
"version": "7",
|
|
"when": 1747039600000,
|
|
"tag": "0001_charming_quicksand",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 2,
|
|
"version": "7",
|
|
"when": 1778584574615,
|
|
"tag": "0002_boring_the_stranger",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 3,
|
|
"version": "7",
|
|
"when": 1778587000000,
|
|
"tag": "0003_query_log_insights",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 4,
|
|
"version": "7",
|
|
"when": 1778709949622,
|
|
"tag": "0004_hot_quicksilver",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 5,
|
|
"version": "7",
|
|
"when": 1779054231051,
|
|
"tag": "0005_past_elektra",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 6,
|
|
"version": "7",
|
|
"when": 1779086009525,
|
|
"tag": "0006_chilly_dark_phoenix",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 7,
|
|
"version": "7",
|
|
"when": 1779573406942,
|
|
"tag": "0007_natural_red_wolf",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 8,
|
|
"version": "7",
|
|
"when": 1779633572753,
|
|
"tag": "0008_common_sumo",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 9,
|
|
"version": "7",
|
|
"when": 1779703007129,
|
|
"tag": "0009_referral_rewards_rework",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 10,
|
|
"version": "7",
|
|
"when": 1780281600000,
|
|
"tag": "0010_dedupe_parts",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 11,
|
|
"version": "7",
|
|
"when": 1780572179333,
|
|
"tag": "0011_email_preferences",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 12,
|
|
"version": "7",
|
|
"when": 1780581755559,
|
|
"tag": "0012_lifecycle_email_sent",
|
|
"breakpoints": true
|
|
}
|
|
]
|
|
} |