Tier Configuration Strategy: Presets vs Custom
Analysis of how to manage organization-level configurations across different subscription tiers.
Last Updated: February 2025 Decision: Tiered Presets + Enterprise Overrides
The Question
How should we handle configuration across different customer tiers?
Options:
- Per-organization custom configs (maximum flexibility)
- Fixed tier presets (operational simplicity)
- Hybrid: Presets + selective overrides (recommended)
Decision: Tiered Presets + Enterprise Overrides ✅
Standard tiers (Free, Basic, Pro, Enterprise, Enterprise Plus): Fixed presets defined in code Custom tier (White-label/On-premise): Fully configurable in database Enterprise override: Allow specific overrides for compliance/migration cases
Why This Approach Wins
1. What Enterprise Customers Actually Want
Reality check:
80% of enterprise customers want:
- ✅ "Better than standard" guarantees
- ✅ Predictable, documented behavior
- ✅ SLAs and priority support
- ❌ NOT: "Configure your own retry backoff schedule"
20% of enterprise customers need:
- ✅ Compliance-driven requirements (2-year retention)
- ✅ Migration from competitors (match exact behavior)
- ✅ Unusual use cases (white-label, on-premise)
Solution: Start with presets, add overrides for the 20%.
2. Operational Simplicity
With Presets:
// Simple to reason about
const config = TIER_PRESETS[org.tier];
// Done. Predictable behavior.
With Per-Org Configs:
// Complex to reason about
const config = await getCustomOrgConfig(org.id);
// Is it reasonable?
// Will it crash our system? (customer set 100 retries)
// Do we need to validate? (yes, complex rules)
// How do we handle migrations? (schema changes affect 1000s of configs)
3. Predictable System Capacity
Scenario: System is slow
With Presets:
- "Check: How many Enterprise Plus orgs are active?"
- "Ah, 50 orgs × 15 retries × 10-day duration = X jobs in queue"
- Predictable capacity planning
With Custom Configs:
- "Some org set retry to 30 days with 100 attempts"
- "Another org set 1-minute intervals"
- "Queue is exploding but we don't know why"
- Unpredictable, hard to debug
4. Clear Upgrade Path (Sales Perspective)
Sales pitch writes itself: Clear value at each tier, obvious upgrade reasons.
Recommended Tier Structure
Tier Presets (Defined in Code)
// config/tier-presets.ts
export const TIER_PRESETS = {
FREE: {
// Retry policy
retryDuration: 24 * 60 * 60 * 1000, // 24 hours
retryAttempts: 5,
retryIntervals: [30, 60, 120, 240, 480], // minutes
// Storage & retention
messageRetention: 7 * 24 * 60 * 60 * 1000, // 7 days
contentRetention: 7 * 24 * 60 * 60 * 1000, // 7 days
// Limits
emailsPerDay: 1000,
emailsPerMonth: 10000,
domainsLimit: 1,
contactsLimit: 1000,
// Features
webhookRetries: 3,
dedicatedIP: false,
customDomain: false,
prioritySupport: false,
slaGuarantee: null,
},
BASIC: {
retryDuration: 48 * 60 * 60 * 1000, // 48 hours
retryAttempts: 6,
retryIntervals: [15, 30, 60, 120, 240, 480],
messageRetention: 30 * 24 * 60 * 60 * 1000, // 30 days
contentRetention: 14 * 24 * 60 * 60 * 1000, // 14 days
emailsPerDay: 10000,
emailsPerMonth: 100000,
domainsLimit: 3,
contactsLimit: 10000,
webhookRetries: 5,
dedicatedIP: false,
customDomain: false,
prioritySupport: false,
slaGuarantee: null,
},
PRO: {
retryDuration: 72 * 60 * 60 * 1000, // 72 hours (industry standard)
retryAttempts: 8,
retryIntervals: [5, 15, 30, 60, 120, 240, 480, 960],
messageRetention: 90 * 24 * 60 * 60 * 1000, // 90 days
contentRetention: 30 * 24 * 60 * 60 * 1000, // 30 days
emailsPerDay: 100000,
emailsPerMonth: 1000000,
domainsLimit: 10,
contactsLimit: 100000,
webhookRetries: 10,
dedicatedIP: false,
customDomain: false,
prioritySupport: true,
slaGuarantee: null,
},
ENTERPRISE: {
retryDuration: 120 * 60 * 60 * 1000, // 5 days
retryAttempts: 12,
retryIntervals: [5, 15, 30, 60, 120, 240, 480, 960, 1440, 2880, 4320, 5760],
messageRetention: 365 * 24 * 60 * 60 * 1000, // 1 year
contentRetention: 90 * 24 * 60 * 60 * 1000, // 90 days
emailsPerDay: 1000000,
emailsPerMonth: 10000000,
domainsLimit: 50,
contactsLimit: 1000000,
webhookRetries: 15,
dedicatedIP: true,
customDomain: true,
prioritySupport: true,
slaGuarantee: 99.9,
},
ENTERPRISE_PLUS: {
retryDuration: 240 * 60 * 60 * 1000, // 10 days
retryAttempts: 15,
retryIntervals: [5, 10, 15, 30, 60, 120, 240, 480, 960, 1440, 2880, 4320, 5760, 8640, 11520],
messageRetention: 730 * 24 * 60 * 60 * 1000, // 2 years
contentRetention: 180 * 24 * 60 * 60 * 1000, // 180 days
emailsPerDay: 10000000,
emailsPerMonth: 100000000,
domainsLimit: 100,
contactsLimit: 10000000,
webhookRetries: 20,
dedicatedIP: true,
customDomain: true,
prioritySupport: true,
slaGuarantee: 99.99,
},
};
Database Schema
// prisma/schema.prisma
model Organization {
id String @id @default(cuid())
name String
tier Tier @default(FREE)
// Optional overrides (nullable = use tier defaults)
// Only populated for special cases
customRetryDuration Int? // milliseconds
customRetryAttempts Int?
customRetryIntervals Json? // [5, 10, 15, ...]
customMessageRetention Int? // milliseconds
customContentRetention Int? // milliseconds
customEmailsPerDay Int?
customEmailsPerMonth Int?
customDomainsLimit Int?
customContactsLimit Int?
customWebhookRetries Int?
// Enterprise-specific fields
dedicatedIP String?
customDomain String?
slaGuarantee Float? // e.g., 99.99
customContract Boolean @default(false)
// Governance & audit
configOverrideReason String? // "HIPAA compliance requires 2-year retention"
configApprovedBy String? // Account manager email
configApprovedAt DateTime?
configLastModified DateTime?
configModifiedBy String?
// ... other fields (users, domains, messages, etc.)
@@index([tier])
}
enum Tier {
FREE
BASIC
PRO
ENTERPRISE
ENTERPRISE_PLUS
CUSTOM // For white-label/on-premise
}
Configuration Resolution Logic
// lib/config/org-config.ts
interface OrgConfig {
// Retry policy
retryDuration: number;
retryAttempts: number;
retryIntervals: number[];
// Retention
messageRetention: number;
contentRetention: number;
// Limits
emailsPerDay: number;
emailsPerMonth: number;
domainsLimit: number;
contactsLimit: number;
// Features
webhookRetries: number;
dedicatedIP: boolean;
customDomain: boolean;
prioritySupport: boolean;
slaGuarantee: number | null;
}
export function getOrgConfig(org: Organization): OrgConfig {
// Start with tier defaults
const tierDefaults = TIER_PRESETS[org.tier];
// For CUSTOM tier or if overrides exist, merge
return {
// Retry policy
retryDuration: org.customRetryDuration ?? tierDefaults.retryDuration,
retryAttempts: org.customRetryAttempts ?? tierDefaults.retryAttempts,
retryIntervals: org.customRetryIntervals
? (org.customRetryIntervals as number[])
: tierDefaults.retryIntervals,
// Retention
messageRetention: org.customMessageRetention ?? tierDefaults.messageRetention,
contentRetention: org.customContentRetention ?? tierDefaults.contentRetention,
// Limits
emailsPerDay: org.customEmailsPerDay ?? tierDefaults.emailsPerDay,
emailsPerMonth: org.customEmailsPerMonth ?? tierDefaults.emailsPerMonth,
domainsLimit: org.customDomainsLimit ?? tierDefaults.domainsLimit,
contactsLimit: org.customContactsLimit ?? tierDefaults.contactsLimit,
// Features
webhookRetries: org.customWebhookRetries ?? tierDefaults.webhookRetries,
dedicatedIP: org.dedicatedIP ? true : tierDefaults.dedicatedIP,
customDomain: org.customDomain ? true : tierDefaults.customDomain,
prioritySupport: tierDefaults.prioritySupport,
slaGuarantee: org.slaGuarantee ?? tierDefaults.slaGuarantee,
};
}
// Validation for custom configs
export function validateCustomConfig(config: Partial<OrgConfig>): string[] {
const errors: string[] = [];
// Retry duration: Max 30 days
if (config.retryDuration && config.retryDuration > 30 * 24 * 60 * 60 * 1000) {
errors.push('Retry duration cannot exceed 30 days');
}
// Retry attempts: Max 50
if (config.retryAttempts && config.retryAttempts > 50) {
errors.push('Retry attempts cannot exceed 50');
}
// Retry intervals: Min 5 minutes
if (config.retryIntervals && config.retryIntervals.some(i => i < 5)) {
errors.push('Retry intervals must be at least 5 minutes');
}
// Message retention: Max 5 years
if (config.messageRetention && config.messageRetention > 5 * 365 * 24 * 60 * 60 * 1000) {
errors.push('Message retention cannot exceed 5 years');
}
return errors;
}
When to Use Overrides
Scenario 1: Compliance Requirements
// Large healthcare provider needs 2-year retention for HIPAA
await prisma.organization.update({
where: { id: 'hospital-org-123' },
data: {
tier: 'ENTERPRISE_PLUS',
customMessageRetention: 2 * 365 * 24 * 60 * 60 * 1000, // 2 years
customContract: true,
configOverrideReason: 'HIPAA compliance requires 2-year message retention',
configApprovedBy: 'sales@postchi.io',
configApprovedAt: new Date(),
}
});
Scenario 2: Migration from Competitor
// Customer migrating from SendGrid wants exact same retry behavior
await prisma.organization.update({
where: { id: 'sendgrid-migration-customer' },
data: {
tier: 'ENTERPRISE',
customRetryIntervals: [1, 10, 60, 240], // Match SendGrid pattern
customContract: true,
configOverrideReason: 'Migration from SendGrid - matching existing retry behavior',
configApprovedBy: 'migration-team@postchi.io',
configApprovedAt: new Date(),
}
});
Scenario 3: Beta Testing New Features
// Internal test org for new retry algorithm
await prisma.organization.update({
where: { id: 'internal-test-org' },
data: {
tier: 'PRO',
customRetryIntervals: [5, 10, 20, 40, 80], // New ML-optimized algorithm
configOverrideReason: 'Testing new ML-based retry algorithm',
configApprovedBy: 'engineering@postchi.io',
configApprovedAt: new Date(),
}
});
UI/UX Implications
Standard Tiers (Free → Enterprise Plus)
Settings page shows:
┌─────────────────────────────────────────┐
│ Retry Policy │
├─────────────────────────────────────────┤
│ Plan: Enterprise Standard │
│ │
│ ✓ Retry Duration: 5 days │
│ ✓ Max Attempts: 12 retries │
│ ✓ Message Retention: 365 days │
│ ✓ Dedicated IP: Included │
│ │
│ [Upgrade to Enterprise Plus] → │
│ Get 10-day retry & 2-year retention │
└─────────────────────────────────────────┘
Read-only with upgrade CTA.
Custom Tier
Settings page shows:
┌─────────────────────────────────────────┐
│ Retry Policy: Custom Configuration │
├─────────────────────────────────────────┤
│ Retry Duration: [10] days ✏️ │
│ Max Attempts: [15] retries ✏️ │
│ Retry Schedule: [Edit Intervals] ✏️ │
│ Message Retention: [730] days ✏️ │
│ │
│ ⚠️ Changes require approval from │
│ your account manager │
│ │
│ [Request Configuration Change] │
└─────────────────────────────────────────┘
Editable with approval workflow.
Enterprise with Override (Special Case)
Settings page shows:
┌─────────────────────────────────────────┐
│ Retry Policy: Enterprise (Custom) │
├─────────────────────────────────────────┤
│ Retry Duration: 5 days │
│ (tier default) │
│ │
│ Max Attempts: 12 retries │
│ (tier default) │
│ │
│ Message Retention: 730 days ⚡ │
│ (custom: HIPAA compliance) │
│ │
│ ℹ️ Contact your account manager to │
│ modify custom configurations │
└─────────────────────────────────────────┘
Shows which settings are customized with reason.
Governance & Approval Workflow
Configuration Change Request Process
Admin Panel for Overrides
// Internal admin panel: /admin/organizations/:id/config
<ConfigOverrideForm>
<Alert type="warning">
This organization is on {org.tier} tier.
Any overrides must have a documented business reason.
</Alert>
<FormField>
<Label>Message Retention Override</Label>
<Input
type="number"
defaultValue={org.customMessageRetention}
placeholder={`Default: ${TIER_PRESETS[org.tier].messageRetention} ms`}
/>
<Help>
Leave blank to use tier default.
Max: 5 years (1825 days)
</Help>
</FormField>
<FormField required>
<Label>Business Justification</Label>
<Textarea
placeholder="e.g., HIPAA compliance requires 2-year retention"
/>
</FormField>
<FormField required>
<Label>Approved By</Label>
<Input
type="email"
placeholder="account-manager@postchi.io"
/>
</FormField>
<Button type="submit">
Apply Configuration Override
</Button>
</ConfigOverrideForm>
Monitoring & Alerts
System Health Checks
// Cron job: Monitor unusual configurations
async function auditCustomConfigs() {
// Find orgs with high retry counts
const highRetryOrgs = await prisma.organization.findMany({
where: {
customRetryAttempts: { gte: 20 }
}
});
if (highRetryOrgs.length > 0) {
await sendAlert({
type: 'unusual_config',
message: `${highRetryOrgs.length} orgs have 20+ retry attempts configured`,
orgs: highRetryOrgs.map(o => o.id)
});
}
// Find orgs with very long retry durations
const longDurationOrgs = await prisma.organization.findMany({
where: {
customRetryDuration: { gte: 14 * 24 * 60 * 60 * 1000 } // 14+ days
}
});
if (longDurationOrgs.length > 0) {
await sendAlert({
type: 'unusual_config',
message: `${longDurationOrgs.length} orgs have 14+ day retry duration`,
orgs: longDurationOrgs.map(o => o.id)
});
}
}
Audit Trail
// Log all config changes
model ConfigAuditLog {
id String @id @default(cuid())
organizationId String
changedBy String // User email or "system"
changedAt DateTime @default(now())
field String // e.g., "customRetryDuration"
oldValue Json?
newValue Json?
reason String?
approvedBy String?
approvedAt DateTime?
organization Organization @relation(fields: [organizationId], references: [id])
@@index([organizationId, changedAt])
}
Implementation Priority
Phase 1: MVP (Launch)
- ✅ Implement 3 tiers: FREE, PRO, ENTERPRISE
- ✅ Hard-coded presets in code
- ✅ No overrides, no customization
- ✅ Simple tier selection in settings
Phase 2: Enterprise Features (Month 2-3)
- ✅ Add ENTERPRISE_PLUS tier
- ✅ Add override fields to database
- ✅ Internal admin panel for overrides
- ✅ Approval workflow
- ❌ No customer-facing customization UI
Phase 3: Self-Service Configuration (Month 4-6)
- ✅ Add CUSTOM tier
- ✅ Customer-facing configuration UI
- ✅ Validation and safety checks
- ✅ Usage analytics per config
- ✅ Configuration templates
Phase 4: Advanced Features (Month 7+)
- ✅ ML-based retry optimization
- ✅ ISP-specific retry recommendations
- ✅ A/B testing different retry strategies
- ✅ Configuration marketplace (templates)
Conclusion
Decision: Tiered Presets + Enterprise Overrides
Why it works:
- ✅ Operational simplicity (predictable behavior)
- ✅ Clear upgrade path (sales enablement)
- ✅ Flexibility for edge cases (enterprise appeal)
- ✅ System health (capacity planning)
- ✅ Governance (audit trail, approvals)
Alternative approaches considered:
- ❌ Per-org custom configs: Too complex, unpredictable
- ❌ Fixed presets only: Not flexible enough for enterprise
Implementation approach:
- Start with simple tier presets (MVP)
- Add override capability for enterprise (selective)
- Full customization only for highest tier (controlled)