Overview
TendSocial's admin configuration system allows Super Admins to manage all platform settings, API keys, feature flags, and email templates without requiring code changes or deployments.
All configuration is stored in the database with 60-second LRU caching for performance, and critical keys can automatically sync to Google Secret Manager for disaster recovery.
Architecture
Database Tables
SystemSettings
Singleton table for app-wide configuration.
CREATE TABLE "SystemSettings" (
id TEXT PRIMARY KEY DEFAULT 'default',
-- App Configuration
appName TEXT DEFAULT 'TendSocial',
appUrl TEXT DEFAULT 'https://app.tendsocial.com',
supportEmail TEXT DEFAULT 'support@tendsocial.com',
alertEmail TEXT DEFAULT 'alerts@tendsocial.com',
-- Signup & Onboarding
allowSignups BOOLEAN DEFAULT true,
requireEmailVerification BOOLEAN DEFAULT true,
defaultTrialDays INTEGER DEFAULT 14,
-- Default Limits
maxTeamMembersDefault INTEGER DEFAULT 10,
maxSocialAccountsDefault INTEGER DEFAULT 5,
-- AI Budget Controls
dailyAIBudgetUSD DECIMAL DEFAULT 50.00,
monthlyAIBudgetUSD DECIMAL DEFAULT 1000.00,
updatedAt TIMESTAMP
);EmailTemplate
Customizable email templates with variable substitution.
CREATE TABLE "EmailTemplate" (
id TEXT PRIMARY KEY,
name TEXT UNIQUE,
description TEXT,
subject TEXT, -- Can include {{variables}}
htmlBody TEXT, -- HTML template
textBody TEXT, -- Plain text fallback
variables JSON, -- ["varName1", "varName2"]
category TEXT, -- 'team', 'billing', 'alerts', 'notifications'
isActive BOOLEAN DEFAULT true,
createdAt TIMESTAMP,
updatedAt TIMESTAMP
);BillingConfig
Payment provider configuration.
CREATE TABLE "BillingConfig" (
id TEXT PRIMARY KEY DEFAULT 'default',
provider TEXT DEFAULT 'lemonsqueezy',
apiKey TEXT,
storeId TEXT,
webhookSecret TEXT,
testMode BOOLEAN DEFAULT false,
variantMapping JSON, -- {"variant_123": "starter"}
currency TEXT DEFAULT 'USD',
taxEnabled BOOLEAN DEFAULT false,
updatedAt TIMESTAMP
);FeatureFlag
Toggle features globally or per segment/user.
Note: The Feature Flag system has been significantly enhanced. See Platform Console Documentation for the complete schema including Segments, Entitlements, and Packages.
Key tables:
FeatureFlag- Flag definitions and enabled stateSegment- User/company groups for targetingFeatureFlagSegment- Join table linking flags to segmentsFeatureFlagUserOverride- Per-user overridesEntitlementFeature- Commercial feature definitionsPlanEntitlement- Feature-to-plan mappingsPackageConfig- Subscription tier definitions
IntegrationConfig
Third-party service credentials.
CREATE TABLE "IntegrationConfig" (
id TEXT PRIMARY KEY,
service TEXT UNIQUE,
displayName TEXT,
isEnabled BOOLEAN DEFAULT false,
apiKey TEXT,
apiSecret TEXT,
webhookUrl TEXT,
settings JSON,
rateLimit INTEGER,
updatedAt TIMESTAMP
);Configuration Service
ConfigService
Central service for all configuration management with caching.
File: src/services/config.service.ts
import { LRUCache } from 'lru-cache';
import prisma from '../infra/prisma.js';
const configCache = new LRUCache<string, any>({
max: 100,
ttl: 60 * 1000, // 60 seconds
});
export class ConfigService {
// System Settings
static async getSystemSettings();
static async updateSystemSettings(updates);
// Billing Config
static async getBillingConfig();
static async updateBillingConfig(updates);
// Email Templates
static async getEmailTemplate(name);
static async getEmailTemplates();
static async updateEmailTemplate(id, updates);
// Feature Flags
static async isFeatureEnabled(featureName, companyId?, tier?);
static async getFeatureFlags();
static async updateFeatureFlag(id, updates);
// Integration Configs
static async getIntegrationConfig(service);
static async getIntegrationConfigs();
static async updateIntegrationConfig(id, updates);
// Cache Management
static clearCache();
}Usage Examples
Get System Settings
import { configService } from './services/config.service.js';
const settings = await configService.getSystemSettings();
console.log(settings.appName); // 'TendSocial'
console.log(settings.defaultTrialDays); // 14Check Feature Flag
const hasMultiAgent = await configService.isFeatureEnabled(
'multi_agent_system',
companyId,
'professional'
);
if (hasMultiAgent) {
// Enable multi-agent features
}Get Integration API Key
const resendConfig = await configService.getIntegrationConfig('resend');
if (resendConfig?.isEnabled && resendConfig.apiKey) {
const resend = new Resend(resendConfig.apiKey);
}Google Secret Manager Sync
Overview
When API keys are updated via Admin UI, they automatically sync to Google Secret Manager (GSM). This provides:
- Disaster Recovery: If database is compromised, keys still available
- Version Control: GSM keeps all versions, easy rollback
- Audit Trail: Track all key changes
- Single Source of Truth: Update in UI, GSM automatically updated
Configuration
Enable GSM sync via environment variables:
GSM_SYNC_ENABLED=true
GCP_PROJECT_ID=your-project-idSecret Mappings
Integration Configs:
| Service | Database Field | GSM Secret Name |
|---|---|---|
| resend | apiKey | RESEND_API_KEY |
| tavily | apiKey | TAVILY_API_KEY |
| anthropic | apiKey | ANTHROPIC_API_KEY |
| openai | apiKey | OPENAI_API_KEY |
| apiKey | GEMINI_API_KEY | |
| newsapi | apiKey | NEWS_API_KEY |
| slack | webhookUrl | SLACK_WEBHOOK_URL |
Billing Config:
| Field | GSM Secret Name |
|---|---|
| apiKey | LEMONSQUEEZY_API_KEY |
| storeId | LEMONSQUEEZY_STORE_ID |
| webhookSecret | LEMONSQUEEZY_WEBHOOK_SECRET |
Sync Behavior
- Update Triggered: Admin updates key via UI
- Database Updated: Primary source of truth
- GSM Sync: New version created in GSM
- Failure Handling: If GSM sync fails, database update still succeeds (logged)
Rollback Process
If a bad key is pushed, rollback in GSM:
# List all versions
gcloud secrets versions list RESEND_API_KEY
# Enable previous version
gcloud secrets versions enable 2 --secret=RESEND_API_KEY
# Disable current version
gcloud secrets versions disable 3 --secret=RESEND_API_KEYGSMSyncService
File: src/services/gsmSync.service.ts
export class GSMSyncService {
// Check if sync is enabled
isEnabled(): boolean;
// Sync integration API key
async syncIntegrationConfig(service, apiKey);
// Sync billing config
async syncBillingConfig(updates);
// Test connection
async testConnection();
}Feature Flags
Pre-configured Flags
| Flag Name | Description | Default Tiers |
|---|---|---|
ai_content_generation | AI-powered content generation | starter, professional, agency, enterprise |
multi_agent_system | Multi-agent research system | professional, agency, enterprise |
trend_monitoring | Automatic trend detection | professional, agency, enterprise |
auto_pilot | Automatic content scheduling | agency, enterprise |
performance_predictions | Post performance predictions | professional, agency, enterprise |
bio_management | Unified bio management | starter, professional, agency, enterprise |
team_collaboration | Team features & workflows | professional, agency, enterprise |
advanced_analytics | Detailed analytics & reports | professional, agency, enterprise |
white_label | White label customization | enterprise |
api_access | External API access | agency, enterprise |
Usage in Code
// Middleware to check feature access
async function requireFeature(featureName: string) {
return async (request, reply) => {
const { companyId } = request.user;
const company = await prisma.company.findUnique({
where: { id: companyId },
select: { subscriptionTier: true },
});
const hasAccess = await configService.isFeatureEnabled(
featureName,
companyId,
company.subscriptionTier
);
if (!hasAccess) {
return reply.status(403).send({
error: 'Feature not available on your plan'
});
}
};
}
// Use in routes
app.post('/api/ai/multi-agent',
{ preValidation: [verifyJwt, requireFeature('multi_agent_system')] },
async (request, reply) => {
// Multi-agent logic
}
);Email Templates
Pre-configured Templates
- team_invite - Team invitation emails
- cost_alert - AI cost limit alerts
- new_message - Inbox message notifications
Variables
Templates support syntax:
team_invite:
- Person sending invitation- Company name- Invitation acceptance URL- Application name
cost_alert:
- Type of alert- Company name- Current cost in cents- Cost limit in cents
new_message:
- Message sender- Social platform- Message preview text- Link to inbox
Usage
import { emailService } from './services/email.service.js';
// Send using database template
await emailService.sendFromTemplate('team_invite', userEmail, {
inviterName: 'John Doe',
companyName: 'Acme Corp',
inviteUrl: 'https://app.tendsocial.com/invite/abc123',
appName: settings.appName,
});
// Or use predefined method
await emailService.sendTeamInvite({
toEmail: userEmail,
inviterName: 'John Doe',
companyName: 'Acme Corp',
inviteToken: 'abc123',
});Configuration Priority
Services check configuration in this order:
- Database (Primary)
- Environment Variables (Fallback)
- Defaults (Hardcoded)
Example:
// Email service checks:
const apiKey =
config?.apiKey || // 1. Database
process.env.RESEND_API_KEY || // 2. Environment
null; // 3. Error if missingThis allows:
- Hot configuration updates (no restart)
- Graceful fallback if database unavailable
- Easy local development (use env vars)
Best Practices
Security
- API Keys: Never expose in frontend, always fetch server-side
- Encryption: Consider encrypting sensitive fields in database
- Access Control: Only Super Admins can modify config
- Audit Logging: Log all configuration changes
Performance
- Caching: 60s TTL prevents repeated database queries
- Selective Invalidation: Only clear specific cache keys on update
- Lazy Loading: Load config only when needed
Deployment
- Bootstrap: Seed default config on first deployment
- Migration: Run migration to create tables
- Validation: Test GSM sync before enabling in production
- Monitoring: Alert on config changes
Troubleshooting
Config Not Updating
Clear Cache:
bashcurl -X POST /api/admin/config/cache/clearCheck Database:
sqlSELECT * FROM "SystemSettings" WHERE id = 'default';Verify Permissions: Ensure user has
isSuperAdmin: true
GSM Sync Failing
Check Status:
bashcurl /api/admin/config/gsm/statusVerify Environment:
bashecho $GSM_SYNC_ENABLED # Should be 'true' echo $GCP_PROJECT_ID # Should be setCheck Permissions: Service account needs
roles/secretmanager.adminManual Sync: Update key again via Admin UI to retry
See Also
- Platform Console Guide - Feature Flags, Segments, Entitlements, Packages
- Admin API Reference
- Feature Flags Endpoints
- Environment Variables