Skip to main content

Futura campaign go-live testing

Use this runbook before launching a Futura WhatsApp campaign. The flow depends on typed segment definitions, a materialized recipient snapshot, template personalization, normalized button replies, consent handling, and staffed manual follow-up for interested replies.
The durable BORD-433/BORD-441 launch package lives in planning/campaigns/futura-launch-runbook.md, planning/campaigns/futura-dry-run-fixtures.json, planning/futura/futura-strategy-templates-compliance.md, and planning/campaigns/bord-431-campaign-journey-scope note.md.
BORD-431 campaign response start_journey is not implemented for the MVP launch. Futura production route metadata must use tag/contact attribute/open conversation/opt-out actions only, with manual human follow-up for interested contacts.
Do not use this checklist to send to a production audience until the campaign has a frozen recipient snapshot, an approved template, and an explicit opt-out path.

Scope

The just-merged campaign flow supports:
  • audience definitions with all, tags, or segment targeting;
  • typed custom-field filters for segment audiences;
  • recipient materialization into campaign_recipients before dispatch;
  • template binding by approved template name or template ID, locale, and body variables;
  • normalized button reply IDs for campaign and journey routing;
  • campaign-button opt-out events written to the consent ledger;
  • deterministic route actions for contact tags, contact attributes, open conversations, and opt-outs.
The MVP launch explicitly does not include BORD-431 campaign response start_journey execution.

Campaign metadata contract

Futura campaign drafts should carry the v1 campaign metadata alongside the legacy campaign columns. The metadata is the go-live contract operators review before materialization.

Preflight checklist

1

Confirm the audience source

Choose tags for the initial Futura list or segment when the campaign depends on contact attributes. Avoid all unless the workspace explicitly approved a full-list send.
2

Validate typed segment filters

For segment audiences, check every filter before materialization. Supported fields are tags, name, phone, and custom_fields. Custom fields use key=value syntax; gt and lt comparisons must compare number or date values, either from custom field definitions or values that parse as numbers or ISO-style dates.
3

Preview the segment count

Preview the segment in the app or via the same backend path used by previewSegment. Confirm the count and a sample of contacts against the campaign brief.
4

Check consent before snapshotting

The materializer skips contacts whose consent_state is opted_out or whose legacy subscriber_status is unsubscribed. Confirm the expected opt-out count with the campaign owner before continuing.
5

Verify the template binding

Confirm the WhatsApp template is approved in Meta and in Switchbord, the locale matches the approved language code, every body variable has a source plus fallback, body value propositions use WhatsApp bolding, and the opt-out quick reply is visible as Stop promozioni or Non scrivermi rather than STOP.
6

Freeze routes and safety limits

Confirm responseRoutes only uses the supported button IDs: futura_not_booked, futura_already_booked, and marketing_opt_out. Confirm route actions do not include start_journey for MVP launch. Confirm launchSafety.maxRecipients is at or below the approved send size.

Materialize recipients

Materialization turns the audience definition into durable campaign_recipients rows. Treat this as the audience snapshot for the send. Expected behavior:
  • reads the campaign audienceDefinition from v1 metadata;
  • resolves all, tag, or segment audiences within the workspace;
  • deduplicates contacts by ID;
  • skips opted-out or unsubscribed contacts;
  • inserts new queued recipients with a unique campaign_id,contact_id key;
  • updates campaign metadata with materialization.materializedCount, skippedOptOutCount, duplicateCount, and materializedAt.
After materialization, verify:
  • materializedCount matches the approved count after consent skips;
  • skippedOptOutCount is explainable and nonzero only when expected;
  • a second materialization run reports duplicates instead of creating duplicate recipients;
  • no campaign_recipients rows were created for contacts outside the workspace;
  • no rows exist for known opted-out test contacts.

Dispatch and personalization checks

Campaign dispatch only processes recipients in queued status while the campaign is running. It sends in batches and requeues campaign.dispatch until no queued recipients remain. For each test recipient, verify:
  • a conversation exists or is created in the expected channel;
  • an outbound messages row is queued with send_mode: template and author_label: Campaign;
  • payload.templateName is the bound template name;
  • payload.templateLocale is present when a locale is configured;
  • payload.templateComponents includes body parameters in index order;
  • contact.first_name resolves from contacts.metadata.first_name first, then from the first token of contacts.name, then to the configured fallback;
  • the rendered message body preview replaces {{1}}, {{2}}, and later placeholders consistently with the template components;
  • opted-out recipients encountered at dispatch are marked failed with error_code: skipped_opt_out and are not sent.
Recipient materialization skips known opt-outs up front, but dispatch rechecks consent so contacts who opt out between materialization and send are still blocked.

Button reply normalization

Inbound webhook processing normalizes both WhatsApp button formats into a single button ID before route handling:
  • legacy reply buttons: message.button.payload;
  • interactive buttons: message.interactive.button_reply.id;
  • interactive list replies: message.interactive.list_reply.id.
Test all campaign buttons using their exact payload IDs: Reject route definitions that use button labels, translated titles, spaces, or casing variants as keys. Routes must use normalized IDs, not display text. For the marketing_opt_out button:
  1. Send a test campaign message to a consenting seed contact.
  2. Reply by tapping the opt-out button, not by typing a STOP keyword.
  3. Confirm a consent_events row is written with event_type: opted_out, source: campaign_button, actor: system, and metadata containing the buttonId and provider message ID.
  4. Confirm the contact subscriber_status is updated to unsubscribed and the contact is no longer eligible for subsequent campaign materialization.
  5. Confirm later dispatch attempts skip the contact before creating a Meta send.
Keyword opt-out handling still applies for text STOP/START style messages, but campaign-button opt-out must work without keyword parsing.

Manual follow-up instead of campaign-started journeys

BORD-431 campaign response start_journey execution is scope noted from the MVP launch. A route action with type: "start_journey" is accepted by the domain schema but is not executed as a campaign-to-journey attachment/start path. Operators must not depend on it for Futura launch follow-up. Use this manual follow-up path:
  • futura_not_booked tags the contact, records futura_response=not_booked, and opens/routes the conversation to sales;
  • a human operator reviews the conversation and sends the next approved reply manually;
  • futura_already_booked tags or marks the contact as already booked and requires no automatic journey;
  • marketing_opt_out records consent opt-out and must never trigger a marketing follow-up.
The later BORD-431 follow-up PR must add idempotent journey start/attachment with campaign context before this runbook can rely on automated journey starts.

Operational go-live checklist

Before production launch:
  • Campaign metadata validates against v1 schema.
  • Segment preview or tag count matches the signed-off audience.
  • Typed custom-field filters were tested with at least one matching and one non-matching seed contact.
  • Recipient materialization counts were recorded in launch notes.
  • Approved template name, locale, and body-variable fallbacks were verified.
  • Template component payloads were inspected for at least two seed contacts.
  • Button IDs were tested from WhatsApp, not only from mocked webhook payloads.
  • marketing_opt_out created a campaign_button consent ledger event.
  • Post-opt-out materialization and dispatch both skipped the seed contact.
  • Manual follow-up staffing is assigned for futura_not_booked and any optional futura_already_booked replies.
  • Production route metadata contains no start_journey actions.
  • campaign.dispatch and message.dispatch outbox jobs drained without unexpected failures.
  • Delivery, read, failed, and skipped counts were compared with campaign detail metrics.
  • Incident owner and rollback/pause decision point are documented.

Rollback and pause criteria

Pause the campaign and stop queue processing if any of these happen:
  • materialization includes contacts outside the approved audience;
  • opt-out contacts receive campaign messages;
  • button replies are recorded as display labels instead of normalized IDs;
  • launch planning depends on automatic campaign-to-journey start before BORD-431 is implemented;
  • Meta returns sustained policy or template errors for the Futura template;
  • delivery failures exceed the campaign owner’s agreed threshold.
When pausing, record campaign ID, workspace ID, materialization counts, current queued/accepted/failed recipient counts, and representative outbox job IDs in the incident notes.