Email Marketing
Campaigns

Campaigns

Campaigns are automated, multi-step email sequences. Once a contact is enrolled, Gordon CRM automatically sends each email in the sequence at the configured interval — no manual intervention required.

Email Categories

Every campaign has a category that controls its compliance behavior:

CategoryConsent GateUnsubscribe HeaderTracking
Marketing (default)is_subscribed must be true✅ RequiredConfigurable (on by default)
Transactional❌ Bypasses consent❌ OmittedAlways off

The category is set in the campaign settings and defaults to marketing. Transactional campaigns should only be used for emails that recipients expect regardless of their marketing preferences — such as appointment reminders or order confirmations.

See Email Marketing → Email Categories for the full category specification.

Tracking Toggle

Marketing campaigns have tracking enabled by default. You can disable tracking for individual campaigns by enabling the "Optimize for Deliverability" toggle in the campaign settings. This removes all tracking artifacts (link wrapping and open pixel) from the email while preserving unsubscribe headers and consent enforcement.

For transactional campaigns, tracking is always off — the toggle is not shown.

See Tracking & Analytics for details on how the tracking engine works.

Campaign Types

Gordon CRM supports two campaign types:

Evergreen (Drip) Campaigns

The default type. Each step is defined as a delay (in hours) from the contact's enrollment time.

Example: A welcome sequence that sends:

  • Step 1: Immediately (0 hours after enrollment)
  • Step 2: 24 hours after enrollment
  • Step 3: 72 hours after enrollment

Every contact gets the same sequence, starting from whenever they were enrolled.

Date-Based (Event) Campaigns

Date-based campaigns use an event's start time as an anchor point. Steps can use two timing modes:

  • before_event — Send X hours before the event starts (countdown reminders)
  • delay — Send X hours after the contact's enrollment (standard drip timing)

Both modes can be mixed in the same campaign. The campaign engine calculates the absolute timestamp for each step and sorts them chronologically, regardless of timing mode.

Example: A conference reminder sequence with mixed timing:

  • Step 1: 0 hours after enrollment (immediate welcome email — delay mode)
  • Step 2: 168 hours (7 days) before the event (before_event mode)
  • Step 3: 24 hours before the event (before_event mode)
  • Step 4: 1 hour before the event (before_event mode)

Campaign Lifecycle

StatusDescription
draftThe campaign is being configured. No emails are sent.
activeThe campaign is live. The sweeper will process due enrollments.
pausedTemporarily suspended. Enrollments are preserved but no emails are sent. Resumes when re-activated.
archivedPermanently deactivated.

Campaign Steps

Each step represents one email in the sequence:

FieldDescription
Sequence OrderThe display order in the campaign builder (not used for scheduling).
Timing Modedelay (hours after enrollment) or before_event (hours before event start).
Timing ValueNumber of hours for the delay or countdown. Set to 0 for immediate send.
Email SubjectSubject line, supports merge fields.
Email BodyHTML email body, supports merge fields.
Sender IdentityThe verified email address to send from. Falls back to workspace default if not set.

Enrollments

A contact enters a campaign via an automation trigger or manual enrollment. Each enrollment tracks:

FieldDescription
statusactiveprocessingcompleted / cancelled / failed / skipped_unsubscribed
current_step_idThe next step to be sent.
next_action_atWhen the sweeper should process this enrollment next.
completed_step_idsArray of step IDs already sent (prevents re-sends).
trigger_event_idFor date-based campaigns, the event whose start time anchors the schedule.
retry_countNumber of failed attempts (max 3 before marking as failed).

Enrollment Statuses

StatusMeaning
activeWaiting for the next scheduled step.
processingCurrently being processed by the sweeper (prevents double-sends).
completedAll steps have been sent.
cancelledManually cancelled by a workspace member, or by a remove_campaign automation.
failedFailed after 3 retry attempts (e.g., no sender identity, persistent errors).
skipped_unsubscribedThe contact was not subscribed (is_subscribed = false) when the sweeper processed this enrollment. Only applies to marketing campaigns — transactional campaigns bypass this check.

The Campaign Sweeper

A background CRON job runs on a schedule and processes due enrollments:

  1. Fetch — Queries for active enrollments where next_action_at ≤ NOW().
  2. Lock — Sets the enrollment to processing to prevent duplicate sends.
  3. Validate — Runs the suppression cascade (see below).
  4. Pre-insert — Creates an email_sends row with status = 'pending' before dispatching the email. This provides the email_send_id needed by the link wrapper to generate tracking URLs.
  5. Send — Resolves merge fields, picks the sender identity, determines tracking configuration, and dispatches via the Resend API.
  6. Update — On success, updates the pre-inserted row to status = 'sent' with the Resend message ID. On failure, updates to status = 'failed'.
  7. Advance — Uses the Universal Step Calculator to determine the next step and updates next_action_at. If no steps remain, marks the enrollment as completed.

Suppression Cascade

Before sending each email, the sweeper checks three conditions in order:

  1. Unsubscribed — If the contact's unsubscribed_at is set, the email is skipped.
  2. Suppressed — If any active contact_suppression record exists (bounce, complaint, manual), the email is skipped.
  3. Marketing consent — If the campaign is category = 'marketing' and the contact's is_subscribed is false, the enrollment is set to skipped_unsubscribed and processing stops. Transactional campaigns bypass this check entirely.

The Universal Step Calculator

The engine calculates the absolute timestamp for each remaining step:

  • Delay steps: enrolled_at + timing_value_hours
  • Before-event steps: event_start_time - timing_value_hours

It then sorts all eligible steps chronologically, picks the earliest one, and schedules it. This allows mixed-mode campaigns (both delay and before_event steps) to interleave correctly.

5-minute tolerance: Steps due within the past 5 minutes are still eligible. This prevents delay: 0 (immediate) steps from being skipped when the sweeper runs shortly after enrollment.

Pausing and Resuming

When you pause a campaign:

  • The sweeper skips all enrollments for that campaign
  • Enrollment records are preserved with their current state
  • When you re-activate, all enrollments resume from where they left off

Example: A 3-step welcome sequence with 24-hour delays between steps:

  1. Contact enrolls Monday at 9 AM. Step 1 sends immediately.
  2. Step 2 is scheduled for Tuesday at 9 AM.
  3. You pause the campaign Monday at 5 PM.
  4. Tuesday at 9 AM arrives — the sweeper sees the campaign is paused and skips the enrollment.
  5. You re-activate the campaign Wednesday at 10 AM.
  6. The sweeper checks the enrollment: Step 2 was due Tuesday at 9 AM (now overdue), so it sends immediately. Step 3 is then scheduled for Thursday at 9 AM.

Key point: Pause does not reset or recalculate step timing. The enrolled contact picks up exactly where they left off. Overdue steps are sent on the next sweeper run after re-activation.

Deleting Steps

If you delete a step from an active campaign:

  • Enrollments that were waiting on that step will have the sweeper recalculate their next step automatically
  • Already-sent emails are unaffected (the email log is preserved)