Campaign Send
Three nodes that make a campaign: who it goes to, what it says, and the send. The compliance filtering is the part you would otherwise have to remember every time.
Bulk Send in the last lesson took a list and a body and mailed it. That is enough for a digest and not enough for marketing, because marketing has obligations. Someone unsubscribed last month. Someone's address bounced twice. Someone is in two of the four segments you picked and should not get it twice.
Campaigns in Expedify are three nodes rather than one, and the split is exactly those obligations. One builds the audience and applies the rules. One picks the message. One sends and records what happened. Chain them and the compliance is structural rather than something you remembered.
The audience
| Field | What it holds |
|---|---|
audience_ids | List of audience/segment IDs to include |
exclude_unsubscribed | Exclude unsubscribed contacts Defaults to true. |
exclude_bounced | Exclude contacts with bounced emails Defaults to true. |
require_email | Only include contacts with valid email addresses Defaults to true. |
max_audience_size | Maximum number of contacts to include Defaults to 10000. |
sampling_method | Method for sampling when audience exceeds max size One of: random · recent · oldest · engagement_score Defaults to random. |
audience_ids
- What it holds
- List of audience/segment IDs to include
exclude_unsubscribed
- What it holds
- Exclude unsubscribed contacts Defaults to
true.
exclude_bounced
- What it holds
- Exclude contacts with bounced emails Defaults to
true.
require_email
- What it holds
- Only include contacts with valid email addresses Defaults to
true.
max_audience_size
- What it holds
- Maximum number of contacts to include Defaults to
10000.
sampling_method
- What it holds
- Method for sampling when audience exceeds max size One of:
random · recent · oldest · engagement_scoreDefaults torandom.
The exclusions default to on, and that is the point of the node. Unsubscribed and bounced contacts are removed before anything downstream sees them, so no part of your workflow has to know about consent. It also deduplicates across the segments you named — a contact in three of them is one recipient.
Sampling is for testing, and it is the one setting to touch carefully. max_audience_size caps the list and sampling_method decides which ones survive the cap. Set the cap to 50 while you are building and your campaign goes to fifty people instead of five thousand — which is how you want to discover a broken merge field. Just remember to raise it.
The message, and the send
| Field | What it holds |
|---|---|
channel | Communication channel One of: email · sms · whatsapp Defaults to email. |
template_source | Source of template (manual or input) One of: manual · input Defaults to manual. |
template_id | Selected template ID (when source is manual) |
contact_source | Source of contact data One of: input · config Defaults to input. |
contact_input_variable | Input variable containing contact data |
track_opens | Track email opens Defaults to true. |
track_clicks | Track email link clicks Defaults to true. |
channel
- What it holds
- Communication channel One of:
email · sms · whatsappDefaults toemail.
template_source
- What it holds
- Source of template (manual or input) One of:
manual · inputDefaults tomanual.
template_id
- What it holds
- Selected template ID (when source is manual)
contact_source
- What it holds
- Source of contact data One of:
input · configDefaults toinput.
contact_input_variable
- What it holds
- Input variable containing contact data
track_opens
- What it holds
- Track email opens Defaults to
true.
track_clicks
- What it holds
- Track email link clicks Defaults to
true.
Two _source fields decide where the node looks. template_source is manual when you picked a template in the panel and input when an upstream node chose it. contact_source is input when the audience node built the list — which is the normal shape and the default.
Personalisation is not a node. There is no Campaign Personalization step, and people look for one because the video for the old version implied it. The merge fields live in the template and are mapped in this node. If a name is not appearing, the mapping is here.
A worked example
A re-engagement campaign, monthly. The segment is named once, the compliance filtering is the audience node's default behaviour, and the template is chosen by a node rather than pasted into the send.
Audience, template, send
Each node hands the next one exactly what it needs, by variable.
Scroll for all 4 steps →
Read the two variables and the chain explains itself. {{audiencesegmentation_1.contacts}} is the filtered list — not the segment, the survivors of it. {{templateselection_1.template.id}} is the chosen template. Neither node knows anything about the other; the send is what joins them.
Putting the template behind its own node looks like ceremony for one field, and it is — until the choice becomes conditional. A Condition ahead of two Template Selections, both feeding one send, gives you a campaign that says different things to different halves of the audience with no duplicated send configuration.
Which sending node
| Reach for | When |
|---|---|
| One person. Transactional. The content belongs to the workflow. | |
| A list, same content, written in the node. No consent rules beyond what you enforce yourself. | |
| Marketing. The content is a template someone maintains elsewhere, unsubscribes and bounces must be honoured, and you want opens and clicks recorded against a campaign. |
- When
- One person. Transactional. The content belongs to the workflow.
- When
- A list, same content, written in the node. No consent rules beyond what you enforce yourself.
- When
- Marketing. The content is a template someone maintains elsewhere, unsubscribes and bounces must be honoured, and you want opens and clicks recorded against a campaign.
The question that settles it: if this message goes to someone who unsubscribed, is that a problem? If yes, you want the campaign nodes, and you want them even for a small list.
What breaks
Watch out: WhatsApp send needs channel: whatsapp + FOUR ids (template_id, integration_id, workflow_segment_id, workflow_campaign_id).
Also contact_input_variable + a tracking_options.template_parameters typed map (param_N: {type, field, entity}).
A campaign that sends nothing used to report success. When no template resolved, each recipient was skipped in a way that recorded nothing at all — and the summary computed failures as “results minus successes”, which on an empty list is zero. So the run was green, the node was green, and not one message existed. This is fixed: a send that reaches nobody now fails and leaves down the error path. Zero recipients is still a success, because there was nothing to do.
If your Expedify predates that fix, the green run means nothing. Check the campaign's own record — recipients and sends — rather than the workflow's execution status. That is the habit worth keeping regardless of version: the execution log tells you the node ran, and the campaign tells you whether anyone was mailed.
A contact id is not a contact. Pointing contact_input_variable at something like {{trigger_1.new_data.id}} is the natural way to mail whoever fired a trigger, and it used to file the raw UUID as the email address — which the provider then refused. It is now recognised and resolved. Prefer handing this field the audience node's contacts array anyway; it is the shape everything downstream expects.
WhatsApp campaigns need four ids, not one. The warning above is the node's own. It is not a bug — a WhatsApp template send has to be attributed to a campaign and a segment on the provider's side as well as yours — but it does mean a WhatsApp campaign cannot be assembled the same way an email one can. Build the email path first.
Try it
- Add an Audience Segmentation, name one segment in
audience_ids, and run only that node. Read{{alias.segment_count}}and compare it with the segment's own member count — the difference is your unsubscribes and bounces. - Set
max_audience_sizeto 5 and run it again. This is the setting to have on while you build. - Add Template Selection and Campaign Send behind it, wired as above, and send to that audience of five. Check your own inbox and the campaign record.
- Now point
template_input_variableat a template that does not exist and run it. The campaign should fail rather than succeed quietly — if it reports success, your deployment predates the fix and you cannot trust a green run.
Next: Voice Response — the first of four nodes about talking to people on the phone, where the reply has to be spoken while they wait.
Related lessons
Base rates — what a piece of evidence is actually worth
A face-recognition system that is 99.9% accurate and almost entirely wrong, and a number that sent an innocent woman to prison. Both are the same arithmetic, and it is the arithmetic that decides what any piece of evidence is worth.
ReadConfirmation and survivorship — what you never looked for
Two questions about evidence you did not go looking for. One is a rule you have to discover, and one is a pattern in five famous people — and in both, the thing that would have told you the truth is the thing nobody checks.
ReadLoss aversion, sunk cost and regression — what it costs you
Four questions you answer about yourself rather than about a scenario, and your own answers are the finding. Then the pattern that makes praise look useless and criticism look like it works, whatever you actually do.
Read
