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 = trueand the contact is excluded from future campaigns.
Creating a Campaign
- Go to Marketing → + New campaign.
- 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).
- 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.
- HTML body — the rendered email. Inline CSS only (most clients strip
- Fill in Recipients:
- All contacts — or —
- By status — tick one or more CRM contact statuses.
- Click Save draft. The campaign is created in
DRAFTstatus.
Launching a Campaign
Open the campaign detail page and click Launch campaign.
The launch process:
- Queries CRM contacts in the org with an email address and
marketingOptOut = false, applying the status filter if set. - Creates one
MarketingSendrow per eligible contact, each with its own unsubscribe token. - Moves the campaign to
SENDINGand queues the sends in batches of 100 to a background worker. - 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:
- Marks the contact's
marketingOptOut = true. - Shows a confirmation page confirming the unsubscribe.
- 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
accessTokenexpires after 15 minutes. Refresh with therefreshTokencookie or call/api/auth/refresh. Marketing writes (POST,PATCH,DELETE) are admin-only — most external users only need the read endpoints.
See Also
- CRM Overview — for contact / status / opt-out fields
- Email Configuration — SMTP setup that campaigns depend on