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_fkeyproviders(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_fkeycampaigns(id)
  • campaign_recipients_contact_id_fkeycontacts(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_fkeycampaigns(id)
  • campaign_events_recipient_id_fkeycampaign_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_fkeycontacts(id)
  • contact_tags_tag_id_fkeytags(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_fkeyproviders(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 ?;