Segments
This page covers narrowing a campaign's audience to the people a message is for. It is for the store owner who wants to mail "people who bought Subscriptions but not Licenses and have not opened anything in ninety days" and have that mean the same thing next month.
A segment is a saved set of audience conditions, evaluated against the current database each time it is used. A customer can match immediately after a purchase.
Before you begin
- At least one list with people on it. See Subscribers, lists and tags.
kahunacart.newsletters.manageto build or edit one.
Build a segment
- Go to Newsletters → Audience.
- Click Build a segment.
- Enter a name.
- Leave the list on Everybody subscribed, or pick a list to narrow it to.
- Add conditions to All of these, Any of these and None of these.
- Read the count in the panel beside the form.
- Click Save.
The segment is now offered on the campaign editor's segment picker.
Leave the list on "Everybody subscribed" and the segment can be used from any campaign. Pick a list and people also have to be subscribed on it, so only a campaign going to that list can use the segment. Most segments want the first.
A condition is three things: the field, the question, and the answer. The second select carries only the questions that field can be asked, and the third depends on both. A list or a tag is a picker, money carries the base currency's symbol and is typed in whole units, a product is a search box showing the title and the SKU, and a date asks for a date or a number of days. Some questions have no answer to fill in.
Changing the field resets the question and the answer.
The three groups
| Group | Means |
|---|---|
| All of these | Every one of these is true. |
| Any of these | At least one of these is true. |
| None of these | Not one of these is true. |
The three are combined with "and", and each may be empty. A segment with nothing in any of the three is everybody on its list.
There is a ceiling of forty conditions across the three groups. When a question needs a tree of ands and ors, it has become a report rather than a segment.
The condition vocabulary
| Field | Type | Operators | Reads |
|---|---|---|---|
list |
select | is, is_not |
Whether they are subscribed to a list. A pending or departed membership does not count. |
tag |
select | is, is_not |
Whether they carry a tag. has and has_not are accepted as spellings of the same two. |
status |
select | is, is_not |
The subscriber's own status: pending, subscribed, unsubscribed, bounced or complained. |
subscribed_at |
date | before, after, within_days, older_than_days |
When they joined. |
source |
select | is, is_not |
How they joined: the signup form, checkout, their account, an import, the API or the store. |
language |
text | is, contains, is_set, is_empty |
The language they read the store in. Compared without regard to case on every database. |
has_customer |
bool | is_true, is_false |
Whether the address is linked to a customer record. |
customer_group |
select | is, is_not |
Which customer group that customer is in. |
order_count |
number | eq, gte, lte |
How many completed orders they have. Somebody with no customer record has none. |
total_spent |
money | gte, lte |
The sum of those orders' totals. Written in the store's base currency the way a price is: 100.00. On a store that sells in more than one currency only the orders in the base currency are added, because 120,000 yen and 40.00 euro are not 120,040 of anything. The field's label says so. Orders in other currencies don't count towards it. |
last_order_at |
date | before, after, within_days, older_than_days |
Their most recent completed order. |
first_order_at |
date | before, after, within_days, older_than_days |
Their earliest completed order. |
bought_product |
product | is, is_not |
Whether any completed order has a line for that product. |
bought_category |
category | is, is_not |
Whether any completed order has a line for a product filed in that category. |
bought_in_days |
number | gte, lte |
How long ago their most recent completed order was, in days. |
campaign_opened |
select | is, is_not |
Whether they opened a particular campaign. |
campaign_clicked |
select | is, is_not |
Whether they clicked anything in a particular campaign. |
not_opened_in_days |
number | gte |
Whether they were sent something in that many days and opened none of it. |
subscription_status |
select | is, is_not |
The state of their subscription. Contributed by the KahunaCart Subscriptions add-on, and only there when it is installed. |
Two of these need care.
not_opened_in_days is about people the store actually wrote to. They were sent something inside the window, and opened none of it. Somebody who joined last week, or somebody the store has not written to in three months, does not match.
bought_in_days is about people who have bought. Somebody who has never placed an order does not match gte 90. "Has never bought anything" is order_count with eq 0.
What counts as an order
The six order fields all mean the same thing by "order", and it is what the customer's own screen means: completed, and not refunded. A cancelled order is not a sale, and neither is a refunded one. An order that was edited after it was placed keeps its old lines in the database, and only the current ones are read.
Order dates are the moment the order completed where there is one, and the moment it was created otherwise.
Who is always left out
Segments always exclude suppressed, unsubscribed and unconfirmed subscribers. This rule cannot be disabled.
| Left out | Means |
|---|---|
| suppressed | The address is on the suppression list: a hard bounce, a complaint, or one a merchant added. This beats everything else. |
| never confirmed | They signed up and never clicked the confirmation link, or their membership of the segment's list is still pending. Usually this means the confirmation email is not arriving. |
| unsubscribed | They left. Either the store entirely, or the list the segment is narrowed to. |
So a preview reads "2,410 people, 14 unsubscribed and 2 suppressed are left out". Somebody who does not match the conditions is not in any of those numbers.
The count beside the form
Everything you change is counted as you build it. The panel beside the form gives the number, one line for who is left out and why, and ten of the people by name. Each of the ten opens that person, so you can check the segment means what you think before you send to it.
A condition you have not finished filling in is left out of the count, and the panel says so. A condition the store refuses is marked on its own row with the reason.
Nothing is saved until you press Save, and leaving with unsaved changes asks first.
How counts refresh
The number beside a segment on the Audience screen is worked out by the worker rather than on every page view, because answering a segment reads the whole subscriber table.
The worker recounts anything whose number is older than segments.count_every_minutes (fifteen by default), twenty-five segments at a time. It needs no cron entry of its own: the store already runs bin/plugin kahunacart work every minute.
Every screen prints how old the number it is showing is. Editing a segment's conditions or its list throws the cached number away.
To get a true number now, use Recount on the segment, or:
bin/plugin kahunacart-newsletters segments:count
bin/plugin kahunacart-newsletters segments:count --id=4
Setting segments.count_every_minutes to 0 recounts on every worker run. That is useful while you are building a segment and is not something to leave on.
What is refused, and when
A field the store has never heard of, or an operator a field does not accept, is refused with the field named, both when the segment is saved and every time it runs.
The second check matters because a field contributed by an add-on is only in the vocabulary while that add-on is installed. A segment saved last month can name a field that does not exist today. That segment refuses and says which field, rather than quietly matching a smaller set of people, and a campaign pointed at it refuses to start. Turn the add-on back on, or take the condition out.
Values are checked by the field that uses them. A date has to be written as 2026-01-31, a number has to be a whole number, and an amount has to look like a price.
Important
A segment whose last count failed raises an alert on the admin's Needs attention strip, and the alert opens the segment.
Use a segment on a campaign
A campaign names a list and, optionally, a segment. Its audience is the people on the list who also match the segment.
Beside the campaign's list picker is a segment picker offering the segments that apply to this list: the ones limited to it, and the ones limited to nothing. Choosing one changes the count under the picker to the number that will be mailed, with the same line about who is left out.
That number is the one in the confirmation before the campaign goes, and the one the audience snapshot writes when it does.
Duplicate, recount and delete
Each row on the Audience screen shows what the segment last counted, when that count was taken, and how many campaigns still point at it.
- Recount takes a count now. Use it after an import.
- Duplicate makes a copy to change, for a variant of a segment.
- Delete asks once. A segment a draft or scheduled campaign still points at is refused, with those campaigns named. Point the campaign somewhere else first. A campaign that has already sent does not block the delete.
Deleting a segment never touches anybody in it.
Preview one at a terminal
bin/plugin kahunacart-newsletters segments:preview 4
It prints the count, the three excluded numbers and ten of the people. The addresses are masked, as a***@example.com, so a preview pasted into a support ticket or a CI log does not leak a mailing list. If you want the addresses themselves, use export:csv.