Skip to main content

Database Schema

Complete database structure for the FlowCampaign email campaign platform.

Database: SQLite 3.x (compatible with Turso/Cloudflare D1)
ORM: Drizzle ORM with TypeScript types
Last Updated: July 13, 2026

All tables support full CRUD operations through the API layer with appropriate Row Level Security (RLS) policies in production.

Entity Relationship Diagram​

Tables Overview​

1. campaigns​

Stores email campaign definitions and metadata.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
nametextNO-Campaign display name
subjecttextNO-Email subject line
from_nametextNO-Sender display name
reply_totextNO''Reply-to email address
provider_idtextNO-FK → providers.id
statustextNO'draft'Campaign status
delay_secondsintegerNO0Delay between sends (seconds)
retry_countintegerNO3Max retry attempts
html_contenttextNO-Email HTML content
total_recipientsintegerNO0Total recipients count
sent_recipientsintegerNO0Sent recipients count
createdintegerNO-Creation timestamp (Unix)
updatedintegerNO-Update timestamp (Unix)

Status Values: draft, scheduled, sending, sent, failed, cancelled

Indexes:

  • idx_campaigns_status ON (status)
  • idx_campaigns_created ON (created)

Foreign Keys:

  • campaigns_provider_id_fkey → providers(id)

2. campaign_recipients​

Tracks individual email recipients and delivery status.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
campaign_idtextNO-FK → campaigns.id
contact_idtextNO-FK → contacts.id
emailtextNO-Recipient email address
first_nametextNO''Recipient first name
last_nametextNO''Recipient last name
companytextNO''Recipient company
custom_fieldstextNO'{}'JSON custom field data
statustextNO'queued'Delivery status
delivery_idtextYES-Provider delivery ID
error_messagetextYES-Delivery error message
sent_atintegerYES-Sent timestamp (Unix)
opened_atintegerYES-Opened timestamp (Unix)
clicked_atintegerYES-Clicked timestamp (Unix)

Status Values: queued, sending, sent, delivered, opened, clicked, bounced, complained, failed

Indexes:

  • idx_recipients_campaign ON (campaign_id)
  • idx_recipients_status ON (status)

Foreign Keys:

  • campaign_recipients_campaign_id_fkey → campaigns(id)
  • campaign_recipients_contact_id_fkey → contacts(id)

3. contacts​

Stores contact information for email recipients.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
first_nametextNO''Contact first name
last_nametextNO''Contact last name
emailtextNO-Contact email (unique)
companytextNO''Company name
notestextNO''Internal notes
custom_fieldstextNO'{}'JSON custom field data
statustextNO'active'Contact status
createdintegerNO-Creation timestamp (Unix)
updatedintegerNO-Update timestamp (Unix)

Status Values: active, unsubscribed, bounced, complained, invalid

Indexes:

  • contacts_email_unique UNIQUE ON (email)
  • idx_contacts_email_uniq UNIQUE ON (email)
  • idx_contacts_company ON (company)

Constraints:

  • Email must be unique across all contacts

4. providers​

Stores email provider configurations with encrypted credentials.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
nicknametextNO-Display name for provider
typetextNO-Provider type
encrypted_credentialstextNO-AES-256 encrypted credentials
ivtextNO-Initialization vector for decryption
is_defaultintegerNOfalseDefault provider flag
statustextNO'unverified'Provider status
monthly_limitintegerNO100000Monthly email limit
daily_limitintegerNO5000Daily email limit
createdintegerNO-Creation timestamp (Unix)
updatedintegerNO-Update timestamp (Unix)

Provider Types: zeptomail, smtp, mock

Status Values: unverified, active, paused, exhausted, failed

Indexes:

  • idx_providers_type ON (type)

Security Note: Credentials are encrypted using AES-256-GCM with a unique IV per provider.


5. campaign_templates​

Reusable email templates for campaigns.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
nametextNO-Template display name
subjecttextNO''Default subject line
html_contenttextNO-HTML template content
createdintegerNO-Creation timestamp (Unix)
updatedintegerNO-Update timestamp (Unix)

Template Variables: Templates support {{variable}} syntax for personalization.

Built-in Templates:

  1. Welcome Email - Indigo accent banner
  2. Password Reset - Slate/Charcoal security block
  3. Monthly Usage Report - Teal metrics table
  4. Billing Receipt - Slate payment itemization receipt
  5. Trial Expiration Warning - Amber alert box
  6. Feature Announcement - Violet visual automation outline

6. campaign_events​

Tracks all email events (opens, clicks, bounces, etc.).

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
campaign_idtextNO-FK → campaigns.id
recipient_idtextYES-FK → campaign_recipients.id
event_typetextNO-Type of event
metadatatextNO'{}'Event metadata JSON
created_atintegerNO-Event timestamp (Unix)

Event Types: sent, delivered, opened, clicked, bounced, complained, unsubscribed

Indexes:

  • idx_events_campaign ON (campaign_id)

Foreign Keys:

  • campaign_events_campaign_id_fkey → campaigns(id)
  • campaign_events_recipient_id_fkey → campaign_recipients(id)

Metadata Examples:

  • opened: {"user_agent": "iPhone Mail", "ip_address": "192.168.1.1"}
  • clicked: {"url": "https://example.com", "link_text": "Learn More"}
  • bounced: {"reason": "mailbox full", "code": "5.2.2"}

7. tags​

Contact segmentation tags for audience targeting.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
nametextNO-Tag name (unique)

Indexes:

  • tags_name_unique UNIQUE ON (name)
  • idx_tags_name_uniq UNIQUE ON (name)

Common Tags: customer, prospect, newsletter, vip, inactive, beta, enterprise


8. contact_tags​

Junction table for contact-tag relationships.

ColumnTypeNullableDefaultDescription
contact_idtextNO-FK → contacts.id
tag_idtextNO-FK → tags.id

Primary Key: Composite (contact_id, tag_id)

Foreign Keys:

  • contact_tags_contact_id_fkey → contacts(id)
  • contact_tags_tag_id_fkey → tags(id)

9. provider_usage​

Tracks daily email usage per provider for quota management.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
provider_idtextNO-FK → providers.id
datetextNO-Date in YYYY-MM-DD format
emails_sentintegerNO0Emails sent on this date

Indexes:

  • idx_usage_provider_date ON (provider_id, date)

Foreign Keys:

  • provider_usage_provider_id_fkey → providers(id)

Usage Reset: Daily counters reset at midnight UTC, monthly counters reset on 1st of month.


10. activity_logs​

Audit trail for system actions and user activities.

ColumnTypeNullableDefaultDescription
idtextNO-Primary key (UUID)
typetextNO-Activity type
descriptiontextNO-Human-readable description
createdintegerNO-Activity timestamp (Unix)

Activity Types: campaign_created, campaign_sent, contact_imported, provider_added, template_created, user_login, user_logout, settings_updated

Retention Policy: Logs retained for 90 days, then archived.


11. settings​

Key-value store for system configuration.

ColumnTypeNullableDefaultDescription
keytextNO-Setting key (primary key)
valuetextNO-Setting value (JSON string)

Common Settings:

  • default_from_email - Default sender email
  • default_from_name - Default sender name
  • unsubscribe_url - Unsubscribe page URL
  • tracking_enabled - Global tracking toggle
  • rate_limit_per_minute - API rate limit
  • bounce_threshold - Bounce rate threshold
  • complaint_threshold - Complaint rate threshold

Database Functions & Utilities​

Encryption Functions​

Encrypt Provider Credentials:

async function encryptCredentials(plainText: string, key: string): Promise<{encrypted: string, iv: string}> {
// AES-256-GCM encryption with unique IV
}

Decrypt Provider Credentials:

async function decryptCredentials(encrypted: string, iv: string, key: string): Promise<string> {
// AES-256-GCM decryption
}

Analytics Functions​

Calculate Campaign Metrics:

-- Calculate open rate for campaign
SELECT
COUNT(*) as total_sent,
COUNT(CASE WHEN opened_at IS NOT NULL THEN 1 END) as total_opened,
ROUND((COUNT(CASE WHEN opened_at IS NOT NULL THEN 1 END) * 100.0 / COUNT(*)), 2) as open_rate
FROM campaign_recipients
WHERE campaign_id = ? AND status IN ('sent', 'delivered', 'opened', 'clicked');

Get Provider Usage:

-- Get monthly usage for provider
SELECT
SUM(emails_sent) as monthly_usage
FROM provider_usage
WHERE provider_id = ? AND date LIKE ? || '%';

Maintenance Functions​

Cleanup Old Data:

-- Archive campaign events older than 90 days
DELETE FROM campaign_events WHERE created_at < ?;

-- Cleanup failed campaign recipients older than 30 days
DELETE FROM campaign_recipients WHERE status = 'failed' AND sent_at < ?;

Migration Strategy​

Version Control​

  • All schema changes via Drizzle migrations
  • Migration files stored in /drizzle/ directory
  • Sequential migration numbering (0000_, 0001_, etc.)
  • Rollback scripts for each migration

Migration Examples​

Initial Schema (0000_bent_nekra.sql):

CREATE TABLE campaigns (...);
CREATE TABLE contacts (...);
-- etc.

Schema Extension (0001_dark_william_stryker.sql):

ALTER TABLE contacts ADD status text DEFAULT 'active' NOT NULL;

Production Migration​

  1. Test migrations in development environment
  2. Create backup before applying migrations
  3. Apply migrations during maintenance window
  4. Verify data integrity post-migration
  5. Update application code to match schema

Backup & Recovery​

Backup Strategy​

  • Daily: Full database export
  • Hourly: Incremental changes
  • Real-time: WAL (Write-Ahead Logging) for point-in-time recovery

Recovery Procedures​

  1. Identify corruption or data loss
  2. Restore most recent backup
  3. Apply WAL logs up to point of failure
  4. Verify data consistency
  5. Resume normal operations

Performance Optimization​

Indexing Strategy​

Read-Optimized Indexes:

  • Campaign lookups by status and date
  • Recipient queries by campaign and status
  • Contact searches by email and company
  • Provider usage by date ranges

Write Optimization:

  • Batch inserts for campaign recipients
  • Asynchronous event logging
  • Queue-based email sending
  • Delayed index updates for bulk operations

Query Patterns​

High-Frequency Queries:

-- Dashboard statistics
SELECT status, COUNT(*) FROM campaigns GROUP BY status;

-- Recent campaigns
SELECT * FROM campaigns ORDER BY created DESC LIMIT 10;

-- Provider usage today
SELECT emails_sent FROM provider_usage WHERE provider_id = ? AND date = ?;

-- Contact search
SELECT * FROM contacts WHERE email LIKE ? OR first_name LIKE ? OR last_name LIKE ?;