Plan-Based Feature Gating — v1.0.9
Plan-Based Feature Gating
Released in v1.0.9
NurtureHub enforces plan limits and feature access server-side at the tRPC layer. This means every plan boundary — contact caps, sequence caps, CRM access, white-label footers — is validated on the server regardless of what the client sends.
How Feature Gating Works
When an authenticated request reaches a gated tRPC procedure, the server:
- Looks up the account's active plan via the
subscriptiontable. - Reads current usage from the
usageEventstable. - Compares usage against the plan's defined limits.
- Either proceeds with the request or returns a structured error with an
upgradeRequiredflag.
Client-side UI reflects these server-enforced states but is not the enforcement mechanism.
Enforced Limits
Contact Count
Each plan has a maximum number of contacts. Attempting to create a contact when the limit is reached returns a BAD_REQUEST error. The account must upgrade or remove existing contacts before adding new ones.
Sequence Count
Each plan has a maximum number of active nurture sequences. Creating a sequence beyond this limit is rejected at the API layer.
CRM Integrations (Growth+)
Access to CRM integrations — including native agentOS sync and open-API connections to Reapit, Alto, Street, and Loop — requires a Growth plan or higher. Starter accounts receive a FORBIDDEN error when attempting to connect or use a CRM integration.
White-Label Email Footers (Agency Pro only)
Removing NurtureHub branding from outgoing email sequences is an Agency Pro-only feature. Accounts on Starter or Growth plans will see an upgrade prompt in the email settings UI.
Plan Limit Summary
| Feature | Starter | Growth | Agency Pro |
|---|---|---|---|
| Contact limit enforcement | ✅ | ✅ | ✅ |
| Sequence limit enforcement | ✅ | ✅ | ✅ |
| CRM integrations | ❌ | ✅ | ✅ |
| White-label email footers | ❌ | ❌ | ✅ |
The usageLimits Query
The billing tRPC router exposes a usageLimits query that returns the account's current usage and plan limits in a single call.
Request
const limits = await trpc.billing.usageLimits.query();
Response Shape
{
contacts: {
used: number, // Current number of contacts
limit: number, // Plan maximum (-1 = unlimited)
percent: number // Percentage used (0–100)
},
sequences: {
used: number,
limit: number,
percent: number
},
crmIntegrations: {
allowed: boolean // true if plan is Growth or Agency Pro
},
whiteLabelFooters: {
allowed: boolean // true if plan is Agency Pro
}
}
Data Sources
subscriptiontable — determines the active plan and its defined limits.usageEventstable — provides current usage counts.
No additional tables or schema changes were introduced in this release.
Upgrade Prompts
The UI surfaces upgrade prompts based on the usageLimits response:
| State | Trigger | Behaviour |
|---|---|---|
| Approaching limit | Usage ≥ 80% of limit | Inline warning with an upgrade call-to-action |
| Limit reached | Usage = 100% of limit | Action blocked; hard upgrade prompt shown in place of the action button |
| Feature not on plan | allowed: false | Inline upgrade prompt shown on the relevant settings or action surface |
Call billing.usageLimits on dashboard load to ensure upgrade prompt state is primed before the user encounters a blocked action.
Error Handling
When a gated action is blocked, the tRPC procedure throws a structured error. Example:
// Contact limit reached
{
code: 'BAD_REQUEST',
message: 'Contact limit reached for your current plan.',
data: {
upgradeRequired: true,
limitType: 'contacts'
}
}
// Feature not on plan
{
code: 'FORBIDDEN',
message: 'CRM integrations are available on Growth and above.',
data: {
upgradeRequired: true,
limitType: 'crmIntegrations'
}
}
Handle these errors in your client code to surface the appropriate upgrade prompt rather than a generic error message.