@cakemail-org/cakemail-cli 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +12 -0
- package/.env.example +40 -0
- package/.env.test.example +45 -0
- package/CHANGELOG.md +1031 -0
- package/README.md +41 -37
- package/audit-formats.js +128 -0
- package/cakemail.rb +20 -0
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/attributes.js +1 -1
- package/dist/commands/attributes.js.map +1 -1
- package/dist/commands/campaigns.js +1 -1
- package/dist/commands/campaigns.js.map +1 -1
- package/dist/commands/contacts.js +1 -1
- package/dist/commands/contacts.js.map +1 -1
- package/dist/commands/emails.js +1 -1
- package/dist/commands/emails.js.map +1 -1
- package/dist/commands/interests.js +1 -1
- package/dist/commands/interests.js.map +1 -1
- package/dist/commands/lists.js +1 -1
- package/dist/commands/lists.js.map +1 -1
- package/dist/commands/logs.js +1 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/segments.js +1 -1
- package/dist/commands/segments.js.map +1 -1
- package/dist/commands/senders.js +1 -1
- package/dist/commands/senders.js.map +1 -1
- package/dist/commands/suppressed.js +1 -1
- package/dist/commands/suppressed.js.map +1 -1
- package/dist/commands/tags.js +1 -1
- package/dist/commands/tags.js.map +1 -1
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/commands/transactional-templates.js +1 -1
- package/dist/commands/transactional-templates.js.map +1 -1
- package/dist/commands/webhooks.js +1 -1
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/utils/config.js +2 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/errors.js +1 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +32 -4
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/spinner.d.ts +17 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +43 -0
- package/dist/utils/spinner.js.map +1 -0
- package/docs/DOCUMENTATION-STANDARD.md +1068 -0
- package/docs/README.md +161 -0
- package/docs/developer/ARCHITECTURE.md +516 -0
- package/docs/developer/AUTH.md +204 -0
- package/docs/developer/CONTRIBUTING.md +227 -0
- package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
- package/docs/developer/PROJECT_INDEX.md +365 -0
- package/docs/planning/API_COVERAGE.md +1045 -0
- package/docs/planning/BACKLOG.md +1159 -0
- package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
- package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
- package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
- package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
- package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
- package/docs/planning/cakemail-profile-system-plan.md +1121 -0
- package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
- package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
- package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
- package/docs/user-manual/.obsidian/app.json +1 -0
- package/docs/user-manual/.obsidian/appearance.json +1 -0
- package/docs/user-manual/.obsidian/core-plugins.json +33 -0
- package/docs/user-manual/.obsidian/workspace.json +167 -0
- package/docs/user-manual/01-getting-started/01-installation.md +214 -0
- package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
- package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
- package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
- package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
- package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
- package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
- package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
- package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
- package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
- package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
- package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
- package/docs/user-manual/03-email-operations/01-senders.md +490 -0
- package/docs/user-manual/03-email-operations/02-templates.md +444 -0
- package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
- package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
- package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
- package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
- package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
- package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
- package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
- package/docs/user-manual/05-contact-management/01-lists.md +836 -0
- package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
- package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
- package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
- package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
- package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
- package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
- package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
- package/docs/user-manual/07-integrations/02-automation.md +326 -0
- package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
- package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
- package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
- package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
- package/docs/user-manual/09-command-reference/01-config.md +776 -0
- package/docs/user-manual/09-command-reference/02-account.md +652 -0
- package/docs/user-manual/09-command-reference/03-lists.md +958 -0
- package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
- package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
- package/docs/user-manual/09-command-reference/06-segments.md +894 -0
- package/docs/user-manual/09-command-reference/07-senders.md +803 -0
- package/docs/user-manual/09-command-reference/08-templates.md +818 -0
- package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
- package/docs/user-manual/09-command-reference/10-emails.md +807 -0
- package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
- package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
- package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
- package/docs/user-manual/09-command-reference/14-interests.md +630 -0
- package/docs/user-manual/09-command-reference/15-tags.md +584 -0
- package/docs/user-manual/09-command-reference/16-logs.md +656 -0
- package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
- package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
- package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
- package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
- package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
- package/docs/user-manual/11-appendix/04-faq.md +484 -0
- package/docs/user-manual/11-appendix/05-glossary.md +250 -0
- package/docs/user-manual/README.md +0 -0
- package/package.json +13 -61
- package/src/cli.ts +125 -0
- package/src/client.ts +16 -0
- package/src/commands/account.ts +267 -0
- package/src/commands/accounts.ts +78 -0
- package/src/commands/actions.ts +249 -0
- package/src/commands/attributes.ts +139 -0
- package/src/commands/campaign-blueprints.ts +106 -0
- package/src/commands/campaigns.ts +469 -0
- package/src/commands/config.ts +77 -0
- package/src/commands/contacts.ts +612 -0
- package/src/commands/custom-attributes.ts +127 -0
- package/src/commands/dkims.ts +117 -0
- package/src/commands/domains.ts +82 -0
- package/src/commands/email-apis.ts +569 -0
- package/src/commands/emails.ts +197 -0
- package/src/commands/forms.ts +283 -0
- package/src/commands/interests.ts +155 -0
- package/src/commands/links.ts +38 -0
- package/src/commands/lists.ts +406 -0
- package/src/commands/logos.ts +71 -0
- package/src/commands/logs.ts +386 -0
- package/src/commands/reports.ts +306 -0
- package/src/commands/segments.ts +158 -0
- package/src/commands/senders.ts +204 -0
- package/src/commands/sub-accounts.ts +271 -0
- package/src/commands/suppressed-emails.ts +234 -0
- package/src/commands/suppressed.ts +198 -0
- package/src/commands/system-emails.ts +85 -0
- package/src/commands/tags.ts +146 -0
- package/src/commands/tasks.ts +116 -0
- package/src/commands/templates.ts +189 -0
- package/src/commands/tokens.ts +83 -0
- package/src/commands/transactional-emails.ts +374 -0
- package/src/commands/transactional-templates.ts +385 -0
- package/src/commands/users.ts +506 -0
- package/src/commands/webhooks.ts +172 -0
- package/src/commands/workflow-blueprints.ts +123 -0
- package/src/commands/workflows.ts +265 -0
- package/src/types/profile.ts +93 -0
- package/src/utils/auth.ts +272 -0
- package/src/utils/config-file.ts +96 -0
- package/src/utils/config.ts +134 -0
- package/src/utils/confirm.ts +32 -0
- package/src/utils/defaults.ts +99 -0
- package/src/utils/errors.ts +116 -0
- package/src/utils/interactive.ts +91 -0
- package/src/utils/list-defaults.ts +74 -0
- package/src/utils/output.ts +190 -0
- package/src/utils/progress.ts +320 -0
- package/src/utils/spinner.ts +22 -0
- package/tests/IMPLEMENTATION_STATUS.md +258 -0
- package/tests/PTY_SETUP.md +118 -0
- package/tests/PTY_TESTING_GUIDE.md +507 -0
- package/tests/README.md +244 -0
- package/tests/fixtures/api-responses/campaigns.json +34 -0
- package/tests/fixtures/test-config.json +13 -0
- package/tests/helpers/cli-runner.ts +128 -0
- package/tests/helpers/mock-server.ts +301 -0
- package/tests/helpers/pty-runner.ts +181 -0
- package/tests/integration/campaigns-real-api.test.ts +196 -0
- package/tests/integration/setup-integration.ts +50 -0
- package/tests/pty/campaigns.test.ts +241 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
# Contact Segmentation
|
|
2
|
+
|
|
3
|
+
Create dynamic contact segments that automatically update based on conditions and behaviors.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Segmentation allows you to:
|
|
8
|
+
- Target specific audience subsets
|
|
9
|
+
- Create dynamic, automatically-updating groups
|
|
10
|
+
- Filter by engagement, attributes, and behavior
|
|
11
|
+
- Personalize campaigns for different audiences
|
|
12
|
+
- Improve campaign relevance and performance
|
|
13
|
+
- Automate audience selection
|
|
14
|
+
|
|
15
|
+
Unlike static lists, segments are dynamic filters that automatically include contacts matching specified conditions. Segment membership updates in real-time as contact data changes.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Create Your First Segment
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ cakemail segments create 123 -n "Active Subscribers" -c '{
|
|
23
|
+
"match": "all",
|
|
24
|
+
"rules": [
|
|
25
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
26
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-01-01"}
|
|
27
|
+
]
|
|
28
|
+
}'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Output:**
|
|
32
|
+
```
|
|
33
|
+
✓ Segment created: 456
|
|
34
|
+
{
|
|
35
|
+
"id": 456,
|
|
36
|
+
"name": "Active Subscribers",
|
|
37
|
+
"contact_count": 1180
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### View Segment Contacts
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
$ cakemail segments contacts 123 456
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Output:**
|
|
48
|
+
```
|
|
49
|
+
┌────────┬──────────────────────┬────────────┬────────────┬──────────────┐
|
|
50
|
+
│ ID │ Email │ First Name │ Last Name │ Status │
|
|
51
|
+
├────────┼──────────────────────┼────────────┼────────────┼──────────────┤
|
|
52
|
+
│ 501 │ john@example.com │ John │ Doe │ subscribed │
|
|
53
|
+
│ 502 │ jane@example.com │ Jane │ Smith │ subscribed │
|
|
54
|
+
└────────┴──────────────────────┴────────────┴────────────┴──────────────┘
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Segment Basics
|
|
58
|
+
|
|
59
|
+
### Understanding Segments vs Lists
|
|
60
|
+
|
|
61
|
+
**Lists** are static containers:
|
|
62
|
+
- Manually add/remove contacts
|
|
63
|
+
- Members don't change automatically
|
|
64
|
+
- Good for: Distinct audiences, newsletters
|
|
65
|
+
|
|
66
|
+
**Segments** are dynamic filters:
|
|
67
|
+
- Automatically include/exclude based on rules
|
|
68
|
+
- Membership updates as data changes
|
|
69
|
+
- Good for: Behavioral targeting, lifecycle stages
|
|
70
|
+
|
|
71
|
+
**Example:**
|
|
72
|
+
```bash
|
|
73
|
+
# Static list - manually managed
|
|
74
|
+
$ cakemail lists create -n "Newsletter Subscribers"
|
|
75
|
+
$ cakemail contacts add 123 -e "user@example.com"
|
|
76
|
+
|
|
77
|
+
# Dynamic segment - rule-based
|
|
78
|
+
$ cakemail segments create 123 -n "Engaged Users" -c '{
|
|
79
|
+
"match": "all",
|
|
80
|
+
"rules": [
|
|
81
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
82
|
+
]
|
|
83
|
+
}'
|
|
84
|
+
# Automatically includes contacts who opened after March 1
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Segment Conditions
|
|
88
|
+
|
|
89
|
+
Every segment has conditions that define which contacts are included.
|
|
90
|
+
|
|
91
|
+
**Basic structure:**
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"match": "all|any",
|
|
95
|
+
"rules": [
|
|
96
|
+
{
|
|
97
|
+
"field": "field_name",
|
|
98
|
+
"operator": "comparison_type",
|
|
99
|
+
"value": "comparison_value"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Match types:**
|
|
106
|
+
- `all` - Contacts must meet ALL rules (AND logic)
|
|
107
|
+
- `any` - Contacts must meet AT LEAST ONE rule (OR logic)
|
|
108
|
+
|
|
109
|
+
## Creating Segments
|
|
110
|
+
|
|
111
|
+
### By Status
|
|
112
|
+
|
|
113
|
+
**Active subscribers only:**
|
|
114
|
+
```bash
|
|
115
|
+
$ cakemail segments create 123 -n "Active Subscribers" -c '{
|
|
116
|
+
"match": "all",
|
|
117
|
+
"rules": [
|
|
118
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
119
|
+
]
|
|
120
|
+
}'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Unsubscribed for analysis:**
|
|
124
|
+
```bash
|
|
125
|
+
$ cakemail segments create 123 -n "Unsubscribed" -c '{
|
|
126
|
+
"match": "all",
|
|
127
|
+
"rules": [
|
|
128
|
+
{"field": "status", "operator": "equals", "value": "unsubscribed"}
|
|
129
|
+
]
|
|
130
|
+
}'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### By Engagement
|
|
134
|
+
|
|
135
|
+
**Highly engaged:**
|
|
136
|
+
```bash
|
|
137
|
+
$ cakemail segments create 123 -n "Highly Engaged" -c '{
|
|
138
|
+
"match": "all",
|
|
139
|
+
"rules": [
|
|
140
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-02-01"},
|
|
141
|
+
{"field": "last_click_date", "operator": "greater_than", "value": "2024-02-01"}
|
|
142
|
+
]
|
|
143
|
+
}'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Recently opened:**
|
|
147
|
+
```bash
|
|
148
|
+
$ cakemail segments create 123 -n "Opened Last 30 Days" -c '{
|
|
149
|
+
"match": "all",
|
|
150
|
+
"rules": [
|
|
151
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-02-15"}
|
|
152
|
+
]
|
|
153
|
+
}'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Never opened:**
|
|
157
|
+
```bash
|
|
158
|
+
$ cakemail segments create 123 -n "Never Opened" -c '{
|
|
159
|
+
"match": "all",
|
|
160
|
+
"rules": [
|
|
161
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
162
|
+
{"field": "last_open_date", "operator": "is_empty", "value": ""}
|
|
163
|
+
]
|
|
164
|
+
}'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Inactive subscribers:**
|
|
168
|
+
```bash
|
|
169
|
+
$ cakemail segments create 123 -n "Inactive 90+ Days" -c '{
|
|
170
|
+
"match": "all",
|
|
171
|
+
"rules": [
|
|
172
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
173
|
+
{"field": "last_open_date", "operator": "less_than", "value": "2023-12-15"}
|
|
174
|
+
]
|
|
175
|
+
}'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### By Custom Attributes
|
|
179
|
+
|
|
180
|
+
**Premium plan users:**
|
|
181
|
+
```bash
|
|
182
|
+
$ cakemail segments create 123 -n "Premium Users" -c '{
|
|
183
|
+
"match": "all",
|
|
184
|
+
"rules": [
|
|
185
|
+
{"field": "custom_attributes.plan_type", "operator": "equals", "value": "premium"},
|
|
186
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
187
|
+
]
|
|
188
|
+
}'
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**High-value customers:**
|
|
192
|
+
```bash
|
|
193
|
+
$ cakemail segments create 123 -n "High Value Customers" -c '{
|
|
194
|
+
"match": "all",
|
|
195
|
+
"rules": [
|
|
196
|
+
{"field": "custom_attributes.lifetime_value", "operator": "greater_than", "value": "1000"}
|
|
197
|
+
]
|
|
198
|
+
}'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**VIP members:**
|
|
202
|
+
```bash
|
|
203
|
+
$ cakemail segments create 123 -n "VIP Members" -c '{
|
|
204
|
+
"match": "all",
|
|
205
|
+
"rules": [
|
|
206
|
+
{"field": "custom_attributes.is_vip", "operator": "equals", "value": "true"},
|
|
207
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
208
|
+
]
|
|
209
|
+
}'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Recent signups:**
|
|
213
|
+
```bash
|
|
214
|
+
$ cakemail segments create 123 -n "New Signups - Last 7 Days" -c '{
|
|
215
|
+
"match": "all",
|
|
216
|
+
"rules": [
|
|
217
|
+
{"field": "custom_attributes.signup_date", "operator": "greater_than", "value": "2024-03-08"}
|
|
218
|
+
]
|
|
219
|
+
}'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### By Date Ranges
|
|
223
|
+
|
|
224
|
+
**Trial ending soon:**
|
|
225
|
+
```bash
|
|
226
|
+
$ cakemail segments create 123 -n "Trial Ending < 7 Days" -c '{
|
|
227
|
+
"match": "all",
|
|
228
|
+
"rules": [
|
|
229
|
+
{"field": "custom_attributes.trial_end_date", "operator": "less_than", "value": "2024-03-22"},
|
|
230
|
+
{"field": "custom_attributes.trial_end_date", "operator": "greater_than", "value": "2024-03-15"}
|
|
231
|
+
]
|
|
232
|
+
}'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Birthday month:**
|
|
236
|
+
```bash
|
|
237
|
+
#!/bin/bash
|
|
238
|
+
# Create birthday segment for current month
|
|
239
|
+
CURRENT_MONTH=$(date +%m)
|
|
240
|
+
YEAR=$(date +%Y)
|
|
241
|
+
|
|
242
|
+
$ cakemail segments create 123 -n "Birthday This Month" -c "{
|
|
243
|
+
\"match\": \"all\",
|
|
244
|
+
\"rules\": [
|
|
245
|
+
{\"field\": \"custom_attributes.birthday\", \"operator\": \"greater_than\", \"value\": \"$YEAR-$CURRENT_MONTH-01\"},
|
|
246
|
+
{\"field\": \"custom_attributes.birthday\", \"operator\": \"less_than\", \"value\": \"$YEAR-$CURRENT_MONTH-31\"}
|
|
247
|
+
]
|
|
248
|
+
}"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Complex Conditions
|
|
252
|
+
|
|
253
|
+
**Engaged OR recent (ANY match):**
|
|
254
|
+
```bash
|
|
255
|
+
$ cakemail segments create 123 -n "Engaged or Recent" -c '{
|
|
256
|
+
"match": "any",
|
|
257
|
+
"rules": [
|
|
258
|
+
{"field": "last_click_date", "operator": "greater_than", "value": "2024-02-01"},
|
|
259
|
+
{"field": "subscribed_on", "operator": "greater_than", "value": "2024-03-01"}
|
|
260
|
+
]
|
|
261
|
+
}'
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Premium AND engaged:**
|
|
265
|
+
```bash
|
|
266
|
+
$ cakemail segments create 123 -n "Premium Engaged Users" -c '{
|
|
267
|
+
"match": "all",
|
|
268
|
+
"rules": [
|
|
269
|
+
{"field": "custom_attributes.plan_type", "operator": "equals", "value": "premium"},
|
|
270
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-02-01"},
|
|
271
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
272
|
+
]
|
|
273
|
+
}'
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Available Operators
|
|
277
|
+
|
|
278
|
+
### Equality Operators
|
|
279
|
+
|
|
280
|
+
**equals:**
|
|
281
|
+
```json
|
|
282
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
283
|
+
{"field": "custom_attributes.plan", "operator": "equals", "value": "premium"}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**not_equals:**
|
|
287
|
+
```json
|
|
288
|
+
{"field": "status", "operator": "not_equals", "value": "unsubscribed"}
|
|
289
|
+
{"field": "custom_attributes.plan", "operator": "not_equals", "value": "free"}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Comparison Operators
|
|
293
|
+
|
|
294
|
+
**greater_than:**
|
|
295
|
+
```json
|
|
296
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
297
|
+
{"field": "custom_attributes.lifetime_value", "operator": "greater_than", "value": "500"}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**less_than:**
|
|
301
|
+
```json
|
|
302
|
+
{"field": "last_open_date", "operator": "less_than", "value": "2024-01-01"}
|
|
303
|
+
{"field": "custom_attributes.age", "operator": "less_than", "value": "30"}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### String Operators
|
|
307
|
+
|
|
308
|
+
**contains:**
|
|
309
|
+
```json
|
|
310
|
+
{"field": "email", "operator": "contains", "value": "@gmail.com"}
|
|
311
|
+
{"field": "custom_attributes.company", "operator": "contains", "value": "Corp"}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**not_contains:**
|
|
315
|
+
```json
|
|
316
|
+
{"field": "email", "operator": "not_contains", "value": "@spam.com"}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**starts_with:**
|
|
320
|
+
```json
|
|
321
|
+
{"field": "first_name", "operator": "starts_with", "value": "J"}
|
|
322
|
+
{"field": "email", "operator": "starts_with", "value": "admin"}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**ends_with:**
|
|
326
|
+
```json
|
|
327
|
+
{"field": "email", "operator": "ends_with", "value": ".edu"}
|
|
328
|
+
{"field": "last_name", "operator": "ends_with", "value": "son"}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Null/Empty Operators
|
|
332
|
+
|
|
333
|
+
**is_empty:**
|
|
334
|
+
```json
|
|
335
|
+
{"field": "first_name", "operator": "is_empty", "value": ""}
|
|
336
|
+
{"field": "last_open_date", "operator": "is_empty", "value": ""}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**is_not_empty:**
|
|
340
|
+
```json
|
|
341
|
+
{"field": "first_name", "operator": "is_not_empty", "value": ""}
|
|
342
|
+
{"field": "custom_attributes.company", "operator": "is_not_empty", "value": ""}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Segment Workflows
|
|
346
|
+
|
|
347
|
+
### Workflow 1: Customer Lifecycle Segments
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
#!/bin/bash
|
|
351
|
+
# create-lifecycle-segments.sh
|
|
352
|
+
|
|
353
|
+
LIST_ID=123
|
|
354
|
+
|
|
355
|
+
echo "=== Creating Lifecycle Segments ==="
|
|
356
|
+
echo ""
|
|
357
|
+
|
|
358
|
+
# New customers (< 30 days)
|
|
359
|
+
cakemail segments create $LIST_ID -n "New Customers" -c '{
|
|
360
|
+
"match": "all",
|
|
361
|
+
"rules": [
|
|
362
|
+
{"field": "custom_attributes.signup_date", "operator": "greater_than", "value": "2024-02-15"}
|
|
363
|
+
]
|
|
364
|
+
}'
|
|
365
|
+
echo "✓ New Customers segment created"
|
|
366
|
+
|
|
367
|
+
# Active customers (opened in last 30 days)
|
|
368
|
+
cakemail segments create $LIST_ID -n "Active Customers" -c '{
|
|
369
|
+
"match": "all",
|
|
370
|
+
"rules": [
|
|
371
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
372
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-02-15"}
|
|
373
|
+
]
|
|
374
|
+
}'
|
|
375
|
+
echo "✓ Active Customers segment created"
|
|
376
|
+
|
|
377
|
+
# At-risk (no open in 30-90 days)
|
|
378
|
+
cakemail segments create $LIST_ID -n "At Risk" -c '{
|
|
379
|
+
"match": "all",
|
|
380
|
+
"rules": [
|
|
381
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
382
|
+
{"field": "last_open_date", "operator": "less_than", "value": "2024-02-15"},
|
|
383
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2023-12-15"}
|
|
384
|
+
]
|
|
385
|
+
}'
|
|
386
|
+
echo "✓ At Risk segment created"
|
|
387
|
+
|
|
388
|
+
# Lapsed (no open in 90+ days)
|
|
389
|
+
cakemail segments create $LIST_ID -n "Lapsed Customers" -c '{
|
|
390
|
+
"match": "all",
|
|
391
|
+
"rules": [
|
|
392
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
393
|
+
{"field": "last_open_date", "operator": "less_than", "value": "2023-12-15"}
|
|
394
|
+
]
|
|
395
|
+
}'
|
|
396
|
+
echo "✓ Lapsed Customers segment created"
|
|
397
|
+
|
|
398
|
+
echo ""
|
|
399
|
+
echo "=== Lifecycle Segments Created ==="
|
|
400
|
+
cakemail segments list $LIST_ID
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Workflow 2: Engagement Scoring Segments
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
#!/bin/bash
|
|
407
|
+
# create-engagement-segments.sh
|
|
408
|
+
|
|
409
|
+
LIST_ID=123
|
|
410
|
+
|
|
411
|
+
echo "=== Creating Engagement Segments ==="
|
|
412
|
+
echo ""
|
|
413
|
+
|
|
414
|
+
# Highly engaged (opened AND clicked recently)
|
|
415
|
+
cakemail segments create $LIST_ID -n "Highly Engaged" -c '{
|
|
416
|
+
"match": "all",
|
|
417
|
+
"rules": [
|
|
418
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
419
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"},
|
|
420
|
+
{"field": "last_click_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
421
|
+
]
|
|
422
|
+
}'
|
|
423
|
+
|
|
424
|
+
# Moderately engaged (opened but no clicks)
|
|
425
|
+
cakemail segments create $LIST_ID -n "Moderately Engaged" -c '{
|
|
426
|
+
"match": "all",
|
|
427
|
+
"rules": [
|
|
428
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
429
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-02-01"},
|
|
430
|
+
{"field": "last_click_date", "operator": "less_than", "value": "2024-02-01"}
|
|
431
|
+
]
|
|
432
|
+
}'
|
|
433
|
+
|
|
434
|
+
# Low engagement (no opens in 30+ days)
|
|
435
|
+
cakemail segments create $LIST_ID -n "Low Engagement" -c '{
|
|
436
|
+
"match": "all",
|
|
437
|
+
"rules": [
|
|
438
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
439
|
+
{"field": "last_open_date", "operator": "less_than", "value": "2024-02-15"}
|
|
440
|
+
]
|
|
441
|
+
}'
|
|
442
|
+
|
|
443
|
+
echo "✓ Engagement segments created"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Workflow 3: E-commerce Segments
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
#!/bin/bash
|
|
450
|
+
# create-ecommerce-segments.sh
|
|
451
|
+
|
|
452
|
+
LIST_ID=123
|
|
453
|
+
|
|
454
|
+
echo "=== Creating E-commerce Segments ==="
|
|
455
|
+
echo ""
|
|
456
|
+
|
|
457
|
+
# Frequent buyers
|
|
458
|
+
cakemail segments create $LIST_ID -n "Frequent Buyers" -c '{
|
|
459
|
+
"match": "all",
|
|
460
|
+
"rules": [
|
|
461
|
+
{"field": "custom_attributes.purchase_count", "operator": "greater_than", "value": "5"},
|
|
462
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
463
|
+
]
|
|
464
|
+
}'
|
|
465
|
+
|
|
466
|
+
# High-value customers
|
|
467
|
+
cakemail segments create $LIST_ID -n "High Value" -c '{
|
|
468
|
+
"match": "all",
|
|
469
|
+
"rules": [
|
|
470
|
+
{"field": "custom_attributes.lifetime_value", "operator": "greater_than", "value": "1000"}
|
|
471
|
+
]
|
|
472
|
+
}'
|
|
473
|
+
|
|
474
|
+
# Recent purchasers (last 30 days)
|
|
475
|
+
cakemail segments create $LIST_ID -n "Recent Purchasers" -c '{
|
|
476
|
+
"match": "all",
|
|
477
|
+
"rules": [
|
|
478
|
+
{"field": "custom_attributes.last_purchase_date", "operator": "greater_than", "value": "2024-02-15"}
|
|
479
|
+
]
|
|
480
|
+
}'
|
|
481
|
+
|
|
482
|
+
# Cart abandoners
|
|
483
|
+
cakemail segments create $LIST_ID -n "Abandoned Cart" -c '{
|
|
484
|
+
"match": "all",
|
|
485
|
+
"rules": [
|
|
486
|
+
{"field": "custom_attributes.has_active_cart", "operator": "equals", "value": "true"},
|
|
487
|
+
{"field": "custom_attributes.cart_abandoned_date", "operator": "greater_than", "value": "2024-03-10"}
|
|
488
|
+
]
|
|
489
|
+
}'
|
|
490
|
+
|
|
491
|
+
echo "✓ E-commerce segments created"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Workflow 4: SaaS/Subscription Segments
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
#!/bin/bash
|
|
498
|
+
# create-saas-segments.sh
|
|
499
|
+
|
|
500
|
+
LIST_ID=123
|
|
501
|
+
|
|
502
|
+
echo "=== Creating SaaS Segments ==="
|
|
503
|
+
echo ""
|
|
504
|
+
|
|
505
|
+
# Trial users
|
|
506
|
+
cakemail segments create $LIST_ID -n "Active Trials" -c '{
|
|
507
|
+
"match": "all",
|
|
508
|
+
"rules": [
|
|
509
|
+
{"field": "custom_attributes.is_trial", "operator": "equals", "value": "true"},
|
|
510
|
+
{"field": "custom_attributes.trial_end_date", "operator": "greater_than", "value": "2024-03-15"}
|
|
511
|
+
]
|
|
512
|
+
}'
|
|
513
|
+
|
|
514
|
+
# Trial ending soon
|
|
515
|
+
cakemail segments create $LIST_ID -n "Trial Ending Soon" -c '{
|
|
516
|
+
"match": "all",
|
|
517
|
+
"rules": [
|
|
518
|
+
{"field": "custom_attributes.is_trial", "operator": "equals", "value": "true"},
|
|
519
|
+
{"field": "custom_attributes.trial_end_date", "operator": "less_than", "value": "2024-03-22"},
|
|
520
|
+
{"field": "custom_attributes.trial_end_date", "operator": "greater_than", "value": "2024-03-15"}
|
|
521
|
+
]
|
|
522
|
+
}'
|
|
523
|
+
|
|
524
|
+
# Paying customers
|
|
525
|
+
cakemail segments create $LIST_ID -n "Paying Customers" -c '{
|
|
526
|
+
"match": "all",
|
|
527
|
+
"rules": [
|
|
528
|
+
{"field": "custom_attributes.is_paying", "operator": "equals", "value": "true"}
|
|
529
|
+
]
|
|
530
|
+
}'
|
|
531
|
+
|
|
532
|
+
# Inactive users (low usage)
|
|
533
|
+
cakemail segments create $LIST_ID -n "Inactive Users" -c '{
|
|
534
|
+
"match": "all",
|
|
535
|
+
"rules": [
|
|
536
|
+
{"field": "custom_attributes.is_paying", "operator": "equals", "value": "true"},
|
|
537
|
+
{"field": "custom_attributes.last_login", "operator": "less_than", "value": "2024-02-15"}
|
|
538
|
+
]
|
|
539
|
+
}'
|
|
540
|
+
|
|
541
|
+
# Churn risk
|
|
542
|
+
cakemail segments create $LIST_ID -n "Churn Risk" -c '{
|
|
543
|
+
"match": "all",
|
|
544
|
+
"rules": [
|
|
545
|
+
{"field": "custom_attributes.health_score", "operator": "less_than", "value": "40"}
|
|
546
|
+
]
|
|
547
|
+
}'
|
|
548
|
+
|
|
549
|
+
echo "✓ SaaS segments created"
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Workflow 5: Segment Analysis
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
#!/bin/bash
|
|
556
|
+
# analyze-segments.sh
|
|
557
|
+
|
|
558
|
+
LIST_ID=123
|
|
559
|
+
|
|
560
|
+
echo "=== Segment Analysis ==="
|
|
561
|
+
echo ""
|
|
562
|
+
|
|
563
|
+
# Get all segments
|
|
564
|
+
SEGMENTS=$(cakemail segments list $LIST_ID -f json | jq -r '.data[].id')
|
|
565
|
+
|
|
566
|
+
echo "Segment | Name | Count | % of List"
|
|
567
|
+
echo "--------|------|-------|----------"
|
|
568
|
+
|
|
569
|
+
# Get total list size
|
|
570
|
+
TOTAL=$(cakemail contacts list $LIST_ID -f json | jq '.count')
|
|
571
|
+
|
|
572
|
+
for SEG_ID in $SEGMENTS; do
|
|
573
|
+
SEG=$(cakemail segments get $LIST_ID $SEG_ID -f json)
|
|
574
|
+
|
|
575
|
+
NAME=$(echo "$SEG" | jq -r '.name' | cut -c1-20)
|
|
576
|
+
COUNT=$(echo "$SEG" | jq -r '.contact_count')
|
|
577
|
+
|
|
578
|
+
if [ $TOTAL -gt 0 ]; then
|
|
579
|
+
PERCENT=$(echo "scale=1; $COUNT * 100 / $TOTAL" | bc)
|
|
580
|
+
else
|
|
581
|
+
PERCENT="0"
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
printf "%-7s | %-20s | %5d | %5s%%\n" "$SEG_ID" "$NAME" "$COUNT" "$PERCENT"
|
|
585
|
+
done
|
|
586
|
+
|
|
587
|
+
echo ""
|
|
588
|
+
echo "Total list size: $TOTAL"
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Using Segments with Campaigns
|
|
592
|
+
|
|
593
|
+
### Send to Segment
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# Create campaign for segment
|
|
597
|
+
$ cakemail campaigns create \
|
|
598
|
+
-n "VIP Exclusive Offer" \
|
|
599
|
+
-l 123 \
|
|
600
|
+
-s 101 \
|
|
601
|
+
--segment 456 \
|
|
602
|
+
--subject "Exclusive Offer for Our VIPs"
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Preview Segment Before Sending
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
# Check segment size
|
|
609
|
+
$ cakemail segments get 123 456 -f json | jq '.contact_count'
|
|
610
|
+
|
|
611
|
+
# Preview contacts
|
|
612
|
+
$ cakemail segments contacts 123 456 -l 10
|
|
613
|
+
|
|
614
|
+
# Send test to segment sample
|
|
615
|
+
$ cakemail campaigns test 790 -e test@example.com
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Multiple Segments Campaign
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
#!/bin/bash
|
|
622
|
+
# send-to-multiple-segments.sh
|
|
623
|
+
|
|
624
|
+
CAMPAIGN_NAME="March Newsletter"
|
|
625
|
+
LIST_ID=123
|
|
626
|
+
SENDER_ID=101
|
|
627
|
+
SEGMENTS=(456 457 458)
|
|
628
|
+
|
|
629
|
+
for SEG_ID in "${SEGMENTS[@]}"; do
|
|
630
|
+
SEG_NAME=$(cakemail segments get $LIST_ID $SEG_ID -f json | jq -r '.name')
|
|
631
|
+
|
|
632
|
+
echo "Creating campaign for segment: $SEG_NAME"
|
|
633
|
+
|
|
634
|
+
CAMPAIGN_ID=$(cakemail campaigns create \
|
|
635
|
+
-n "$CAMPAIGN_NAME - $SEG_NAME" \
|
|
636
|
+
-l $LIST_ID \
|
|
637
|
+
-s $SENDER_ID \
|
|
638
|
+
--segment $SEG_ID \
|
|
639
|
+
--subject "March Newsletter" \
|
|
640
|
+
-f json | jq -r '.id')
|
|
641
|
+
|
|
642
|
+
echo " Campaign created: $CAMPAIGN_ID"
|
|
643
|
+
|
|
644
|
+
# Schedule for different times
|
|
645
|
+
# (Example: stagger sends)
|
|
646
|
+
sleep 2
|
|
647
|
+
done
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
## Segment Management
|
|
651
|
+
|
|
652
|
+
### Update Segment Conditions
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
# Broaden segment
|
|
656
|
+
$ cakemail segments update 123 456 -c '{
|
|
657
|
+
"match": "any",
|
|
658
|
+
"rules": [
|
|
659
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-01-01"},
|
|
660
|
+
{"field": "last_click_date", "operator": "greater_than", "value": "2024-01-01"}
|
|
661
|
+
]
|
|
662
|
+
}'
|
|
663
|
+
|
|
664
|
+
# Narrow segment
|
|
665
|
+
$ cakemail segments update 123 456 -c '{
|
|
666
|
+
"match": "all",
|
|
667
|
+
"rules": [
|
|
668
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
669
|
+
{"field": "custom_attributes.plan", "operator": "equals", "value": "premium"},
|
|
670
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
671
|
+
]
|
|
672
|
+
}'
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Rename Segment
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
$ cakemail segments update 123 456 -n "Highly Engaged Premium Users"
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### Clone Segment
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
#!/bin/bash
|
|
685
|
+
# clone-segment.sh
|
|
686
|
+
|
|
687
|
+
SOURCE_SEG=$1
|
|
688
|
+
NEW_NAME="$2"
|
|
689
|
+
LIST_ID=123
|
|
690
|
+
|
|
691
|
+
# Get source segment
|
|
692
|
+
SOURCE=$(cakemail segments get $LIST_ID $SOURCE_SEG -f json)
|
|
693
|
+
CONDITIONS=$(echo "$SOURCE" | jq -c '.conditions')
|
|
694
|
+
|
|
695
|
+
# Create new segment with same conditions
|
|
696
|
+
cakemail segments create $LIST_ID -n "$NEW_NAME" -c "$CONDITIONS"
|
|
697
|
+
|
|
698
|
+
echo "✓ Segment cloned"
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Delete Unused Segments
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
#!/bin/bash
|
|
705
|
+
# delete-empty-segments.sh
|
|
706
|
+
|
|
707
|
+
LIST_ID=123
|
|
708
|
+
|
|
709
|
+
echo "=== Deleting Empty Segments ==="
|
|
710
|
+
echo ""
|
|
711
|
+
|
|
712
|
+
SEGMENTS=$(cakemail segments list $LIST_ID -f json | jq -r '.data[].id')
|
|
713
|
+
|
|
714
|
+
for SEG_ID in $SEGMENTS; do
|
|
715
|
+
SEG=$(cakemail segments get $LIST_ID $SEG_ID -f json)
|
|
716
|
+
NAME=$(echo "$SEG" | jq -r '.name')
|
|
717
|
+
COUNT=$(echo "$SEG" | jq -r '.contact_count')
|
|
718
|
+
|
|
719
|
+
if [ $COUNT -eq 0 ]; then
|
|
720
|
+
echo "Deleting empty segment: $NAME (ID: $SEG_ID)"
|
|
721
|
+
cakemail segments delete $LIST_ID $SEG_ID --force
|
|
722
|
+
fi
|
|
723
|
+
done
|
|
724
|
+
|
|
725
|
+
echo "✓ Cleanup complete"
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
## Advanced Segmentation
|
|
729
|
+
|
|
730
|
+
### Time-Based Dynamic Segments
|
|
731
|
+
|
|
732
|
+
```bash
|
|
733
|
+
#!/bin/bash
|
|
734
|
+
# update-time-based-segments.sh
|
|
735
|
+
|
|
736
|
+
LIST_ID=123
|
|
737
|
+
SEGMENT_ID=456
|
|
738
|
+
|
|
739
|
+
# Update to rolling 30-day window
|
|
740
|
+
THIRTY_DAYS_AGO=$(date -d "30 days ago" +%Y-%m-%d)
|
|
741
|
+
|
|
742
|
+
cakemail segments update $LIST_ID $SEGMENT_ID -c "{
|
|
743
|
+
\"match\": \"all\",
|
|
744
|
+
\"rules\": [
|
|
745
|
+
{\"field\": \"last_open_date\", \"operator\": \"greater_than\", \"value\": \"$THIRTY_DAYS_AGO\"}
|
|
746
|
+
]
|
|
747
|
+
}"
|
|
748
|
+
|
|
749
|
+
echo "✓ Updated to rolling 30-day window"
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
**Schedule with cron:**
|
|
753
|
+
```bash
|
|
754
|
+
# Update daily at midnight
|
|
755
|
+
0 0 * * * /path/to/update-time-based-segments.sh
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
### Combine Segments Logically
|
|
759
|
+
|
|
760
|
+
```bash
|
|
761
|
+
# Segment A OR Segment B (contacts in either)
|
|
762
|
+
# Export both, combine, deduplicate
|
|
763
|
+
|
|
764
|
+
# Segment A AND Segment B (contacts in both)
|
|
765
|
+
# Use nested conditions to replicate both segment rules
|
|
766
|
+
|
|
767
|
+
# Segment A NOT Segment B (in A but not B)
|
|
768
|
+
# Export both, find difference
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### Export Segment for External Processing
|
|
772
|
+
|
|
773
|
+
```bash
|
|
774
|
+
#!/bin/bash
|
|
775
|
+
# export-segment.sh
|
|
776
|
+
|
|
777
|
+
LIST_ID=123
|
|
778
|
+
SEGMENT_ID=$1
|
|
779
|
+
|
|
780
|
+
if [ -z "$SEGMENT_ID" ]; then
|
|
781
|
+
echo "Usage: $0 <segment-id>"
|
|
782
|
+
exit 1
|
|
783
|
+
fi
|
|
784
|
+
|
|
785
|
+
# Get segment contacts
|
|
786
|
+
cakemail segments contacts $LIST_ID $SEGMENT_ID -f json | \
|
|
787
|
+
jq -r '.data[] | [.email, .first_name, .last_name] | @csv' > segment-export.csv
|
|
788
|
+
|
|
789
|
+
echo "✓ Exported to segment-export.csv"
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
## Best Practices
|
|
793
|
+
|
|
794
|
+
### 1. Name Segments Descriptively
|
|
795
|
+
|
|
796
|
+
```bash
|
|
797
|
+
# Good names
|
|
798
|
+
"Highly Engaged - Last 30 Days"
|
|
799
|
+
"Premium Plan - Active"
|
|
800
|
+
"Trial Ending < 7 Days"
|
|
801
|
+
"Cart Abandoned - Last Week"
|
|
802
|
+
|
|
803
|
+
# Avoid
|
|
804
|
+
"Segment 1"
|
|
805
|
+
"Test"
|
|
806
|
+
"Users"
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### 2. Start Broad, Then Narrow
|
|
810
|
+
|
|
811
|
+
```bash
|
|
812
|
+
# Start with broad segment
|
|
813
|
+
$ cakemail segments create 123 -n "Engaged Users" -c '{
|
|
814
|
+
"match": "all",
|
|
815
|
+
"rules": [
|
|
816
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-01-01"}
|
|
817
|
+
]
|
|
818
|
+
}'
|
|
819
|
+
|
|
820
|
+
# Preview size
|
|
821
|
+
$ cakemail segments get 123 456 -f json | jq '.contact_count'
|
|
822
|
+
|
|
823
|
+
# If too large, narrow
|
|
824
|
+
$ cakemail segments update 123 456 -c '{
|
|
825
|
+
"match": "all",
|
|
826
|
+
"rules": [
|
|
827
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"},
|
|
828
|
+
{"field": "last_click_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
829
|
+
]
|
|
830
|
+
}'
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### 3. Document Complex Segments
|
|
834
|
+
|
|
835
|
+
```bash
|
|
836
|
+
# Create README for segment definitions
|
|
837
|
+
cat > segments-documentation.md << 'EOF'
|
|
838
|
+
# Segment Definitions
|
|
839
|
+
|
|
840
|
+
## High Value Customers (ID: 456)
|
|
841
|
+
- **Rules:** LTV > $1000 AND Status = Subscribed
|
|
842
|
+
- **Purpose:** Exclusive offers and VIP treatment
|
|
843
|
+
- **Expected Size:** ~5% of list
|
|
844
|
+
- **Owner:** Marketing Team
|
|
845
|
+
- **Created:** 2024-03-15
|
|
846
|
+
|
|
847
|
+
## At Risk (ID: 457)
|
|
848
|
+
- **Rules:** No open 30-90 days AND Status = Subscribed
|
|
849
|
+
- **Purpose:** Re-engagement campaigns
|
|
850
|
+
- **Expected Size:** ~15-20% of list
|
|
851
|
+
- **Owner:** Retention Team
|
|
852
|
+
- **Created:** 2024-03-15
|
|
853
|
+
EOF
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### 4. Monitor Segment Health
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
#!/bin/bash
|
|
860
|
+
# monitor-segments.sh
|
|
861
|
+
|
|
862
|
+
LIST_ID=123
|
|
863
|
+
|
|
864
|
+
echo "=== Segment Health Check ==="
|
|
865
|
+
echo ""
|
|
866
|
+
|
|
867
|
+
SEGMENTS=$(cakemail segments list $LIST_ID -f json | jq -r '.data[].id')
|
|
868
|
+
|
|
869
|
+
for SEG_ID in $SEGMENTS; do
|
|
870
|
+
SEG=$(cakemail segments get $LIST_ID $SEG_ID -f json)
|
|
871
|
+
NAME=$(echo "$SEG" | jq -r '.name')
|
|
872
|
+
COUNT=$(echo "$SEG" | jq -r '.contact_count')
|
|
873
|
+
|
|
874
|
+
echo "$NAME (ID: $SEG_ID)"
|
|
875
|
+
echo " Contacts: $COUNT"
|
|
876
|
+
|
|
877
|
+
if [ $COUNT -eq 0 ]; then
|
|
878
|
+
echo " ⚠️ Empty segment - review conditions"
|
|
879
|
+
elif [ $COUNT -lt 10 ]; then
|
|
880
|
+
echo " ⚠️ Very small segment - may need adjustment"
|
|
881
|
+
else
|
|
882
|
+
echo " ✅ Healthy"
|
|
883
|
+
fi
|
|
884
|
+
|
|
885
|
+
echo ""
|
|
886
|
+
done
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### 5. Test Before Large Sends
|
|
890
|
+
|
|
891
|
+
```bash
|
|
892
|
+
# Create test segment (small sample)
|
|
893
|
+
$ cakemail segments create 123 -n "Test - Premium Users" -c '{
|
|
894
|
+
"match": "all",
|
|
895
|
+
"rules": [
|
|
896
|
+
{"field": "custom_attributes.plan", "operator": "equals", "value": "premium"},
|
|
897
|
+
{"field": "email", "operator": "contains", "value": "@yourcompany.com"}
|
|
898
|
+
]
|
|
899
|
+
}'
|
|
900
|
+
|
|
901
|
+
# Send to test segment first
|
|
902
|
+
$ cakemail campaigns create -n "Test Campaign" -l 123 -s 101 --segment 456
|
|
903
|
+
|
|
904
|
+
# Review results
|
|
905
|
+
# Then send to full segment
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
## Troubleshooting
|
|
909
|
+
|
|
910
|
+
### Segment Has Zero Contacts
|
|
911
|
+
|
|
912
|
+
**Problem:** Created segment but contact_count is 0
|
|
913
|
+
|
|
914
|
+
**Solutions:**
|
|
915
|
+
```bash
|
|
916
|
+
# 1. Check segment conditions
|
|
917
|
+
$ cakemail segments get 123 456 -f json | jq '.conditions'
|
|
918
|
+
|
|
919
|
+
# 2. Test individual rules
|
|
920
|
+
# Try with just one rule to see if any contacts match
|
|
921
|
+
$ cakemail segments update 123 456 -c '{
|
|
922
|
+
"match": "all",
|
|
923
|
+
"rules": [
|
|
924
|
+
{"field": "status", "operator": "equals", "value": "subscribed"}
|
|
925
|
+
]
|
|
926
|
+
}'
|
|
927
|
+
|
|
928
|
+
# 3. Check if contacts have required attributes
|
|
929
|
+
$ cakemail contacts get 123 501 -f json | jq '.custom_attributes'
|
|
930
|
+
|
|
931
|
+
# 4. Verify date format
|
|
932
|
+
# Use YYYY-MM-DD format
|
|
933
|
+
$ cakemail segments update 123 456 -c '{
|
|
934
|
+
"match": "all",
|
|
935
|
+
"rules": [
|
|
936
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
937
|
+
]
|
|
938
|
+
}'
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
### Segment Larger Than Expected
|
|
942
|
+
|
|
943
|
+
**Problem:** Segment includes more contacts than anticipated
|
|
944
|
+
|
|
945
|
+
**Solutions:**
|
|
946
|
+
```bash
|
|
947
|
+
# Preview contacts
|
|
948
|
+
$ cakemail segments contacts 123 456 -l 50
|
|
949
|
+
|
|
950
|
+
# Export for analysis
|
|
951
|
+
$ cakemail segments contacts 123 456 -f json > review.json
|
|
952
|
+
|
|
953
|
+
# Check specific contacts
|
|
954
|
+
$ jq '.data[] | {email, status, last_open_date, custom_attributes}' review.json
|
|
955
|
+
|
|
956
|
+
# Adjust conditions to be more restrictive
|
|
957
|
+
$ cakemail segments update 123 456 -c '{
|
|
958
|
+
"match": "all",
|
|
959
|
+
"rules": [
|
|
960
|
+
{"field": "status", "operator": "equals", "value": "subscribed"},
|
|
961
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"},
|
|
962
|
+
{"field": "last_click_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
963
|
+
]
|
|
964
|
+
}'
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### Custom Attribute Rule Not Working
|
|
968
|
+
|
|
969
|
+
**Problem:** Rule on custom attribute not matching contacts
|
|
970
|
+
|
|
971
|
+
**Solutions:**
|
|
972
|
+
```bash
|
|
973
|
+
# 1. Verify attribute exists
|
|
974
|
+
$ cakemail attributes list 123
|
|
975
|
+
|
|
976
|
+
# 2. Check exact attribute name
|
|
977
|
+
$ cakemail contacts get 123 501 -f json | jq '.custom_attributes | keys'
|
|
978
|
+
|
|
979
|
+
# 3. Use correct dot notation
|
|
980
|
+
# Correct:
|
|
981
|
+
{"field": "custom_attributes.plan_type", "operator": "equals", "value": "premium"}
|
|
982
|
+
|
|
983
|
+
# Wrong:
|
|
984
|
+
{"field": "plan_type", "operator": "equals", "value": "premium"}
|
|
985
|
+
|
|
986
|
+
# 4. Check value type (string vs number vs boolean)
|
|
987
|
+
# For numbers (no quotes):
|
|
988
|
+
{"field": "custom_attributes.age", "operator": "greater_than", "value": "25"}
|
|
989
|
+
|
|
990
|
+
# For booleans:
|
|
991
|
+
{"field": "custom_attributes.is_vip", "operator": "equals", "value": "true"}
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
### Date Comparison Not Working
|
|
995
|
+
|
|
996
|
+
**Problem:** Date-based rules not matching correctly
|
|
997
|
+
|
|
998
|
+
**Solutions:**
|
|
999
|
+
```bash
|
|
1000
|
+
# Use ISO format (YYYY-MM-DD)
|
|
1001
|
+
{"field": "last_open_date", "operator": "greater_than", "value": "2024-03-01"}
|
|
1002
|
+
|
|
1003
|
+
# Not: "03/01/2024" or "March 1, 2024"
|
|
1004
|
+
|
|
1005
|
+
# For rolling dates, use script:
|
|
1006
|
+
THIRTY_DAYS_AGO=$(date -d "30 days ago" +%Y-%m-%d)
|
|
1007
|
+
|
|
1008
|
+
$ cakemail segments update 123 456 -c "{
|
|
1009
|
+
\"match\": \"all\",
|
|
1010
|
+
\"rules\": [
|
|
1011
|
+
{\"field\": \"last_open_date\", \"operator\": \"greater_than\", \"value\": \"$THIRTY_DAYS_AGO\"}
|
|
1012
|
+
]
|
|
1013
|
+
}"
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
## Best Practices Summary
|
|
1017
|
+
|
|
1018
|
+
1. **Name descriptively** - Clear, specific segment names
|
|
1019
|
+
2. **Start broad** - Begin with simple rules, narrow as needed
|
|
1020
|
+
3. **Preview first** - Check contacts before sending campaigns
|
|
1021
|
+
4. **Document segments** - Keep record of purpose and conditions
|
|
1022
|
+
5. **Monitor size** - Track segment growth/shrinkage
|
|
1023
|
+
6. **Test conditions** - Verify rules match expected contacts
|
|
1024
|
+
7. **Use date-based rules** - Leverage dynamic date filtering
|
|
1025
|
+
8. **Combine wisely** - Use "any" vs "all" appropriately
|
|
1026
|
+
9. **Regular audits** - Review and clean unused segments
|
|
1027
|
+
10. **Update dynamically** - Leverage automated condition updates
|
|
1028
|
+
|