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:
| Category | Consent Gate | Unsubscribe Header | Tracking |
|---|---|---|---|
| Marketing (default) | ✅ is_subscribed must be true | ✅ Required | Configurable (on by default) |
| Transactional | ❌ Bypasses consent | ❌ Omitted | Always 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 —
delaymode) - Step 2: 168 hours (7 days) before the event (
before_eventmode) - Step 3: 24 hours before the event (
before_eventmode) - Step 4: 1 hour before the event (
before_eventmode)
Campaign Lifecycle
| Status | Description |
|---|---|
draft | The campaign is being configured. No emails are sent. |
active | The campaign is live. The sweeper will process due enrollments. |
paused | Temporarily suspended. Enrollments are preserved but no emails are sent. Resumes when re-activated. |
archived | Permanently deactivated. |
Campaign Steps
Each step represents one email in the sequence:
| Field | Description |
|---|---|
| Sequence Order | The display order in the campaign builder (not used for scheduling). |
| Timing Mode | delay (hours after enrollment) or before_event (hours before event start). |
| Timing Value | Number of hours for the delay or countdown. Set to 0 for immediate send. |
| Email Subject | Subject line, supports merge fields. |
| Email Body | HTML email body, supports merge fields. |
| Sender Identity | The 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:
| Field | Description |
|---|---|
status | active → processing → completed / cancelled / failed / skipped_unsubscribed |
current_step_id | The next step to be sent. |
next_action_at | When the sweeper should process this enrollment next. |
completed_step_ids | Array of step IDs already sent (prevents re-sends). |
trigger_event_id | For date-based campaigns, the event whose start time anchors the schedule. |
retry_count | Number of failed attempts (max 3 before marking as failed). |
Enrollment Statuses
| Status | Meaning |
|---|---|
active | Waiting for the next scheduled step. |
processing | Currently being processed by the sweeper (prevents double-sends). |
completed | All steps have been sent. |
cancelled | Manually cancelled by a workspace member, or by a remove_campaign automation. |
failed | Failed after 3 retry attempts (e.g., no sender identity, persistent errors). |
skipped_unsubscribed | The 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:
- Fetch — Queries for
activeenrollments wherenext_action_at ≤ NOW(). - Lock — Sets the enrollment to
processingto prevent duplicate sends. - Validate — Runs the suppression cascade (see below).
- Pre-insert — Creates an
email_sendsrow withstatus = 'pending'before dispatching the email. This provides theemail_send_idneeded by the link wrapper to generate tracking URLs. - Send — Resolves merge fields, picks the sender identity, determines tracking configuration, and dispatches via the Resend API.
- Update — On success, updates the pre-inserted row to
status = 'sent'with the Resend message ID. On failure, updates tostatus = 'failed'. - Advance — Uses the Universal Step Calculator to determine the next step and updates
next_action_at. If no steps remain, marks the enrollment ascompleted.
Suppression Cascade
Before sending each email, the sweeper checks three conditions in order:
- Unsubscribed — If the contact's
unsubscribed_atis set, the email is skipped. - Suppressed — If any active
contact_suppressionrecord exists (bounce, complaint, manual), the email is skipped. - Marketing consent — If the campaign is
category = 'marketing'and the contact'sis_subscribedisfalse, the enrollment is set toskipped_unsubscribedand 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:
- Contact enrolls Monday at 9 AM. Step 1 sends immediately.
- Step 2 is scheduled for Tuesday at 9 AM.
- You pause the campaign Monday at 5 PM.
- Tuesday at 9 AM arrives — the sweeper sees the campaign is paused and skips the enrollment.
- You re-activate the campaign Wednesday at 10 AM.
- 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)