Skip to content

Mass Communication

A campaign is one deliberate message to a declared audience: tell every citizen matching this segment about the new participatory budget edition. In Atelier, campaigns are platform substrate — the communication_campaign entity belongs to the platform application, so every app on every tenant can run outreach without declaring anything of its own. There is no campaigns subsystem to integrate with: a campaign is an entity like any other, authored on the generic console, sent by a standard action, delivered by the same notification machinery that serves every event.

What makes the lane worth its own page is the invariant it carries: a campaign can only ever reach consenting citizens, and that guarantee is compiled in — not left to operator discipline.

The building blocks

ConceptWhat it is
CampaignA communication_campaign row: a name, an audience, an optional template, and lifecycle timestamps (scheduled_for, sent_at).
SegmentThe audience, stored on the campaign itself: an identity-rooted entity type, a recipient path, and a set of declared filter conditions (segment_filters). Authored visually with the segment builder.
TemplateThe message. Each campaign picks its own parameterizable template (a relation to the shared template catalog); a campaign authored without one falls back to the lane's declared default.
Consent purposeThe lane's declared invariant: the campaign recipient rule carries required_consent_purpose: campaign_outreach, and the compiler ANDs the consent condition into every audience it resolves.
Send actionsend_campaign — a standard detail-surface action with a one-send guard. Sending is not special machinery; it is the action engine doing what it always does.
Delivery ledger & countsEvery delivery attempt lands in the notification delivery ledger; the campaign_delivery_counts view tallies them per campaign, channel, and status — the send log.
Context pairOptional traceability: context_entity_type + context_entity_id link a campaign to a domain object (civic stamps the participatory-budget edition). Context is never dispatch input — the send lane doesn't read it.

How you run a campaign

Step 1 — Author it. Create a campaign on the generic console (/platform/communication_campaign/new). The segment_filters field renders as the segment builder: pick the identity-rooted entity, add conditions, and preview the audience — matching counts and sample recipients, resolved through the same compiler the send will use. The enforced consent purpose is displayed read-only on the builder: it is part of the lane, not part of your segment, so there is nothing to forget and nothing to remove.

Step 2 — Pick the message. Relate the campaign to a template from the shared catalog, and preview it rendered. Skip it and the lane's fallback template renders the campaign name.

Step 3 — Send. The campaign's detail view carries the send action. Executing it stamps sent_at, emits the campaign_send event, and the campaign notification rule takes over: the segment resolves at send time, the consent condition compiled in, and delivery fans out per recipient through the durable notification pipeline. The ledger records every attempt; the counts view is your send log.

Campaigns are staff work end to end: the send action has no citizen surface, and applying it requires the admin grant.

What's guaranteed

  • Consent is compiled, not remembered. The recipient rule's declared purpose is ANDed into every audience resolution — a segment authored with no consent conditions still only matches citizens who granted campaign_outreach. Preview resolves through the same compiler, so the audience you preview is the audience the send reaches. (See Privacy & Pseudonymity for the declaration pattern.)
  • One send per campaign. The action's submission criteria refuse a campaign whose sent_at is already set. Re-targeting is a new campaign, never a re-fire — no audience is ever double-sent.
  • Fail-closed audiences. An empty or unresolvable segment never widens to "everyone," and a pseudonymized entity is refused as a segment source at compile time — campaign targeting can't be used to walk pseudonyms back to identities.
  • Erasure-safe by construction. The delivery ledger's recipient identities are scrubbed by the platform erasure walk; the per-campaign tallies survive, so the send log keeps its analytical value after any citizen is erased.
  • Auditable. Every delivery attempt is a ledger row; every send is an action execution with the same audit trail as any other operation.

Extending mass communication

  • A new vertical wants outreach — nothing to declare. The entity, the send action, the notification rule, and the consent gate are platform rows already present on every tenant.
  • A domain wants scoped campaigns — fill the context pair from your own entity (the civic pattern). It is traceability for lists and reporting; dispatch never depends on it.
  • A new message — author a template in the shared catalog and relate campaigns to it; templates support the same inheritance and per-organization overrides as every notification template.
  • A different audience shape — segments are declared conditions over identity-rooted entities. New fields and relations declared in sheets become segmentable automatically; the consent invariant rides along regardless of what the segment says.

Atelier — declare your application, generate the product.