Audience Segments
A segment is a saved filter over your riders. Engage evaluates every segment against your live database, so the count you see is the count that will receive your next send.
Navigation
Access segments from Engage > Segments.
How Segmentation Works
Each segment definition is a tree of AND / OR / NOT groups containing rules. The Engage backend compiles that tree into a parameterized SELECT against customers joined to per-customer rollup views (engage_ride_agg, engage_wallet_agg, engage_subscription_agg, etc.) and runs it through the engage_run_segment_sql RPC.
The RPC enforces a SELECT-only whitelist and bans semicolons, so a segment definition can never run an arbitrary write. Service role gates the call.
Available Attributes
| Source | Attributes |
|---|---|
customers | created_at, language, city, country, timezone, marketing_email_consent, marketing_sms_consent, marketing_push_consent, push_token, rider_score, rider_score_tier |
rides (rollup) | first_ride_at, last_ride_at, total_count, total_revenue, force_ended_count |
wallet_transactions (rollup) | current_balance, lifetime_topup, last_topup_at, auto_topup_enabled |
subscriptions (rollup) | active_plan, status, next_renewal_at, churned_at |
violations (rollup) | total_count, last_violation_at, severity_max |
referrals, loyalty | counts, tier, points balance |
The rollup views are evaluated at query time, not pre-aggregated. There is no staleness window.
Operators
| Operator | Works on | Example |
|---|---|---|
equals | text, number, boolean | language = "es" |
not_equals | text, number, boolean | country != "US" |
greater_than | number, date | rides.total_count > 5 |
less_than | number, date | wallet.current_balance < 3 |
within_last | date | last_ride_at within last 30 days |
more_than_ago | date | last_ride_at more than 60 days ago |
is_set / is_null | any | push_token is_set |
in | text | language in ("en", "es") |
Boolean Composition
Group rules with AND, OR, NOT. Groups can nest up to 4 levels deep (a validation cap that catches most accidental builder loops without hampering real segments).
Example - Re-Engagement Candidates
AND
├── rides.last_at more than 60 days ago
├── rides.last_at within last 180 days
├── marketing_email_consent = true
└── NOT
└── violations.severity_max >= "high"
That segment finds riders who used to be active, are not yet stale, can be emailed, and have no serious safety issues.
Live Count
Every segment shows a live count that is cached for 60 seconds.
- Build your rules.
- Click Preview.
- The count appears with a sample of 10 matching rows for sanity-checking.
The 60-second cache means refreshing the page right after a save shows the same count - that is intentional, not stale data.
Saving and Reusing
Saved segments live on your subaccount and are RLS-scoped - no other operator can see them.
- Click Save.
- Name the segment (
Active 30d,High-Value Lapsed, etc.). - The segment now appears in the Audience dropdown of the campaign composer and journey builder.
You can edit a saved segment at any time. Editing does not affect campaigns already in flight - those use the audience that was compiled at send time.
CSV Export
To pull a segment out for external analysis, click Export CSV on the segment detail page. The CSV streams from the same compiled SQL.
Ad-Hoc Segments
You do not have to save every segment. The campaign composer accepts an inline filter for one-off sends:
- In the campaign composer, choose Audience > Custom Filter.
- Build the rules.
- Engage will use them for this send only.
This is the right move for promo blasts that target a unique cut.
Recommended Starter Segments
Build these first - they cover 80% of common sends:
| Segment | Definition |
|---|---|
Active 30d | last_ride_at within last 30 days |
Lapsed 30-60d | last_ride_at between 30 and 60 days ago |
Lapsed 60-90d | last_ride_at between 60 and 90 days ago |
Deep Lapsed 90d+ | last_ride_at more than 90 days ago |
Low Balance, No Auto Top-Up | wallet.current_balance < 3 AND wallet.auto_topup_enabled = false |
New Signups, No Ride Yet | created_at within last 14 days AND rides.total_count = 0 |
Top 10% by Ride Count | rides.total_count >= 50 (adjust to match your top 10%) |
Spanish Speakers Active | language = "es" AND last_ride_at within last 60 days |
Subscription Renewal Next 7d | subscription.next_renewal_at within next 7 days AND subscription.status = "active" |
Performance and Limits
- Segment SQL is parameterized - no string interpolation, no injection surface.
- Validation caps nesting depth at 4 to prevent runaway compiles.
- Counts are cached 60 seconds per segment.
- Large segments (>100k riders) compile fine, but sends are subject to rate limits.
Best Practices
- Always preview before saving - misnamed columns and typos show up in the count.
- Layer consent into every segment - if you are sending email, include
marketing_email_consent = true. The dispatcher will still suppress non-consenting riders, but a clean segment count avoids confusion. - Use named segments for recurring sends. Ad-hoc filters are fine for one-off blasts but get unwieldy if you reuse them.
- Watch your active rate. If a segment is too narrow, you will undercount the audience and underestimate impact. If it is too broad, you risk over-mailing.
Troubleshooting
Segment count is zero
- Did you check both
marketing_*_consentflags and the rollup view filters? - New customer columns (e.g.,
rider_score_tier) are nullable - aNOT NULLstyle rule on them will exclude everyone without a score.
Count looks too high
- The rollup views ignore deleted customers via
is_deleted = false. Check whether you are inadvertently including test or staff accounts.
Edit broke a live campaign
- It did not. Campaigns snapshot their audience at compile time. Edits to the segment take effect on the next send.
Need Help?
For segmentation help, contact support@levyelectric.com.