Technical Reference
Email

Email — Technical Reference

This page covers the data models, background processing engines, and tracking infrastructure behind the Email module.


Data Models

sender_identities

Stores verified domains and subdomains used for sending and reply routing.

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id (ON DELETE CASCADE)
domaintextRoot domain (e.g., acme.com)
email_addresstextSending email address (e.g., hello@mail.acme.com)
resend_domain_idtextDomain identifier registered with Resend
dns_verifiedbooleanFlag indicating DKIM/SPF verification status
verified_attimestamptzWhen domain verification completed
created_attimestamptzRecord creation timestamp
updated_attimestamptzLast updated timestamp

campaigns

Defines automated drip sequences.

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id (ON DELETE CASCADE)
nametextCampaign name
statustext'draft', 'active', 'paused', or 'archived' (CHECK constraint)
created_byuuidFK → user_profiles.id
updated_byuuidFK → user_profiles.id
created_attimestamptzRecord creation timestamp
updated_attimestamptzLast updated timestamp

campaign_steps

Individual emails within a campaign sequence.

ColumnTypeDescription
iduuidPrimary key
campaign_iduuidFK → campaigns.id (ON DELETE CASCADE)
sequence_orderintegerDisplay order in composer
timing_modetext'delay' (after enrollment) or 'before_event' (countdown to event)
timing_value_hoursintegerInterval in hours
email_subjecttextEmail subject line (supports merge fields)
email_body_htmltextRich text HTML email body (supports merge fields)
sender_identity_iduuidOptional FK → sender_identities.id (custom step sender)
created_attimestamptzRecord creation timestamp
updated_attimestamptzLast updated timestamp

campaign_enrollments

Tracks contact positions inside campaigns.

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id (ON DELETE CASCADE)
contact_iduuidFK → contacts.id (ON DELETE CASCADE)
campaign_iduuidFK → campaigns.id (ON DELETE CASCADE)
current_step_iduuidFK → campaign_steps.id (next step to send)
trigger_event_iduuidFK → events.id (optional, for date-based events)
statustext'active', 'processing', 'completed', 'skipped_unsubscribed', 'cancelled', or 'failed'
next_action_attimestamptzScheduled timestamp of the next step
retry_countintegerFailed attempts count (max 3 before marking 'failed')
last_errortextDiagnostic error message of last failure
enrolled_attimestamptzEnrollment timestamp
completed_attimestamptzCompletion timestamp
cancelled_attimestamptzCancellation timestamp

broadcasts

One-time email campaigns sent to targeted audiences.

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id (ON DELETE CASCADE)
sender_identity_iduuidFK → sender_identities.id (optional)
subjecttextEmail subject line
body_htmltextEmail body content
target_tagsuuid[]Target tags (include filter)
target_eventsuuid[]Target events (include filter)
scheduled_fortimestamptzOptional scheduled delivery time
statustext'draft', 'scheduled', 'sending', or 'sent'
sent_attimestamptzDispatched timestamp
sent_countintegerNumber of successful dispatches
created_byuuidFK → user_profiles.id
created_attimestamptzRecord creation timestamp
updated_attimestamptzLast updated timestamp

email_sends

Unified log of all outgoing emails.

ColumnTypeDescription
iduuidPrimary key
workspace_iduuidFK → workspaces.id (ON DELETE CASCADE)
contact_iduuidFK → contacts.id (ON DELETE CASCADE)
sender_identity_iduuidFK → sender_identities.id
send_typetext'campaign', 'broadcast', or 'transactional'
campaign_step_iduuidFK → campaign_steps.id (optional)
broadcast_iduuidFK → broadcasts.id (optional)
resend_message_idtextMessage identifier returned by Resend
subjecttextThe sent subject line
statustext'sent', 'delivered', 'opened', 'clicked', 'bounced', or 'complained'
sent_attimestamptzDispatched timestamp
opened_attimestamptzFirst opened timestamp
clicked_attimestamptzFirst clicked timestamp

tracked_links

Links wrapped for native engagement tracking.

ColumnTypeDescription
iduuidPrimary key (used in tracking redirect URLs)
email_send_iduuidFK → email_sends.id (ON DELETE CASCADE)
workspace_iduuidFK → workspaces.id (ON DELETE CASCADE)
contact_iduuidFK → contacts.id (ON DELETE CASCADE)
original_urltextOriginal target URL
click_countintegerDirect click counter
first_clicked_attimestamptzFirst click timestamp
last_clicked_attimestamptzMost recent click timestamp
created_attimestamptzRecord creation timestamp

The Sweeper Engines

Email sending in Gordon CRM is decoupled into asynchronous background processing via scheduled workers.

Campaign Sweeper

The campaign runner runs on a periodic CRON cycle:

  1. Queries for active campaign enrollments where next_action_at <= NOW().
  2. Locks rows by updating status to 'processing' (using UPDATE ... RETURNING for concurrency safety).
  3. Validates against the Tiered Suppression Engine:
    • Marketing Emails: Skips and sets status to 'skipped_unsubscribed' if is_subscribed = false or unsubscribed_at is set, or if the contact has any active suppression (bounce, complaint, unsubscribe, manual).
    • Transactional Emails: Bypasses soft suppressions and general opt-outs. It only skips if the contact has an active hard suppression (bounce, complaint, or manual).
  4. Calculates Next Steps: Uses the Universal Step Calculator to schedule the subsequent step. The calculator computes timing targets for all remaining steps:
    • Delay step: enrolled_at + timing_value_hours
    • Before-event step: event_start_time - timing_value_hours It then sorts them, scheduling the earliest one.
  5. Dispatches: Injects merge fields, handles tracking wrapping, injects unsubscribe headers and links (only for 'marketing' emails), appends the transactional subscribe footer (only for transactional emails sent to soft-suppressed/opted-out contacts if enabled in settings), pre-inserts email_sends with status 'pending', sends via Resend, and updates status to 'sent' (or 'failed' on error). Overdue steps within a 5-minute tolerance window remain eligible to prevent skips.

Broadcast Sweeper

The broadcast runner dispatches scheduled or queued dispatches:

  1. Queries for broadcasts with status 'sending' or 'scheduled' where scheduled_for <= NOW().
  2. Locks row status to 'processing'.
  3. Resolves Target Audience: Calculates the recipient list by applying includes (all contacts, tag array, event array) and excludes (tag array, event array).
    • Marketing Broadcasts: Filters out any contact that has is_subscribed = false, unsubscribed_at set, or any active suppression.
    • Transactional Broadcasts: Bypasses soft suppressions and only filters out contacts with active hard suppressions (bounce, complaint, manual).
  4. Pre-inserts Logs: Creates email_sends entries set to 'pending' in bulk.
  5. Batch Sends: Injects unsubscribe headers/links (marketing) or appends subscribe footers (transactional, for opted-out contacts), and sends via the Resend Batch API in chunks of 100 emails per batch.
  6. Finalizes: Updates status to 'sent' (with sent_count and sent_at) or 'failed' with error messages.

First-Party Tracking Engine

Gordon CRM utilizes native link-wrapping and pixel tracking hosted on a branded tracking subdomain (e.g., links.yourdomain.com) rather than relying on Resend's default tracking.

Tracking Precedence Matrix

The campaign and broadcast engines route tracking decisions based on category settings:

Categorydisable_trackingLink WrappingOpen PixelUnsubscribe Headeris_subscribed GateTransactional Footer
Marketingfalse (default)✅ Required✅ EnforcedN/A
Marketingtrue✅ Required✅ EnforcedN/A
TransactionalN/A❌ Omitted❌ Bypassed✅ Optional (Opted-out only)

Note on Transactional Footers: If the recipient is opted out of marketing (has a soft opt-out status) and subscribe_footer_enabled is true in the workspace settings, a subscribe footer with a custom message and action link is appended to transactional emails.

Link Wrapping Algorithm

If tracking is active, the system scans the HTML body for <a> tags and substitutes the links:

  1. Regex Scan: Parses <a href="..."> elements.
  2. Skips Restrictions: Ignores unsubscribe endpoints (/unsubscribe/ or /api/unsubscribe), mailto: links, tel: links, internal anchors (#), and Naked URLs (e.g. <a href="http://google.com">google.com</a>) where the link text resembles a URL. This text-to-href mismatch check prevents Gmail and Outlook phishing flags.
  3. Database Log: Creates a tracked_links row for every unique target.
  4. URL Rewrite: Replaces the link destination with https://links.yourdomain.com/api/c/{tracked_link_id}.

Open and Click Endpoints

  • Open tracking pixel (/api/o/[id]): Appends a transparent 1x1 GIF tracking image (/api/o/{email_send_id}) before </body>. Loading this endpoint sets opened_at on the target email_sends log and returns the image.
  • Click redirect (/api/c/[id]): Clicking a wrapped link records the click count and timestamps in tracked_links and email_sends, then responds with a 302 Redirect to the original destination.

Status Precedence

Email status only advances forward based on the following ordinal precedence hierarchy:

StatusPrecedence Value
pending-1
sent0
delivered1
opened2
clicked3
bounced99 (Terminal Override)
complained99 (Terminal Override)

Sender Identity Resolution

The shared resolveSenderIdentity utility resolves the "from" address for campaigns, broadcasts, agreements, and other system emails by walking the following fallback chain:

  1. Explicit Sender: Evaluates any explicit sender_identity_id specified on the campaign step or broadcast. The sender identity must be verified (dns_verified = true).
  2. Workspace Default: Falls back to the first verified sender identity created for the workspace.
  3. Dedicated Fallback Domain: Generates a virtual sender address using the slugified workspace name and a short hash of the workspace ID: {slugified-workspace-name}-{short-workspace-uuid-hash}@[FALLBACK_SENDER_DOMAIN], routing replies directly to the workspace owner's email.

DNS & Verification Architecture

Subdomain Sending Isolation

The system registers a dedicated mail subdomain (e.g., mail.acme.com) for sending, isolating root domain reputation. Verification requires DKIM and SPF records.

Tracking Subdomain DNS Setup

Branded click and open tracking uses a CNAME record mapping links.yourdomain.com to Vercel. The target value is retrieved dynamically from the Vercel API. If the API is unreachable, the DNS setup page defaults to cname.vercel-dns.com.

Fallback Sender Mechanics

For workspaces without custom verified senders, a virtual sender address is generated:

{slugified-workspace-name}-{short-workspace-uuid-hash}@mail.gordoncrm.com

Replies are routed directly to the workspace owner's email. Fallback tracking redirects through the FALLBACK_TRACKING_DOMAIN environment variable.


Server Actions

getBroadcastStats

The getBroadcastStats server action aggregates metrics from the email_sends table for a specific broadcast to supply the list view accordion:

export async function getBroadcastStats(broadcastId: string): Promise<{
    data?: {
        total: number;
        delivered: number;
        opened: number;
        clicked: number;
        bounced: number;
        complained: number;
        openRate: number | null;
        clickRate: number | null;
    };
    error?: string;
}>

It dynamically queries email_sends for status, opened_at, and clicked_at timestamps, filtering by broadcast_id and send_type = 'broadcast'.

getBroadcasts

The getBroadcasts server action retrieves a paginated, filtered, and sorted list of broadcasts for the active workspace.

export interface BroadcastListParams {
    page?: number;
    pageSize?: number;
    search?: string;
    status?: string;
    category?: string;
    sortColumn?: string;
    sortDir?: "asc" | "desc";
}

Filtering & Sorting Parameters:

  • status: Optional string to filter by dispatch status ('draft', 'scheduled', 'processing', 'sent', or 'failed').
  • category: Optional string to filter by category ('marketing' or 'transactional').
  • sortColumn: Specifies the column to sort by. Only allowlisted columns are queried:
    • "category": Category badge.
    • "status": Dispatch status.
    • "created_at" (default): Broadcast creation timestamp.
    • "sent_count" (Recipients): Number of sent recipients.
  • sortDir: The sort direction ("asc" or "desc"). Defaults to "desc".

sendTestEmail

The sendTestEmail server action dispatches a single test email. It allows users to preview campaign steps or broadcasts before launching them.

export async function sendTestEmail(params: {
    workspaceId: string;
    subject: string;
    bodyHtml: string;
    recipientEmail: string;
    previewContactId?: string;
    category: "marketing" | "transactional";
    appendFooter?: boolean;
}): Promise<{ success?: boolean; error?: string }>

Key Operations:

  • Merge-Tag Personalization: If a previewContactId is supplied, the action queries the database for the preview contact's details and compiles the subject and body using the compileTemplate() merge-field resolver.
  • Unsubscribe Link Injection: If the email category is 'marketing', the system wraps tracking links and appends the standard unsubscribe link.
  • Transactional Subscribe Footer Injection: If the category is 'transactional', the contact is currently opted out of marketing, and the appendFooter parameter is true, the system appends the workspace's transactional subscribe footer.
  • Dispatch: Sends the email to recipientEmail using the resolved workspace sender identity via Resend.

undoSendBroadcast

The undoSendBroadcast server action allows administrators to cancel a queued broadcast send within a short grace period (typically 15 seconds after clicking Send Now) or before the background sweeper begins delivery.

export async function undoSendBroadcast(
    broadcastId: string
): Promise<{ success?: boolean; error?: string }>

Key Operations:

  • Atomic Status Check: Validates that the target broadcast's status is 'sending'. If the status is not 'sending', or if the background sweeper has already commenced delivery (sending out the first batch of emails), the action fails with an error to prevent inconsistent delivery states.
  • Status Reversion: Reverts the broadcast status column back to 'draft'.
  • Pre-Insert Cleanup: Hard-deletes any pending email_sends log rows that were created during the audience resolution stage, ensuring that no emails are sent in subsequent sweeper runs.
  • Cache Revalidation: Calls revalidatePath() to refresh dashboard list and detail page displays immediately.

UI & Display Architecture

Contact Detail Emails History

The Emails section on the Contact Detail page queries email_sends to render the message history:

  • Campaign Email Subject Display: For campaign-type emails, it renders the actual email subject (email_sends.subject) rather than the parent campaign's name, making multi-step campaigns visually distinguishable. A link to the parent campaign remains available.
  • Broadcast Email Subject Display: For broadcast-type/transactional emails, it renders the subject line.

Performance & Hydration Optimization

Server-Side Pre-fetching & Hydration Splits

  • Campaigns Split:
    • Server Component (campaigns/page.tsx): Pre-fetches page 1 campaign directory listings on the server.
    • Client Component (campaigns-client.tsx): Handles campaign lists rendering, filters, and step creation forms.
    • Eager Mount Check: Conditionally mounts the sheet and dialogs (ResponsiveSheet / AlertDialog) to defer loading.
  • Broadcasts Split:
    • Server Component (broadcasts/page.tsx): Parallel pre-fetches page 1 broadcasts, workspace tags, and events.
    • Client Component (broadcasts-client.tsx): Manages pagination, sorting, status/category filters, and lazy metrics calculations.
    • Debounced Search: Restricts search queries with a 300ms input debounce buffer to prevent redundant API load.
  • State gates: Both pages use isFirstQueryRef mount guards and initialWorkspaceIdRef hooks to sync state transitions cleanly across workspaces.

Row-Level Security (RLS)

All tables enforce tenant-level isolation using the get_user_workspace_ids() helper.

TableSELECTINSERTUPDATEDELETE
sender_identitiesWorkspace usersAdmin+Admin+Admin+
campaignsWorkspace usersAll membersAll membersAdmin+
campaign_stepsWorkspace usersAll membersAll membersAdmin+
campaign_enrollmentsWorkspace usersAll membersAll membersAdmin+
broadcastsWorkspace usersAll membersAll membersAdmin+
email_sendsWorkspace usersAll membersAll membersAdmin+
tracked_linksWorkspace usersAll membersAll membersAdmin+