Logotipo Hedhog

Get Hedhog updates in your inbox

New releases, fresh recipes, and breaking changes — no spam.

Campaign Submodule

recipient-lists

Named lists of recipients that campaigns target. Supports manual entry, CSV import, CRM sync, and public signup.

Source path: libraries/campaign/src/services/campaign-recipient-list.service.ts, campaign-realtime.service.ts, dto/public-signup.dto.ts

Module file: campaign.module.ts

Introduction

The Recipient Lists submodule models `campaign_recipient_list`, the audience container a campaign points at via `recipient_list_id`. Every list carries a unique, validated slug (lowercase letters, numbers, hyphens, minimum 2 characters — auto-generated from the name if not supplied), a `source` classification ("manual" | "import" | "crm" | "mixed") that reflects how its recipients were populated, and rolled-up counters (`total_recipients`, `total_active`, `total_unsubscribed`, `total_suppressed`, `total_invalid`) that CampaignRecipientListService recalculates after every mutation rather than computing on read. All routes are dual-mounted under both `/campaign/lists/*` and `/campaign/recipient-lists/*` — the controller registers each handler twice with NestJS's multi-decorator support — so both naming conventions resolve identically; this documentation uses `/campaign/recipient-lists/*` as the canonical form.

Recipients can be added to a list three ways. Inline JSON (`POST .../recipients` with an array of `{ email, name?, ... }` objects or a single `{ email }` shorthand) upserts by email within the list. CRM sync (`person_ids` in the same endpoint) resolves each CRM Person's primary EMAIL-type contact, skipping people with no valid email and reporting them back in a `skipped` array, then flips the list's `source` to "mixed" (or "crm" if every recipient came from CRM) once at least one person is added. CSV import is a two-step flow: `POST .../recipients/import/preview` parses the uploaded file (custom hand-rolled CSV parser supporting both comma and semicolon delimiters and quoted fields, capped at 10 MB / enforced again at 5000 data rows) and returns detected columns plus a 20-row sample without persisting anything; `POST .../recipients/import` takes the same file plus a JSON `mapping` of CSV columns to recipient fields and performs the actual upsert-by-email, optionally linking each imported row to an existing CRM company via `company_id`.

Public signup (`POST /campaign/lists/:slug/signup`, unauthenticated) lets a list opt into self-service subscription. It only works when `allow_public_signup` is true on the list; when `enable_captcha` is also true, the configured CAPTCHA Integration Profile (reCAPTCHA, Cloudflare Turnstile, or Altcha) is invoked to validate the submitted `captcha_token` before the email is upserted. This is the typical entry point for embeddable newsletter signup forms.

The submodule also hosts the campaign realtime cursor (`GET /campaign/realtime/cursor`, `GET /campaign/realtime/stream`): every list mutation calls `CampaignRealtimeService.publish()`, bumping an in-memory sequence counter that admin UIs poll or subscribe to over Server-Sent Events to know when to refetch list data, without the server tracking per-client state.

HTTP Endpoints

15 endpoints

Get the current realtime update cursor (sequence number and last-updated timestamp) for polling-based refresh.

Open a Server-Sent Events stream that pushes the updated cursor whenever a recipient list changes (create, update, delete, recipient mutation).

Aggregate statistics across all recipient lists: total lists and summed recipient/active/unsubscribed/suppressed/invalid counters. Also available at /campaign/lists/stats.

Paginated list of recipient lists. Also available at /campaign/lists.

Query
page, pageSize, search, status, source

Create a recipient list. Also available at /campaign/lists.

Body

FieldTypeRequiredNotes
namestringyes
slugstringnoAuto-generated from name if omitted; lowercase letters/numbers/hyphens, min 2 chars, must be unique
descriptionstringno
source"manual" | "import" | "crm" | "mixed"noDefault: "manual"
status"active" | "inactive"noDefault: "active"
allow_public_signupbooleannoDefault: false
enable_captchabooleannoDefault: false
captcha_integration_profile_idnumbernoRequired when enable_captcha is true
metadataobjectno

Bulk delete recipient lists. Also available at /campaign/lists.

Body

FieldTypeRequiredNotes
idsnumber[]yesIDs to delete

Get a recipient list by ID. Also available at /campaign/lists/:id.

Params
id (int)

Update a recipient list. Only provided fields are changed. Also available at /campaign/lists/:id.

Params
id (int)

Body

FieldTypeRequiredNotes
namestringno
descriptionstringno
source"manual" | "import" | "crm" | "mixed"no
status"active" | "inactive"no
metadataobjectno
slugstringnoMust remain unique
allow_public_signupbooleanno
enable_captchabooleanno
captcha_integration_profile_idnumberno

Delete a single recipient list. Also available at /campaign/lists/:id.

Params
id (int)

Paginated list of recipients in a specific list. Also available at /campaign/lists/:id/recipients.

Params
id (int)
Query
page, pageSize, search, status

Preview a CSV import: parses delimiters/quoting and returns detected columns plus a 20-row sample without persisting any data. Rejects files over 5000 data rows. Also available at /campaign/lists/:id/recipients/import/preview.

Params
id (int)

Body

FieldTypeRequiredNotes
filefile (CSV, max 10 MB)yesmultipart/form-data upload; text/csv or application/vnd.ms-excel

Import recipients from a CSV file. Upserts by email within the list and optionally creates linked CRM Person records. Also available at /campaign/lists/:id/recipients/import.

Params
id (int)

Body

FieldTypeRequiredNotes
filefile (CSV, max 10 MB)yesmultipart/form-data upload
mappingstring (JSON)yesJSON object mapping CSV column names to recipient fields (e.g. {"Email":"email","Nome":"name"})
company_idnumbernoCRM company ID to associate imported recipients/persons

Add recipients to a list from CRM person IDs or inline data. Upserts by email and recalculates list counters. Also available at /campaign/lists/:id/recipients.

Params
id (int)

Body

FieldTypeRequiredNotes
person_idsnumber[]noCRM person IDs; auto-resolves primary EMAIL contact. When provided, other fields are ignored
recipientsobject[]noArray of { email, name?, first_name?, last_name?, company?, phone?, status?, source?, person_id?, metadata? }
emailstringnoShorthand for adding a single recipient by email

Remove specific recipients from a list. Also available at /campaign/lists/:id/recipients.

Params
id (int)

Body

FieldTypeRequiredNotes
idsnumber[]yesRecipient IDs to remove from this list

Public signup for a recipient list identified by slug. Only works when the list has allow_public_signup enabled. Validates CAPTCHA (reCAPTCHA, Cloudflare Turnstile, or Altcha) when enable_captcha is set, then upserts the recipient as active.

Params
slug (string) — public slug of the recipient list

Body

FieldTypeRequiredNotes
emailstringyesValid email address
namestringno
first_namestringno
last_namestringno
companystringno
phonestringno
captcha_tokenstringnoRequired when the list has enable_captcha set