KW Knowledge base

Marketing Campaigns

Sending bulk email campaigns to CRM contacts: creating campaigns, audience filtering, launch, delivery tracking, and unsubscribe handling.

Mitch Wigham
Updated 24 June 2026 · 6 views

What Is the Marketing Module?

The Marketing module sends bulk HTML email campaigns to contacts pulled from the CRM. A campaign is a one-off email — there is no concept of saved segments or reusable templates; each campaign carries its own body and audience filter.

Where to find it: Sidebar → Marketing or /marketing. Requires the marketing licence feature and an ADMIN / MANAGER role.

Concepts

  • Campaign — an email message (subject + HTML body) plus an audience. A campaign is sent once.
  • Audience — chosen per-campaign, not saved separately. Either:
    • All contacts — every CRM contact with an email address that has not opted out.
    • By status — contacts whose CRM status is one of a set you pick at launch time.
  • Send — a per-recipient row tracking delivery, open, and failure for that contact.
  • Unsubscribe — every send carries a unique unsubscribe token; clicking it sets the contact's marketingOptOut = true and the contact is excluded from future campaigns.

Creating a Campaign

  1. Go to Marketing → + New campaign.
  2. Fill in Basics:
    • Name (required) — internal reference, not shown to recipients.
    • From name — appears in the recipient's mail client.
    • From email — the address used in the From: header.
    • Subject (required).
  3. Fill in Body:
    • HTML body — the rendered email. Inline CSS only (most clients strip <style>).
    • Plain text body — the fallback for clients that block HTML.
    • Merge placeholders{{firstName}}, {{lastName}}, {{companyName}}, {{email}} are interpolated per recipient.
  4. Fill in Recipients:
    • All contacts — or —
    • By status — tick one or more CRM contact statuses.
  5. Click Save draft. The campaign is created in DRAFT status.

Launching a Campaign

Open the campaign detail page and click Launch campaign.

The launch process:

  1. Queries CRM contacts in the org with an email address and marketingOptOut = false, applying the status filter if set.
  2. Creates one MarketingSend row per eligible contact, each with its own unsubscribe token.
  3. Moves the campaign to SENDING and queues the sends in batches of 100 to a background worker.
  4. The worker emails each contact, increments delivered / failed counts, and tracks opens via a 1×1 tracking pixel.

Tracking Results

The campaign detail page shows KPI cards:

  • Recipients — total contacts the campaign was queued to.
  • Delivered — confirmed accepted by the recipient's SMTP server.
  • Failed — bounced, deferred past retry, or rejected.
  • Opens — unique opens (pixel-loaded).
  • Open rate — opens / delivered.

While the campaign is SENDING, click Refresh to poll progress. Once SENT, the KPIs are final.

A failed-send row can be Resent from the email log page.

Status Lifecycle

Status Meaning
DRAFT Created, not yet sent. Editable.
SENDING Launched. Sends are queued and progressing. Edit locked.
SENT All recipients processed.
FAILED The launch itself failed (e.g. SMTP not configured). Re-launch from a new campaign.

Unsubscribe Handling

Every outgoing email contains an unsubscribe link with a per-recipient token. Clicking it:

  1. Marks the contact's marketingOptOut = true.
  2. Shows a confirmation page confirming the unsubscribe.
  3. Excludes the contact from all future campaigns immediately.

You do not need to do anything on the platform side — the unsubscribe workflow is self-service. To re-subscribe a contact, edit their CRM record and un-tick Marketing Opt-Out.

Editing a Campaign

Only DRAFT campaigns are editable. Once a campaign is in SENDING or SENT, the edit form is locked (sending a 409 if you try). To "edit" a sent campaign, clone it: open the detail page and click Duplicate (creates a new DRAFT with the same body and audience).

API Access (for external automation)

The marketing service is reachable from the platform BFF at /api/marketing/.... Most external automation is around read-only data — checking the status of a campaign or pulling a CSV of who received a particular send.

# Authenticated. First, log in to get a JWT.
TOKEN=$(curl -s -X POST https://portal.kwgroup.org.uk/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"you@example.com","password":"...","mfaCode":"123456"}' \
  | jq -r .accessToken)

# List campaigns in your org.
curl -s -H "authorization: Bearer $TOKEN" \
  https://portal.kwgroup.org.uk/api/marketing/campaigns

# Get delivery + open stats for a single campaign.
curl -s -H "authorization: Bearer $TOKEN" \
  https://portal.kwgroup.org.uk/api/marketing/campaigns/<id>/stats

The accessToken expires after 15 minutes. Refresh with the refreshToken cookie or call /api/auth/refresh. Marketing writes (POST, PATCH, DELETE) are admin-only — most external users only need the read endpoints.

See Also

Still need help?

Log a support ticket and the team will pick it up from this page.