Skip to content

Offer the card trial right after a workspace's first successful call - #1078

Merged
helpcodeai merged 2 commits into
mainfrom
helpcode/card-trial-after-first-success
Oct 13, 2026
Merged

helpcodeai merged 2 commits into
mainfrom
helpcode/card-trial-after-first-success

Conversation

@helpcodeai

@helpcodeai helpcodeai commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Offer the card trial at the moment a Cloud workspace sees the product work: right after its first successful tool call. Today the card trial is only offered at sign-up, before anyone has seen any value; users who start the card trial pay at about 44%, against about 10% of activated workspaces overall.

Cloud only (DEPLOYMENT_MODE=cloud). Self-hosted is unaffected (verified by booting both modes). Nothing is added to MCP tool responses (Claude directory policy, see #914).

Stacked on #1081. This branch contains #1081's commit (the truthful card-trial eligibility). Merge #1081 first; then gh pr update-branch 1078 merges cleanly, because the squashed commit and this branch's copy of it are identical.

Follow-ups after review (latest commit):

  • No trial length is promised: banner and email say "start your trial" (the card trial ends with the free trial). The email button is "Start your trial" (or "Choose a plan" under 48 h). The /start-trial headline fix ("Your free trial" plus the real end date) is in Say before checkout when no card trial is available and the plan is billed today #1081.
  • One call to action at a time: while the offer card is on screen, the top TrialBanner keeps its countdown but drops its "Add a payment method" link (first-success-offer-visibility.ts). Playwright covers both states.
  • Never to past subscribers: a workspace whose active admins all had a subscription is not made eligible (FirstSuccessService asks CardTrialEligibilityService, resolved lazily; no answer = eligible); /api/license/card-trial-offer returns null to such an admin; the email goes only to admins who can start a card trial (no answer from the site postpones it; none left settles skipped:no_trial_recipient).

Changes

Trigger (backend, audit/first-success.service.ts)

  • After AuditService stores a SUCCESS invocation, the workspace is checked once per process, in the background (same pattern as AdsActivationService). Later calls cost nothing; after a restart, one indexed org_settings read.
  • Stores the workspace's first success time (org_settings.first_successful_call_at) if missing, from the earliest SUCCESS row.
  • If that first success is under 24 h old and the workspace is on the no-card trial (running trial licence and no other licence in any state), it records org_settings.card_trial_offer_eligible_at (insert with ON CONFLICT DO NOTHING, so it is the lock) and one card_trial_offer_eligible product event (connectorId, adapterSlug, toolName).
  • Paying, card trial (paid-plan licence while trialing), former customers, ended trials and no licence: not eligible, they keep the existing banner and wall. Workspaces activated before the deploy only get their first success stored.

In-app offer (frontend)

  • GET /api/license/card-trial-offer returns { offer: { connectorName, trialEndsAt } } for admins of an eligible workspace while a card trial can still start (48 h left), otherwise { offer: null } (non-admins, self-hosted included).
  • FirstSuccessOffer card on the dashboard, the connector list and the connect page (/mcp-server/<id>, which re-asks 3 s after the live check sees the first request). Copy: "Your AI just ran its first {connector} action." / "Keep it running: start your trial, and {connector} keeps working after your free trial ends on {date}." Buttons: "Not now" (hidden for 3 days, every page) and "Start your trial" → /start-trial?source=first_success.
  • Events: card_trial_offer_shown (once a day per user and browser), card_trial_offer_clicked, card_trial_offer_dismissed, with metadata.via.

Email (license/card-trial-offer.service.ts, called by the hourly Cloud onboarding cron)

  • One email per workspace to active, verified admins who did not unsubscribe, 30 min to 48 h after eligibility (30 to 90 min in practice with the :17 timer). Re-checked at send time; not sent once the workspace started a card trial, pays, or the trial ended.
  • Held back 12 h after a trial lifecycle or win-back email to the workspace (trial_email_* flags) or an onboarding/activation nudge to an admin (onboarding_last_reminder_at, activation_reminder_at).
  • org_settings.card_trial_offer_email makes it once-only (send time or skipped:<reason>); a failed send retries next run. Records card_trial_offer_emailed.
  • Value only, no card/charge/price words (template guard tests), no em dashes, unsubscribe line. English, like the other Cloud trial emails. Links to /start-trial?source=first_success, or to /settings/license?source=first_success when under 48 h are left.

Attribution

  • /start-trial reads ?source= (allow-listed) and sends it with POST /api/license/checkout-link; the DTO accepts only first_success. The backend forwards source to the licence site's checkout intent and records checkout_link_created (via, kind).
  • The website PR stores it on the Stripe Checkout Session and subscription as metadata.trial_source. Without it the site ignores the field, so deploy order does not matter.
  • docs/operations/card-trial-offer.md: the design, plus the weekly funnel SQL (eligible / shown / clicked / dismissed / emailed / checkout opened / subscribed) and a Stripe Search API query (card trials started with trial_source=first_success, paid after trial).

ee/ edit (needs owner OK): ee/cloud/onboarding-cron.service.ts gets an optional CardTrialOfferService and one call after the win-back pass, plus a cardTrialOffers counter in its log. The logic itself lives in AGPL code. One test added to its spec.

Type

  • New feature

Testing

  • Added/updated unit tests
  • Tested manually

Commands and results:

  • packages/backend: npx tsc --noEmit -p tsconfig.json: clean. npm test: 554 suites passed, 5 skipped; 14,308 tests passed. npm run lint: 0 errors (11 existing warnings).
  • New specs: audit/first-success.service.spec.ts (every licence state, once-only, race, stale first success, self-hosted, AuditService hook), license/card-trial-offer.spec.ts (in-app visibility per role and licence, email window, once-only, 12 h throttling for each email kind, opt-out and unverified admins, failed send retry, plan link under 48 h, step selection), license-checkout.spec.ts (source passthrough, DTO allow-list, event), email-templates.spec.ts (two new samples run through all guards + copy tests), cron order test.
  • packages/frontend: npx tsc --noEmit: clean. npm run lint: 0 errors (26 existing warnings). Playwright first-success-offer.spec.ts (10) + card-trial.spec.ts (15): 25 passed.
  • Booted the built backend in cloud and self-hosted mode against a fresh migrated local DB: /health 200, route mounted, no DI errors.
  • Ran the real path against that DB in cloud mode: SUCCESS invocation → both org_settings rows + one eligible event with metadata; currentOffer ADMIN returns the connector name and trial end, EDITOR null; email pass with no SMTP → failed (unsettled); second success → still one event; after a starter licence → offer null, email pass settles skipped:customer.
  • Measurement SQL run on scratch tables in local Postgres; the jq aggregation checked on sample data.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation (if applicable)
  • All existing tests pass (npm test)
  • This PR has a descriptive title

Related Issues

Website: HelpCode-ai/anythingmcp-website#380 (stores source as Stripe metadata.trial_source)

A Cloud workspace on the no-card trial that makes its first successful tool call becomes eligible for the card-trial offer: its admins see a dismissible card on the dashboard, the connector list and the connect page, and get one value-only email 30 to 90 minutes later. The checkout carries source=first_success to the licence site for Stripe metadata (trial_source). Nothing reaches MCP tool responses; self-hosted is unaffected.
…ver to past subscribers

The banner and the email say "start your trial" instead of "7-day": the card trial ends with the free trial. While the offer card is on screen, the top trial banner drops its own payment link. Workspaces whose admins all had a subscription are not made eligible, the in-app offer skips such an admin, and the email goes only to admins who can start a card trial.
@helpcodeai
helpcodeai force-pushed the helpcode/card-trial-after-first-success branch from 7624dc2 to 66ecfd3 Compare October 13, 2026 16:47
@helpcodeai
helpcodeai merged commit 09a2a8b into main Oct 13, 2026
13 checks passed
@helpcodeai
helpcodeai deleted the helpcode/card-trial-after-first-success branch October 13, 2026 16:51
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 13, 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