Add Scale plan ($29.99/mo, 25k SMS/mo, 15 devices) between Pro and
Custom in the subscription priority chain. Update dashboard upgrade
prompts to surface Scale for Pro users approaching their monthly limit,
and expose Scale upgrade links in subscription-info and account-settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add deviceLimit to plans (default -1 = unlimited) with per-subscription
customDeviceLimit override, resolved in getEffectiveLimits and exposed
via the usage object. Gateway blocks device creation and disabled to
enabled transitions with 429 once the enabled-device count reaches the
limit; already-enabled devices are never affected and the check fails
open on lookup errors. Send a throttled device_limit_reached email
notification and show approaching/reached banners with an upgrade CTA
in the dashboard device list.
Also replace the isYearly checkout field with billingInterval
('monthly' | 'yearly') across DTO, service, and checkout page (legacy
?billing= param still accepted until the marketing site redeploys).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Order the polar products array from the isYearly flag so the chosen
interval is preselected at checkout, forward the ?billing= param from
the checkout page as isYearly, and only reuse cached checkout sessions
that match the requested plan and billing interval and are neither
completed nor abandoned.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kotlin migration (phases 3-5):
- Port all DTOs, helpers, models, workers, receivers, and services from Java to Kotlin
- Room DB files ported to Kotlin with all logic kept commented out (not yet enabled)
- Add SMSFilterScreen and SMSFilterViewModel in Compose (replaces Java SMSFilterActivity in new UI)
- All helpers exposed as Kotlin objects with @JvmStatic for Java interop
Onboarding improvements:
- Rewrote copy on all 5 onboarding screens (Welcome, Credentials, DeviceSetup, Permissions, SetupComplete)
- Added receive SMS toggle on SetupCompleteScreen (defaults on)
- Gateway now set to enabled by default after successful registration
Dashboard improvements:
- Add receive SMS toggle and SIM subscription ID display in device card
- Add permission warning card when SMS permissions are missing
- Remove all-time stats section (replaced by subscription usage bars)
- Trim Quick Actions to Dashboard + Docs; move Get Support and Share to Settings
- Merge user greeting into TopAppBar subtitle
- Device ID now has inline copy button
Dashboard UI polish (community review fixes):
- Redundant Enabled badge removed; only shows when gateway is Disabled
- Add Gateway label below the main switch for clarity
- Replace infinity symbol with Unlimited in usage display
- SIM subscription IDs use neutral color instead of primary/orange
- Status bar matches background color instead of primary orange
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pnpm v10 is now the npm "latest" tag but generates a different lockfile
format than v9.0 used in this repo, causing --frozen-lockfile to fail.
Replace corepack pnpm@latest with npm install -g pnpm@9 in api and web
Dockerfiles. Also bump docker/* actions to v3/v6 to resolve Node.js 20
deprecation warnings ahead of the June 2026 forced migration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add fourth_reminder to AbandonedEmailType and schema enum
- Replace expiry-based timing with createdAt-based timing so the
schedule is independent of Stripe session expiry windows
- Register all 6 emails in emailSchedule with correct delays:
10 min, 1 hr, 24 hr, 3 d, 7 d, 14 d after session creation
- Add isCompleted filter to query so paid users are never emailed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Complete rewrite of all 6 abandoned checkout templates for better
conversion and deliverability:
- Remove social media icons from all templates (promotions tab signal)
- Remove fake discounts, fabricated testimonials, and unverifiable
claims (money-back guarantee, Calendly link)
- Give each email a distinct purpose: recovery nudge (10min), feature
comparison table (1hr), cost objection handling (24hr), personal
founder message (3d), honest comparison + low pressure (7d),
graceful farewell + feedback ask (14d)
- Add opt-out notice to every email footer (required for marketing emails)
- Fix spam trigger subject '⏰ Your textbee pro upgrade is waiting!'
to 'Your TextBee checkout is still open'
- Standardise year to 2026 and brand to on-brand orange throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added `lastEnabledAt` property to track when a user re-enables a previously disabled webhook, preventing immediate auto-disable due to historical failures.
- Updated `WebhookService` to set `lastEnabledAt` when a webhook is re-enabled and adjusted the auto-disable logic to respect a grace period based on this new property.
- Updated device query in GatewayService to cast the filter object, addressing a type collision with the reserved `model` field in Mongoose 9.6. This change maintains the runtime behavior while ensuring type compatibility.
- Updated formatDate function to handle undefined and invalid date strings.
- Introduced buildDeviceLabel function to construct device labels more robustly.
- Refactored deviceName mapping in ProductClient to utilize the new buildDeviceLabel function.
- Removed READ_SMS permission from AndroidManifest and AppConstants.
- Updated SMSBroadcastReceiver intent filter priority from maximum to a standard value.
Replace the monolithic h-64 skeleton with GetStartedCardSkeleton that mirrors
the card header and step list so first paint matches the loaded layout.
Made-with: Cursor
Mongoose 9 updateOne expects an ObjectId for _id; webhookSubscriptionId
was inferred as ObjectId | WebhookSubscription in CI, causing TS2769.
Use webhookSubscription._id after findById instead.
Made-with: Cursor
Add polarCustomerPortalRequestUrl() pointing to /portal/request with a
URL-encoded email query when available. Wire Manage Subscription links in
account settings and subscription info; subscription info loads currentUser
via whoAmI (shared React Query cache with account settings).
Made-with: Cursor
- Added a new method in AuthService to find active API keys using a masked match and fallback to regex.
- Updated OptionalAuthGuard and AuthGuard to utilize the new method for improved API key validation.
- Introduced an index on the apiKey field in the ApiKey schema for optimized query performance.
- Added a query parameter to filter API keys by status (active, revoked, all) in the getApiKey endpoint.
- Updated the AuthService to handle status filtering logic for API key retrieval.
- Modified the frontend to support status-based API key listing and added a button to view revoked keys.