Skip to content

Win back Cloud trials a day after expiry; test a €5.99 first month for private users - #958

Merged
keysersoft merged 4 commits into
mainfrom
feat/winback-first-month-test
Oct 8, 2026
Merged

keysersoft merged 4 commits into
mainfrom
feat/winback-first-month-test

Conversation

@keysersoft

Copy link
Copy Markdown
Contributor

Summary

Cloud win-back changes asked by Matteo on 8 Oct: the first win-back goes out the day after a trial ends instead of a week later, nobody who has or had a Stripe subscription or card trial gets a discount, and a 50/50 test of "Cloud Starter for €5.99 the first month" against START30 for private users.

Deploy order: the licence site's POST /api/license/subscription-history (website PR) must be live first. Until it answers, discount win-backs are postponed to the next hourly run without being flagged, so nothing wrong goes out, but a workspace still unanswered past day 7 misses its first offer.

Changes

  • Timing. First stage (START30, the test variant, or the how-to for workspaces without calls) is sent 1–7 days after expiry; the 7-day tail covers trials that ended before this ships. Same flag key trial_email_winback7, so nothing is sent twice. Second stage (WINBACK50, 30–37 days) unchanged. Copy says "ended on 7 October" instead of "a week ago" / "last week".
  • No discounts for customers.
    • A workspace with any licence other than trial, in any status (paid, card trial trialing, revoked unpaid card trial, ...) is skipped and the stage flagged. Before, only active non-trial licences were checked.
    • LicenseService.subscriptionHistory(emails) asks the licence site, with the service token, whether each recipient ever had an AnythingMCP Stripe subscription. Those recipients are dropped. Any failure or incomplete answer: nothing sent, nothing flagged, retried next run. Logs carry the workspace id, never an address.
  • A/B test (first discount only).
    • Eligible: ≥1 successful call, every active admin on a consumer email domain (src/common/consumer-email-domains.ts), and no business connector. "Business" = a catalog connector in e-commerce, CRM, accounting, ERP, payments, logistics, finance, HR, support, ITSM, wholesale, advertising, marketing automation, field service, construction, DMS, time tracking, e-signature or banking. Vinted is the exception, since its sellers are private people; Etsy sellers on Gmail are small businesses.
    • Arm from sha256(organizationId): even → first_month_599 (code WINBACK_CONSUMER_PROMO_CODE, default STARTER599), odd → start30_control. Stored in OrgSettings winback_test_arm once the email is sent; non-eligible workspaces keep START30 with no arm.
    • The cron log line counts winbackFirstMonth and winbackControl.
  • New firstMonth email: "Your first month of AnythingMCP Cloud for €5.99", then €19/month, cancel anytime from the billing portal; discount win-backs are the agreed exception to the no-price-talk rule. Admin preview samples renamed to winback-discount-first, -first-month, -final, winback-help.

ee/cloud/onboarding-cron.service.ts is edited on purpose: the Cloud win-back lives there (owner's decision for this change).

Cloud and self-hosted

Cloud only: CloudModule is loaded only with DEPLOYMENT_MODE=cloud. Self-hosted trials keep the licence site's own emails, unchanged.

Type

  • New feature

Testing

  • Added/updated unit tests

Timing window edges (0.5 d nothing, 1.2 d and 6.9 d sent, 7.1 d nothing), any non-trial licence skipped and flagged, site says true → recipient skipped, site error → nothing sent and not flagged, consumer workspace without business connector → arm by hash with the right offer and arm stored, e-commerce connector → START30 without arm, Vinted still eligible, company admin → START30, second stage unchanged, old flag respected.

In packages/backend:

Command Result
npm run lint exit 0, 12 warnings in untouched files (two unrelated --fix rewrites reverted)
npx tsc --noEmit -p tsconfig.json exit 0
npm test -- --silent 481 suites, 10,477 tests passed (357 skipped)

Related Issues

None (internal growth experiment).

Exact, case-insensitive match against free mailbox providers (Gmail, GMX, web.de, Outlook, iCloud...), to tell private users from businesses.
LicenseService.subscriptionHistory posts up to 20 addresses per call to /api/license/subscription-history with the service token. It returns null on any error or incomplete answer, so callers can send nothing and retry. Addresses are never logged.
The first win-back (30% off, or the how-to without calls) now goes out 1 to 7 days after the trial ends instead of 7 to 14, under the same flag key trial_email_winback7. The copy dates the trial instead of saying it ended a week ago.

No discount goes to a workspace with any non-trial licence, in any state (paid, card trial, revoked), and each recipient is checked against the licence site's Stripe subscription history; if the site cannot answer, nothing is sent and the stage stays open.

Consumer workspaces (all admins on free mailboxes, no business connector, Vinted excepted) that used the product are split 50/50 by a hash of the workspace id: Cloud Starter for €5.99 the first month (WINBACK_CONSUMER_PROMO_CODE, default STARTER599) or START30 as control. The arm is stored in OrgSettings winback_test_arm and counted in the cron log. Cloud only: CloudModule is not loaded on self-hosted.
@keysersoft
keysersoft merged commit 408e451 into main Oct 8, 2026
13 checks passed
@keysersoft
keysersoft deleted the feat/winback-first-month-test branch October 8, 2026 14:18
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant