Logotipo Hedhog

Get Hedhog updates in your inbox

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

Campaign Submodule

suppression

Global, campaign-independent suppression list that CampaignDispatchService consults to skip emails it must not send to.

Source path: libraries/campaign/src/services/campaign-suppression.service.ts

Module file: campaign.module.ts

Introduction

The Suppression submodule maintains `campaign_suppression`, a single global list of email addresses that must never receive campaign mail, regardless of which recipient list they appear on. Every campaign send performs a bulk suppression check (`CampaignSuppressionService.bulkCheck`) against the full audience before creating message rows, so a suppressed address is skipped at enqueue time, not silently dropped later — the corresponding `campaign_message` row is created with `status: skipped` so the campaign's totals stay accurate.

Each suppression entry carries a `reason` — `manual`, `bounce`, `complaint`, `unsubscribe`, or `invalid` — plus a free-text `note`, an optional `source` string, and optional `campaign_id`/`message_id` back-references to the send that triggered it. Adding an entry has a side effect beyond the suppression table itself: `CampaignSuppressionService.create` also updates any matching `campaign_recipient` rows to `status: bounced` (when `reason` is "bounce") or `status: suppressed` (for every other reason), keeping the recipient-level status consistent with the suppression-level record. The unsubscribe flow (in the Unsubscribes submodule) and any future bounce/complaint webhook handler both funnel through this same `create` method.

Removing a suppression entry — whether by bulk delete or by single-ID delete — is the literal inverse: it deletes the suppression row(s) and resets the corresponding `campaign_recipient` rows back to `status: active`, effectively re-admitting that address into future sends. There is no separate "reactivate" endpoint; deleting the suppression record is how an operator manually un-suppresses an address.

`GET /campaign/suppression/stats` returns a reason-by-reason breakdown (total, plus counts for unsubscribe/bounce/complaint/manual/invalid), which the dashboard surfaces to give operators visibility into why their suppression list is growing — a spike in `bounce` typically signals a deliverability problem distinct from a spike in `unsubscribe`.

HTTP Endpoints

5 endpoints

Suppression list statistics broken down by reason (bounce, complaint, unsubscribe, manual, invalid) plus a total count.

Paginated list of suppressed email addresses.

Query
page, pageSize, search, reason, source

Add an email to the suppression list. Also marks any matching recipient records as suppressed (or bounced, if reason is "bounce").

Body

FieldTypeRequiredNotes
emailstringyesEmail address to suppress
reason"manual" | "bounce" | "complaint" | "unsubscribe" | "invalid"noDefault: "manual"
sourcestringnoDefault: "manual"
notestringnoOptional free-text note
campaign_idnumbernoAssociated campaign ID
message_idnumbernoAssociated message ID

Bulk delete suppression records. Also re-activates matching recipient records (status set back to "active").

Body

FieldTypeRequiredNotes
idsnumber[]yesIDs to delete

Delete a single suppression record. Also re-activates the corresponding recipient.

Params
id (int)