Repository navigation
Offer the card trial right after a workspace's first successful call - #1078
Merged
Merged
Conversation
3 tasks done
helpcodeai
force-pushed
the
helpcode/card-trial-after-first-success
branch
from
October 13, 2026 16:37
6dad85a to
7624dc2
Compare
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
force-pushed
the
helpcode/card-trial-after-first-success
branch
from
October 13, 2026 16:47
7624dc2 to
66ecfd3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 1078merges cleanly, because the squashed commit and this branch's copy of it are identical.Follow-ups after review (latest commit):
/start-trialheadline 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.first-success-offer-visibility.ts). Playwright covers both states.FirstSuccessServiceasksCardTrialEligibilityService, resolved lazily; no answer = eligible);/api/license/card-trial-offerreturns 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 settlesskipped:no_trial_recipient).Changes
Trigger (backend,
audit/first-success.service.ts)AdsActivationService). Later calls cost nothing; after a restart, one indexedorg_settingsread.org_settings.first_successful_call_at) if missing, from the earliest SUCCESS row.triallicence and no other licence in any state), it recordsorg_settings.card_trial_offer_eligible_at(insert withON CONFLICT DO NOTHING, so it is the lock) and onecard_trial_offer_eligibleproduct event (connectorId,adapterSlug,toolName).In-app offer (frontend)
GET /api/license/card-trial-offerreturns{ 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).FirstSuccessOffercard 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.card_trial_offer_shown(once a day per user and browser),card_trial_offer_clicked,card_trial_offer_dismissed, withmetadata.via.Email (
license/card-trial-offer.service.ts, called by the hourly Cloud onboarding cron)trial_email_*flags) or an onboarding/activation nudge to an admin (onboarding_last_reminder_at,activation_reminder_at).org_settings.card_trial_offer_emailmakes it once-only (send time orskipped:<reason>); a failed send retries next run. Recordscard_trial_offer_emailed./start-trial?source=first_success, or to/settings/license?source=first_successwhen under 48 h are left.Attribution
/start-trialreads?source=(allow-listed) and sends it withPOST /api/license/checkout-link; the DTO accepts onlyfirst_success. The backend forwardssourceto the licence site's checkout intent and recordscheckout_link_created(via,kind).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 withtrial_source=first_success, paid after trial).ee/ edit (needs owner OK):
ee/cloud/onboarding-cron.service.tsgets an optionalCardTrialOfferServiceand one call after the win-back pass, plus acardTrialOfferscounter in its log. The logic itself lives in AGPL code. One test added to its spec.Type
Testing
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).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). Playwrightfirst-success-offer.spec.ts(10) +card-trial.spec.ts(15): 25 passed./health200, route mounted, no DI errors.org_settingsrows + one eligible event with metadata;currentOfferADMIN returns the connector name and trial end, EDITOR null; email pass with no SMTP →failed(unsettled); second success → still one event; after astarterlicence → offer null, email pass settlesskipped:customer.Checklist
npm test)Related Issues
Website: HelpCode-ai/anythingmcp-website#380 (stores
sourceas Stripemetadata.trial_source)