Campaign Builder
A campaign is a single send (or a recurring digest) to an audience segment. The composer lets you choose channel, audience, template, schedule, and optional A/B variants.
Navigation
Access campaigns from Engage > Campaigns.
Campaign Types
| Type | When to use |
|---|---|
| One-time | A promo blast, an announcement, a one-off win-back |
| Recurring | A daily or weekly digest (e.g., "your weekly ride summary") |
| Journey step | Sent as part of a Journey, not directly |
Recurring campaigns evaluate the audience segment fresh on every send, so a "Lapsed 30d" recurring weekly digest will see different riders each week.
Channels
| Channel | Provider | Defaults |
|---|---|---|
| Postmark (Levy domain or your custom) | List-Unsubscribe headers set, legal address required | |
| SMS | Telnyx primary, Twilio failover | Quiet hours 9 PM - 9 AM recipient-local; STOP keyword enforced |
| Push | Expo (APNs + FCM) | Token validation, permanent-failure handling |
| Multi-channel | Cascading | Try push first, fall back to email if no token |
Composing a Campaign
Step 1 - Basics
- Click New Campaign.
- Set the name (used internally only).
- Choose the channel.
- Choose the audience segment.
Step 2 - Template
Assign one of your library templates. The composer shows a side-by-side preview with mobile and desktop toggles. Variables resolve against the first matching customer in your segment so you see real names, not placeholders.
If you have not built templates yet, clone a Levy Recommended template - see Pre-Built Templates.
Step 3 - Variables and UTM Tags
Variables use double-braces and resolve at send time:
Hey {{customer.first_name | default:"there"}},
You have {{wallet.balance | currency}} in your wallet
and your last ride was {{ride.last_at | date:"MMM D"}}.
Supported filters:
| Filter | Example | Output |
|---|---|---|
date:"FORMAT" | {{ride.last_at | date:"MMM D"}} | Apr 12 |
currency | {{wallet.balance | currency}} | $4.50 |
uppercase | {{customer.first_name | uppercase}} | MARIA |
lowercase | {{customer.first_name | lowercase}} | maria |
default:"X" | {{customer.first_name | default:"there"}} | there if null |
truncate:N | {{ride.notes | truncate:50}} | First 50 chars + ... |
Default UTM tags from Settings > Defaults are appended to every link. Override per campaign in the Tracking tab.
Step 4 - Schedule
| Schedule | Behavior |
|---|---|
| Immediate | Queued the moment you hit Send |
| Scheduled at | Queued at a specific timestamp |
| Recurring | Daily, weekly, or monthly |
If your segment has per-customer timezones on customers.timezone, you can opt into Send in recipient timezone - the dispatcher staggers the queue to deliver at the target local time for each rider.
Step 5 - A/B Variants (Optional)
You can attach 2-4 variants per campaign. The Bayesian winner picker auto-promotes a winner at 95% confidence after at least 500 sends per variant. See A/B Testing.
Step 6 - Dry Run
Click Dry Run before any large send. The dispatcher walks the full pipeline without calling the provider and reports:
- How many sends would happen
- How many would be suppressed (with reasons)
- How many would be rate-limited (queued for later batches)
- Which variables resolved cleanly, and which fell back to defaults
A dry run consumes no provider quota.
Step 7 - Send
- Click Send.
- The campaign status moves from
drafttoqueuedtosendingtosent. - Engagement events stream into the funnel view as they arrive from providers.
Idempotency and Retries
Engage prevents duplicate sends with multiple safeguards:
campaign_sends.idempotency_keyis UNIQUE. A retry insert collides at the constraint and is dropped.campaign_versionsis UNIQUE on(campaign_id, variant_id). Replaying a send never duplicates the snapshot.- 24h dedup across campaigns. The same customer cannot receive the same template twice in 24 hours, even if two different campaigns target them.
If you accidentally hit Send twice, the second call is a no-op.
Suppression Behavior
Before any send, the dispatcher checks the suppression list:
| Suppression source | Effect |
|---|---|
| Per-subaccount opt-out | Skipped, logged as suppressed_subaccount |
| Global opt-out (hard bounce / STOP / complaint) | Skipped, logged as suppressed_global |
| Missing channel consent | Skipped, logged as no_consent |
| Quiet hours (SMS only) | Held in queue until 9 AM local |
See Suppression List for managing entries.
Recurring Campaigns
Recurring sends re-evaluate the segment every cycle. To stop a recurring campaign:
- Open the campaign.
- Click Pause (stops the next cycle but preserves history) or End (terminal).
Duplicating a Campaign
Click Duplicate on any campaign to clone it. The duplicate inherits everything (audience, template, schedule, variants) but starts in draft status so you can edit before sending.
Analytics
Every campaign has a built-in analytics page. See Engagement Analytics.
The headline metrics:
- Delivered / bounced / complained
- Open rate (email/push only)
- Click rate (email/SMS only)
- Conversion (custom goal hit within attribution window)
- Per-variant breakdown when A/B is on
- Per-language breakdown for multilingual templates
Best Practices
- Always dry-run before a large send. It is free and catches misnamed variables.
- Use UTM tags. Without them you cannot prove which campaigns drove rides or top-ups.
- Schedule for the rider, not for you. A 10 AM ET send is 7 AM PT - which is fine, until you have West Coast riders. Use recipient-timezone sending whenever your audience spans timezones.
- Cap your blast cadence. More than 2 emails or 1 SMS per week per rider tends to spike unsubscribes. Engage will not stop you, but the suppression rate will tell you when you have crossed the line.
Troubleshooting
Campaign stuck in queued
Check engage-dispatch cron status under Engage > Settings > Diagnostics. If the last run was more than 5 minutes ago, the cron is paused or failing - contact support.
High suppression count on a dry run
Often this is missing consent flags. Check the segment definition - does it include marketing_email_consent = true?
Variables show as {{customer.first_name}} literally
The template was not saved with the variable wrapper. Re-open the template, ensure it has double-braces, save, and re-dry-run.
A/B variant traffic split looks wrong
Variant assignment is deterministic per recipient (seeded by customer ID), so the actual split converges to your target only at high N. Below ~200 sends, expect noise.
Need Help?
For campaign help, contact support@levyelectric.com.