Files
worldmonitor/convex
Elie Habib 3e7bf49393 refactor(emails): refresh Pro welcome email — surface WM Analyst, Widgets, MCP (#3300)
* refactor(emails): refresh Pro welcome email — surface WM Analyst, Widgets, MCP

The Pro welcome email rendered a stale 4-card 2×2 grid (Near-Real-Time,
AI Analyst, Multi-Channel Alerts, "10 Dashboards") that missed three of
the release's signature differentiators and carried a wrong stat ("10
Dashboards" — the actual Pro surface is 50+ panels per the pricing
table). Net effect: paying users got a welcome email that undersold what
they bought and didn't point at the highest-retention action (the brief).

This commit rewrites the Pro path of userWelcomeHtml + featureCardsHtml
to the "signature-first" 2×3 grid designed via the playground at
docs/plans/pro-welcome-email-playground.html:

  WM Analyst 🤖        Create Custom Widgets 🧩
  MCP Integration 🔌   Daily AI Brief ☀️
  Multi-Channel        50+ Pro Panels 📐
  Delivery 📬

Other shifts on the Pro path:
- Headline: "Welcome to {planName}!" → "Welcome to {planName} — your
  intel, delivered." — parameterized so pro_annual/pro_monthly both read
  correctly.
- CTA: "Open Dashboard" (→ /) → "Open My Brief" (→ /brief). The brief
  is the single highest-retention action for a new Pro.
- New "Invite your team" referral block above the CTA.
- New support contact line under the CTA, pointing at ADMIN_EMAIL so
  replies route correctly.

API-plan path (api_starter/api_starter_annual/api_business/enterprise)
preserved byte-for-byte — same 4 cards, same "Welcome to {planName}!"
headline, same "Open Dashboard" CTA, no referral, no support line. A
follow-up will refresh that variant with its own MCP / Webhooks / API
lead after this ships.

Size: 6.4 KB rendered (was ~4 KB; Gmail clips at ~102 KB).

Playground (for future refreshes): docs/plans/pro-welcome-email-playground.html

* fix(emails): allowlist Pro plans + drop referral block pending Phase 9

Addresses two P1 review comments from @greptile-apps on #3300.

1. `isPro` was a deny-list (`!API_PLANS.has(planKey)`) — every plan key
   outside API_PLANS fell into the Pro branch, including `free` (already
   in PLAN_DISPLAY) and any future tier (e.g. `pro_team`) added to
   PLAN_DISPLAY without a matching API_PLANS update. Switched to an
   explicit allowlist: `PRO_PLANS = Set(['pro_monthly','pro_annual'])`.
   Anything outside falls back to the neutral "Welcome to {planName}!"
   shell + "Open Dashboard" CTA.

2. The "Invite your team — earn a month free" referral block linked to
   `https://worldmonitor.app/referrals`, but that page isn't mounted and
   `src/services/referral.ts` is still flagged "Phase 9 / Todo #223".
   Shipping the block today would send paying users to a 404 and promise
   a credit the backend can't grant. Removed the block entirely;
   reinstate in a follow-up PR once the referral endpoint + credit logic
   ship.

Playground (`docs/plans/pro-welcome-email-playground.html`) remains the
source of truth for future refreshes.

* fix(emails): align card gate with shell gate + wire reply_to for support line

Addresses two more review findings on #3300.

1. `featureCardsHtml()` still branched on `API_PLANS.has(planKey)`, so
   every non-API plan (including `free` and any future tier added to
   PLAN_DISPLAY without a matching PRO_PLANS update) got the 6-card Pro
   marketing grid even though the shell gate (`userWelcomeHtml`) now
   allowlists Pro correctly. Result: `free` or unknown-tier users saw
   "Welcome to Free!" + "Open Dashboard" but still received "WM Analyst",
   "Create Custom Widgets", "MCP Integration", etc. card content.

   Fixed by parallel-allowlisting on `!PRO_PLANS.has(planKey)` — falls
   through to the 4-card generic grid (same cards the API variant shows)
   for anyone who isn't on pro_monthly / pro_annual. The generic grid is
   safe for unknown tiers: no Pro-only claims, no specific promises.

2. The Pro support line reads "Questions? Reply to this email or ping
   elie@worldmonitor.app" but FROM is `noreply@worldmonitor.app` and the
   Resend payload omitted reply_to — replies would bounce.

   Added an optional `replyTo` param to `sendEmail()` and thread
   ADMIN_EMAIL through on the user welcome call. Gmail and every other
   major client honour Reply-To over From when both are present.

   The admin notification intentionally passes no replyTo to avoid
   routing replies back to ADMIN_EMAIL (self-loop).
2026-04-22 23:18:32 +04:00
..