@cakemail-org/cakemail-cli 1.5.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 +319 -15
- package/audit-formats.js +128 -0
- package/cakemail.rb +20 -0
- package/dist/cli.js +27 -10
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -6
- 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.d.ts.map +1 -1
- package/dist/commands/campaigns.js +103 -8
- package/dist/commands/campaigns.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +63 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contacts.d.ts.map +1 -1
- package/dist/commands/contacts.js +91 -12
- 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.d.ts +5 -0
- package/dist/commands/interests.d.ts.map +1 -0
- package/dist/commands/interests.js +172 -0
- package/dist/commands/interests.js.map +1 -0
- package/dist/commands/lists.d.ts.map +1 -1
- package/dist/commands/lists.js +6 -8
- package/dist/commands/lists.js.map +1 -1
- package/dist/commands/logs.d.ts +5 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +237 -0
- package/dist/commands/logs.js.map +1 -0
- 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.d.ts.map +1 -1
- package/dist/commands/senders.js +11 -8
- 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.d.ts +5 -0
- package/dist/commands/tags.d.ts.map +1 -0
- package/dist/commands/tags.js +124 -0
- package/dist/commands/tags.js.map +1 -0
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/commands/transactional-templates.d.ts +5 -0
- package/dist/commands/transactional-templates.d.ts.map +1 -0
- package/dist/commands/transactional-templates.js +354 -0
- package/dist/commands/transactional-templates.js.map +1 -0
- package/dist/commands/webhooks.js +1 -1
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/utils/auth.d.ts +8 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +39 -11
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/config-file.d.ts +7 -0
- package/dist/utils/config-file.d.ts.map +1 -1
- package/dist/utils/config-file.js +15 -0
- package/dist/utils/config-file.js.map +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -4
- 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/list-defaults.d.ts +33 -0
- package/dist/utils/list-defaults.d.ts.map +1 -0
- package/dist/utils/list-defaults.js +52 -0
- package/dist/utils/list-defaults.js.map +1 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +36 -13
- package/dist/utils/output.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 -47
- 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,867 @@
|
|
|
1
|
+
# Campaign Analytics
|
|
2
|
+
|
|
3
|
+
Analyze campaign performance with comprehensive metrics and insights to optimize your email marketing.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Campaign analytics allow you to:
|
|
8
|
+
- Track email delivery and engagement
|
|
9
|
+
- Measure open and click-through rates
|
|
10
|
+
- Identify top-performing content and links
|
|
11
|
+
- Compare campaign performance over time
|
|
12
|
+
- Export data for deeper analysis
|
|
13
|
+
- Make data-driven decisions
|
|
14
|
+
|
|
15
|
+
Understanding your campaign metrics is essential for improving email marketing effectiveness.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### View Campaign Report
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ cakemail reports campaign 790
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Output:**
|
|
26
|
+
```
|
|
27
|
+
Campaign: March Newsletter (ID: 790)
|
|
28
|
+
Status: sent
|
|
29
|
+
Sent: 2024-03-15 10:00:00
|
|
30
|
+
|
|
31
|
+
=== Delivery Metrics ===
|
|
32
|
+
Total Recipients: 1,247
|
|
33
|
+
Delivered: 1,189 (95.3%)
|
|
34
|
+
Bounced: 58 (4.7%)
|
|
35
|
+
Hard Bounces: 45
|
|
36
|
+
Soft Bounces: 13
|
|
37
|
+
|
|
38
|
+
=== Engagement Metrics ===
|
|
39
|
+
Opens: 723 (60.8%)
|
|
40
|
+
Unique Opens: 567 (47.7%)
|
|
41
|
+
Clicks: 234 (19.7%)
|
|
42
|
+
Unique Clicks: 189 (15.9%)
|
|
43
|
+
Unsubscribes: 12 (1.0%)
|
|
44
|
+
|
|
45
|
+
=== Performance ===
|
|
46
|
+
Open Rate: 47.7%
|
|
47
|
+
Click Rate: 15.9%
|
|
48
|
+
Click-to-Open: 33.3%
|
|
49
|
+
Bounce Rate: 4.7%
|
|
50
|
+
Unsubscribe Rate: 1.0%
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Core Metrics
|
|
54
|
+
|
|
55
|
+
### Delivery Metrics
|
|
56
|
+
|
|
57
|
+
**Total Recipients:**
|
|
58
|
+
- Number of contacts campaign was sent to
|
|
59
|
+
- Includes all contacts in target list/segment
|
|
60
|
+
|
|
61
|
+
**Delivered:**
|
|
62
|
+
- Successfully delivered emails
|
|
63
|
+
- Excludes hard and soft bounces
|
|
64
|
+
- Formula: Total Recipients - Bounces
|
|
65
|
+
|
|
66
|
+
**Bounced:**
|
|
67
|
+
- Emails that couldn't be delivered
|
|
68
|
+
- **Hard Bounce**: Permanent failure (invalid email)
|
|
69
|
+
- **Soft Bounce**: Temporary failure (inbox full, server down)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# View detailed bounce information
|
|
73
|
+
$ cakemail reports campaign 790 -f json | jq '{
|
|
74
|
+
total: .total_recipients,
|
|
75
|
+
delivered: .delivered,
|
|
76
|
+
bounced: .bounced,
|
|
77
|
+
hard_bounces: .hard_bounces,
|
|
78
|
+
soft_bounces: .soft_bounces,
|
|
79
|
+
delivery_rate: (.delivered / .total_recipients * 100 | round)
|
|
80
|
+
}'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Output:**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"total": 1247,
|
|
87
|
+
"delivered": 1189,
|
|
88
|
+
"bounced": 58,
|
|
89
|
+
"hard_bounces": 45,
|
|
90
|
+
"soft_bounces": 13,
|
|
91
|
+
"delivery_rate": 95
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Engagement Metrics
|
|
96
|
+
|
|
97
|
+
**Opens:**
|
|
98
|
+
- Total times campaign was opened
|
|
99
|
+
- Includes multiple opens by same contact
|
|
100
|
+
- Tracked via invisible pixel
|
|
101
|
+
|
|
102
|
+
**Unique Opens:**
|
|
103
|
+
- Number of unique contacts who opened
|
|
104
|
+
- Each contact counted only once
|
|
105
|
+
- More accurate engagement indicator
|
|
106
|
+
|
|
107
|
+
**Open Rate:**
|
|
108
|
+
- Percentage of delivered emails that were opened
|
|
109
|
+
- Formula: (Unique Opens / Delivered) × 100
|
|
110
|
+
- Industry average: 15-25%
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Calculate open rate
|
|
114
|
+
$ cakemail reports campaign 790 -f json | jq '{
|
|
115
|
+
delivered: .delivered,
|
|
116
|
+
unique_opens: .unique_opens,
|
|
117
|
+
open_rate: (.unique_opens / .delivered * 100 | round)
|
|
118
|
+
}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Clicks:**
|
|
122
|
+
- Total link clicks in campaign
|
|
123
|
+
- Includes multiple clicks by same contact
|
|
124
|
+
|
|
125
|
+
**Unique Clicks:**
|
|
126
|
+
- Number of unique contacts who clicked
|
|
127
|
+
- Each contact counted only once
|
|
128
|
+
|
|
129
|
+
**Click Rate (CTR):**
|
|
130
|
+
- Percentage of delivered emails with clicks
|
|
131
|
+
- Formula: (Unique Clicks / Delivered) × 100
|
|
132
|
+
- Industry average: 2-5%
|
|
133
|
+
|
|
134
|
+
**Click-to-Open Rate (CTOR):**
|
|
135
|
+
- Percentage of openers who clicked
|
|
136
|
+
- Formula: (Unique Clicks / Unique Opens) × 100
|
|
137
|
+
- Measures content effectiveness
|
|
138
|
+
- Industry average: 10-20%
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# View click metrics
|
|
142
|
+
$ cakemail reports campaign 790 -f json | jq '{
|
|
143
|
+
unique_opens: .unique_opens,
|
|
144
|
+
unique_clicks: .unique_clicks,
|
|
145
|
+
click_rate: (.unique_clicks / .delivered * 100 | round),
|
|
146
|
+
ctor: (.unique_clicks / .unique_opens * 100 | round)
|
|
147
|
+
}'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Action Metrics
|
|
151
|
+
|
|
152
|
+
**Unsubscribes:**
|
|
153
|
+
- Contacts who unsubscribed after this campaign
|
|
154
|
+
- Important metric for content relevance
|
|
155
|
+
|
|
156
|
+
**Unsubscribe Rate:**
|
|
157
|
+
- Percentage of delivered emails that unsubscribed
|
|
158
|
+
- Formula: (Unsubscribes / Delivered) × 100
|
|
159
|
+
- Healthy rate: < 0.5%
|
|
160
|
+
- Concerning rate: > 1%
|
|
161
|
+
|
|
162
|
+
**Spam Complaints:**
|
|
163
|
+
- Contacts who marked email as spam
|
|
164
|
+
- Critical metric for sender reputation
|
|
165
|
+
- Target: < 0.1%
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# View action metrics
|
|
169
|
+
$ cakemail reports campaign 790 -f json | jq '{
|
|
170
|
+
unsubscribes: .unsubscribes,
|
|
171
|
+
unsubscribe_rate: (.unsubscribes / .delivered * 100 | round),
|
|
172
|
+
spam_complaints: .spam_complaints,
|
|
173
|
+
spam_rate: (.spam_complaints / .delivered * 100 | round)
|
|
174
|
+
}'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Viewing Campaign Analytics
|
|
178
|
+
|
|
179
|
+
### Basic Campaign Report
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
$ cakemail reports campaign 790
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### JSON Format for Processing
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
$ cakemail reports campaign 790 -f json > campaign-790.json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Extract Specific Metrics
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Open rate only
|
|
195
|
+
$ cakemail reports campaign 790 -f json | jq '.open_rate'
|
|
196
|
+
|
|
197
|
+
# Top metrics summary
|
|
198
|
+
$ cakemail reports campaign 790 -f json | jq '{
|
|
199
|
+
name: .campaign_name,
|
|
200
|
+
delivered: .delivered,
|
|
201
|
+
open_rate: .open_rate,
|
|
202
|
+
click_rate: .click_rate,
|
|
203
|
+
unsubscribe_rate: .unsubscribe_rate
|
|
204
|
+
}'
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Multiple Campaign Reports
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
#!/bin/bash
|
|
211
|
+
# report-multiple-campaigns.sh
|
|
212
|
+
|
|
213
|
+
CAMPAIGNS=(790 791 792)
|
|
214
|
+
|
|
215
|
+
echo "Campaign | Delivered | Open Rate | Click Rate"
|
|
216
|
+
echo "---------|-----------|-----------|------------"
|
|
217
|
+
|
|
218
|
+
for ID in "${CAMPAIGNS[@]}"; do
|
|
219
|
+
REPORT=$(cakemail reports campaign $ID -f json)
|
|
220
|
+
|
|
221
|
+
NAME=$(echo "$REPORT" | jq -r '.campaign_name' | cut -c1-15)
|
|
222
|
+
DELIVERED=$(echo "$REPORT" | jq -r '.delivered')
|
|
223
|
+
OPEN=$(echo "$REPORT" | jq -r '.open_rate')
|
|
224
|
+
CLICK=$(echo "$REPORT" | jq -r '.click_rate')
|
|
225
|
+
|
|
226
|
+
printf "%-15s | %9d | %8.1f%% | %9.1f%%\n" "$NAME" $DELIVERED $OPEN $CLICK
|
|
227
|
+
done
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Link Analytics
|
|
231
|
+
|
|
232
|
+
### View Campaign Links
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
$ cakemail reports campaign-links 790
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Output:**
|
|
239
|
+
```
|
|
240
|
+
┌────────────────────────────────────────┬────────┬─────────┬─────────┐
|
|
241
|
+
│ URL │ Clicks │ Unique │ CTR │
|
|
242
|
+
├────────────────────────────────────────┼────────┼─────────┼─────────┤
|
|
243
|
+
│ https://example.com/product │ 450 │ 320 │ 26.9% │
|
|
244
|
+
│ https://example.com/blog/article │ 230 │ 180 │ 15.1% │
|
|
245
|
+
│ https://example.com/special-offer │ 180 │ 150 │ 12.6% │
|
|
246
|
+
└────────────────────────────────────────┴────────┴─────────┴─────────┘
|
|
247
|
+
|
|
248
|
+
Total clicks: 860 (650 unique)
|
|
249
|
+
Overall CTR: 54.7%
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Top Performing Links
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
#!/bin/bash
|
|
256
|
+
# top-links.sh
|
|
257
|
+
|
|
258
|
+
CAMPAIGN_ID=$1
|
|
259
|
+
|
|
260
|
+
cakemail reports campaign-links $CAMPAIGN_ID -f json | \
|
|
261
|
+
jq -r '.links | sort_by(-.unique_clicks) | .[0:5][] |
|
|
262
|
+
"\(.unique_clicks)\t\(.url)"' | \
|
|
263
|
+
column -t -s $'\t'
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Export Link Data
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
$ cakemail reports campaign-links 790 -f json > links-790.json
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Time-Based Analytics
|
|
273
|
+
|
|
274
|
+
### Opens Over Time
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
#!/bin/bash
|
|
278
|
+
# opens-over-time.sh
|
|
279
|
+
|
|
280
|
+
CAMPAIGN_ID=$1
|
|
281
|
+
|
|
282
|
+
echo "=== Opens Timeline ==="
|
|
283
|
+
echo ""
|
|
284
|
+
|
|
285
|
+
# Get campaign details
|
|
286
|
+
CAMPAIGN=$(cakemail campaigns get $CAMPAIGN_ID -f json)
|
|
287
|
+
SENT_DATE=$(echo "$CAMPAIGN" | jq -r '.delivered_at')
|
|
288
|
+
|
|
289
|
+
echo "Campaign sent: $SENT_DATE"
|
|
290
|
+
echo ""
|
|
291
|
+
|
|
292
|
+
# Get current stats
|
|
293
|
+
STATS=$(cakemail reports campaign $CAMPAIGN_ID -f json)
|
|
294
|
+
|
|
295
|
+
echo "Current stats:"
|
|
296
|
+
echo " Unique Opens: $(echo "$STATS" | jq -r '.unique_opens')"
|
|
297
|
+
echo " Open Rate: $(echo "$STATS" | jq -r '.open_rate')%"
|
|
298
|
+
echo ""
|
|
299
|
+
|
|
300
|
+
echo "Typical open timeline:"
|
|
301
|
+
echo " First 24 hours: 50-70% of total opens"
|
|
302
|
+
echo " First 48 hours: 70-85% of total opens"
|
|
303
|
+
echo " First 7 days: 90-95% of total opens"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Daily Performance Tracking
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
#!/bin/bash
|
|
310
|
+
# track-daily-performance.sh
|
|
311
|
+
|
|
312
|
+
CAMPAIGN_ID=$1
|
|
313
|
+
LOG_FILE="campaign-${CAMPAIGN_ID}-tracking.log"
|
|
314
|
+
|
|
315
|
+
# Log current metrics
|
|
316
|
+
DATE=$(date +%Y-%m-%d-%H:%M:%S)
|
|
317
|
+
STATS=$(cakemail reports campaign $CAMPAIGN_ID -f json)
|
|
318
|
+
|
|
319
|
+
OPENS=$(echo "$STATS" | jq -r '.unique_opens')
|
|
320
|
+
CLICKS=$(echo "$STATS" | jq -r '.unique_clicks')
|
|
321
|
+
OPEN_RATE=$(echo "$STATS" | jq -r '.open_rate')
|
|
322
|
+
CLICK_RATE=$(echo "$STATS" | jq -r '.click_rate')
|
|
323
|
+
|
|
324
|
+
echo "$DATE,$OPENS,$CLICKS,$OPEN_RATE,$CLICK_RATE" >> $LOG_FILE
|
|
325
|
+
|
|
326
|
+
echo "Logged: $OPENS opens, $CLICKS clicks"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Schedule with cron:**
|
|
330
|
+
```bash
|
|
331
|
+
# Track every 6 hours for first week
|
|
332
|
+
0 */6 * * * /path/to/track-daily-performance.sh 790
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Comparative Analysis
|
|
336
|
+
|
|
337
|
+
### Compare Two Campaigns
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
#!/bin/bash
|
|
341
|
+
# compare-campaigns.sh
|
|
342
|
+
|
|
343
|
+
CAMPAIGN_A=$1
|
|
344
|
+
CAMPAIGN_B=$2
|
|
345
|
+
|
|
346
|
+
echo "=== Campaign Comparison ==="
|
|
347
|
+
echo ""
|
|
348
|
+
|
|
349
|
+
# Get reports
|
|
350
|
+
REPORT_A=$(cakemail reports campaign $CAMPAIGN_A -f json)
|
|
351
|
+
REPORT_B=$(cakemail reports campaign $CAMPAIGN_B -f json)
|
|
352
|
+
|
|
353
|
+
# Campaign names
|
|
354
|
+
NAME_A=$(echo "$REPORT_A" | jq -r '.campaign_name')
|
|
355
|
+
NAME_B=$(echo "$REPORT_B" | jq -r '.campaign_name')
|
|
356
|
+
|
|
357
|
+
echo "Campaign A: $NAME_A (ID: $CAMPAIGN_A)"
|
|
358
|
+
echo "Campaign B: $NAME_B (ID: $CAMPAIGN_B)"
|
|
359
|
+
echo ""
|
|
360
|
+
|
|
361
|
+
# Compare metrics
|
|
362
|
+
echo "Metric | Campaign A | Campaign B | Difference"
|
|
363
|
+
echo "--------------------|------------|------------|------------"
|
|
364
|
+
|
|
365
|
+
# Open Rate
|
|
366
|
+
OPEN_A=$(echo "$REPORT_A" | jq -r '.open_rate')
|
|
367
|
+
OPEN_B=$(echo "$REPORT_B" | jq -r '.open_rate')
|
|
368
|
+
OPEN_DIFF=$(echo "$OPEN_B - $OPEN_A" | bc)
|
|
369
|
+
printf "Open Rate | %9.1f%% | %9.1f%% | %+9.1f%%\n" $OPEN_A $OPEN_B $OPEN_DIFF
|
|
370
|
+
|
|
371
|
+
# Click Rate
|
|
372
|
+
CLICK_A=$(echo "$REPORT_A" | jq -r '.click_rate')
|
|
373
|
+
CLICK_B=$(echo "$REPORT_B" | jq -r '.click_rate')
|
|
374
|
+
CLICK_DIFF=$(echo "$CLICK_B - $CLICK_A" | bc)
|
|
375
|
+
printf "Click Rate | %9.1f%% | %9.1f%% | %+9.1f%%\n" $CLICK_A $CLICK_B $CLICK_DIFF
|
|
376
|
+
|
|
377
|
+
# Unsubscribe Rate
|
|
378
|
+
UNSUB_A=$(echo "$REPORT_A" | jq -r '.unsubscribe_rate')
|
|
379
|
+
UNSUB_B=$(echo "$REPORT_B" | jq -r '.unsubscribe_rate')
|
|
380
|
+
UNSUB_DIFF=$(echo "$UNSUB_B - $UNSUB_A" | bc)
|
|
381
|
+
printf "Unsubscribe Rate | %9.1f%% | %9.1f%% | %+9.1f%%\n" $UNSUB_A $UNSUB_B $UNSUB_DIFF
|
|
382
|
+
|
|
383
|
+
echo ""
|
|
384
|
+
|
|
385
|
+
# Winner
|
|
386
|
+
if (( $(echo "$OPEN_B > $OPEN_A" | bc -l) )); then
|
|
387
|
+
echo "🏆 Campaign B performed better"
|
|
388
|
+
else
|
|
389
|
+
echo "🏆 Campaign A performed better"
|
|
390
|
+
fi
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Historical Performance
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
#!/bin/bash
|
|
397
|
+
# historical-performance.sh
|
|
398
|
+
|
|
399
|
+
LIST_ID=123
|
|
400
|
+
|
|
401
|
+
echo "=== Historical Campaign Performance ==="
|
|
402
|
+
echo ""
|
|
403
|
+
|
|
404
|
+
# Get all sent campaigns
|
|
405
|
+
CAMPAIGNS=$(cakemail campaigns list \
|
|
406
|
+
--filter "status==sent" \
|
|
407
|
+
--sort "-delivered_at" \
|
|
408
|
+
--limit 10 \
|
|
409
|
+
-f json | jq -r '.data[].id')
|
|
410
|
+
|
|
411
|
+
echo "Campaign | Date | Recipients | Open Rate | Click Rate"
|
|
412
|
+
echo "---------|------------|------------|-----------|------------"
|
|
413
|
+
|
|
414
|
+
for ID in $CAMPAIGNS; do
|
|
415
|
+
CAMPAIGN=$(cakemail campaigns get $ID -f json)
|
|
416
|
+
REPORT=$(cakemail reports campaign $ID -f json)
|
|
417
|
+
|
|
418
|
+
NAME=$(echo "$CAMPAIGN" | jq -r '.name' | cut -c1-15)
|
|
419
|
+
DATE=$(echo "$CAMPAIGN" | jq -r '.delivered_at' | cut -d'T' -f1)
|
|
420
|
+
RECIPIENTS=$(echo "$REPORT" | jq -r '.total_recipients')
|
|
421
|
+
OPEN=$(echo "$REPORT" | jq -r '.open_rate')
|
|
422
|
+
CLICK=$(echo "$REPORT" | jq -r '.click_rate')
|
|
423
|
+
|
|
424
|
+
printf "%-15s | %10s | %10d | %8.1f%% | %9.1f%%\n" \
|
|
425
|
+
"$NAME" "$DATE" $RECIPIENTS $OPEN $CLICK
|
|
426
|
+
done
|
|
427
|
+
|
|
428
|
+
echo ""
|
|
429
|
+
|
|
430
|
+
# Calculate averages
|
|
431
|
+
echo "Calculating averages across all campaigns..."
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Month-over-Month Trends
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
#!/bin/bash
|
|
438
|
+
# monthly-trends.sh
|
|
439
|
+
|
|
440
|
+
YEAR=2024
|
|
441
|
+
|
|
442
|
+
echo "=== Monthly Campaign Trends - $YEAR ==="
|
|
443
|
+
echo ""
|
|
444
|
+
echo "Month | Campaigns | Avg Open Rate | Avg Click Rate"
|
|
445
|
+
echo "------|-----------|---------------|----------------"
|
|
446
|
+
|
|
447
|
+
for MONTH in {01..12}; do
|
|
448
|
+
START_DATE="$YEAR-$MONTH-01"
|
|
449
|
+
|
|
450
|
+
# Calculate end date
|
|
451
|
+
if [ $MONTH -eq 12 ]; then
|
|
452
|
+
END_DATE="$((YEAR + 1))-01-01"
|
|
453
|
+
else
|
|
454
|
+
END_DATE="$YEAR-$(printf "%02d" $((10#$MONTH + 1)))-01"
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
# Get campaigns for month
|
|
458
|
+
CAMPAIGNS=$(cakemail campaigns list \
|
|
459
|
+
--filter "status==sent;delivered_at>=$START_DATE;delivered_at<$END_DATE" \
|
|
460
|
+
-f json | jq -r '.data[].id')
|
|
461
|
+
|
|
462
|
+
if [ -z "$CAMPAIGNS" ]; then
|
|
463
|
+
continue
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
COUNT=$(echo "$CAMPAIGNS" | wc -l)
|
|
467
|
+
TOTAL_OPEN=0
|
|
468
|
+
TOTAL_CLICK=0
|
|
469
|
+
|
|
470
|
+
for ID in $CAMPAIGNS; do
|
|
471
|
+
REPORT=$(cakemail reports campaign $ID -f json 2>/dev/null)
|
|
472
|
+
if [ -n "$REPORT" ]; then
|
|
473
|
+
OPEN=$(echo "$REPORT" | jq -r '.open_rate')
|
|
474
|
+
CLICK=$(echo "$REPORT" | jq -r '.click_rate')
|
|
475
|
+
TOTAL_OPEN=$(echo "$TOTAL_OPEN + $OPEN" | bc)
|
|
476
|
+
TOTAL_CLICK=$(echo "$TOTAL_CLICK + $CLICK" | bc)
|
|
477
|
+
fi
|
|
478
|
+
done
|
|
479
|
+
|
|
480
|
+
if [ $COUNT -gt 0 ]; then
|
|
481
|
+
AVG_OPEN=$(echo "scale=1; $TOTAL_OPEN / $COUNT" | bc)
|
|
482
|
+
AVG_CLICK=$(echo "scale=1; $TOTAL_CLICK / $COUNT" | bc)
|
|
483
|
+
printf "%5s | %9d | %12.1f%% | %13.1f%%\n" \
|
|
484
|
+
"$MONTH" $COUNT $AVG_OPEN $AVG_CLICK
|
|
485
|
+
fi
|
|
486
|
+
done
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Segmentation Analysis
|
|
490
|
+
|
|
491
|
+
### Performance by Segment
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
#!/bin/bash
|
|
495
|
+
# segment-performance.sh
|
|
496
|
+
|
|
497
|
+
LIST_ID=123
|
|
498
|
+
|
|
499
|
+
echo "=== Campaign Performance by Segment ==="
|
|
500
|
+
echo ""
|
|
501
|
+
|
|
502
|
+
# Get all segments
|
|
503
|
+
SEGMENTS=$(cakemail segments list $LIST_ID -f json | jq -r '.data[] | "\(.id):\(.name)"')
|
|
504
|
+
|
|
505
|
+
echo "Segment | Campaigns | Avg Open | Avg Click"
|
|
506
|
+
echo "-----------------|-----------|----------|----------"
|
|
507
|
+
|
|
508
|
+
for SEG in $SEGMENTS; do
|
|
509
|
+
SEG_ID=$(echo "$SEG" | cut -d: -f1)
|
|
510
|
+
SEG_NAME=$(echo "$SEG" | cut -d: -f2- | cut -c1-15)
|
|
511
|
+
|
|
512
|
+
# Find campaigns sent to this segment
|
|
513
|
+
CAMPAIGNS=$(cakemail campaigns list \
|
|
514
|
+
--filter "status==sent;segment_id==$SEG_ID" \
|
|
515
|
+
-f json | jq -r '.data[].id')
|
|
516
|
+
|
|
517
|
+
if [ -z "$CAMPAIGNS" ]; then
|
|
518
|
+
continue
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
COUNT=$(echo "$CAMPAIGNS" | wc -l)
|
|
522
|
+
TOTAL_OPEN=0
|
|
523
|
+
TOTAL_CLICK=0
|
|
524
|
+
|
|
525
|
+
for ID in $CAMPAIGNS; do
|
|
526
|
+
REPORT=$(cakemail reports campaign $ID -f json 2>/dev/null)
|
|
527
|
+
if [ -n "$REPORT" ]; then
|
|
528
|
+
OPEN=$(echo "$REPORT" | jq -r '.open_rate')
|
|
529
|
+
CLICK=$(echo "$REPORT" | jq -r '.click_rate')
|
|
530
|
+
TOTAL_OPEN=$(echo "$TOTAL_OPEN + $OPEN" | bc)
|
|
531
|
+
TOTAL_CLICK=$(echo "$TOTAL_CLICK + $CLICK" | bc)
|
|
532
|
+
fi
|
|
533
|
+
done
|
|
534
|
+
|
|
535
|
+
AVG_OPEN=$(echo "scale=1; $TOTAL_OPEN / $COUNT" | bc)
|
|
536
|
+
AVG_CLICK=$(echo "scale=1; $TOTAL_CLICK / $COUNT" | bc)
|
|
537
|
+
|
|
538
|
+
printf "%-15s | %9d | %7.1f%% | %8.1f%%\n" \
|
|
539
|
+
"$SEG_NAME" $COUNT $AVG_OPEN $AVG_CLICK
|
|
540
|
+
done
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Best Performing Segment
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
#!/bin/bash
|
|
547
|
+
# best-segment.sh
|
|
548
|
+
|
|
549
|
+
# Find segment with highest average open rate
|
|
550
|
+
# (Implementation from segment-performance.sh with sorting)
|
|
551
|
+
|
|
552
|
+
# Output best segment recommendation
|
|
553
|
+
echo "🏆 Best Performing Segment: Premium Users"
|
|
554
|
+
echo " Average Open Rate: 62.3%"
|
|
555
|
+
echo " Average Click Rate: 28.7%"
|
|
556
|
+
echo ""
|
|
557
|
+
echo "💡 Recommendation: Prioritize campaigns to this segment"
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Key Performance Indicators (KPIs)
|
|
561
|
+
|
|
562
|
+
### Email Deliverability KPIs
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
#!/bin/bash
|
|
566
|
+
# deliverability-kpis.sh
|
|
567
|
+
|
|
568
|
+
CAMPAIGN_ID=$1
|
|
569
|
+
|
|
570
|
+
REPORT=$(cakemail reports campaign $CAMPAIGN_ID -f json)
|
|
571
|
+
|
|
572
|
+
echo "=== Deliverability KPIs ==="
|
|
573
|
+
echo ""
|
|
574
|
+
|
|
575
|
+
# Delivery Rate
|
|
576
|
+
TOTAL=$(echo "$REPORT" | jq -r '.total_recipients')
|
|
577
|
+
DELIVERED=$(echo "$REPORT" | jq -r '.delivered')
|
|
578
|
+
DELIVERY_RATE=$(echo "scale=1; $DELIVERED * 100 / $TOTAL" | bc)
|
|
579
|
+
|
|
580
|
+
echo "Delivery Rate: $DELIVERY_RATE%"
|
|
581
|
+
if (( $(echo "$DELIVERY_RATE >= 95" | bc -l) )); then
|
|
582
|
+
echo " ✅ Excellent (target: ≥95%)"
|
|
583
|
+
elif (( $(echo "$DELIVERY_RATE >= 90" | bc -l) )); then
|
|
584
|
+
echo " ⚠️ Good (target: ≥95%)"
|
|
585
|
+
else
|
|
586
|
+
echo " ❌ Poor - Clean your list"
|
|
587
|
+
fi
|
|
588
|
+
echo ""
|
|
589
|
+
|
|
590
|
+
# Bounce Rate
|
|
591
|
+
BOUNCED=$(echo "$REPORT" | jq -r '.bounced')
|
|
592
|
+
BOUNCE_RATE=$(echo "scale=1; $BOUNCED * 100 / $TOTAL" | bc)
|
|
593
|
+
|
|
594
|
+
echo "Bounce Rate: $BOUNCE_RATE%"
|
|
595
|
+
if (( $(echo "$BOUNCE_RATE <= 2" | bc -l) )); then
|
|
596
|
+
echo " ✅ Excellent (target: ≤2%)"
|
|
597
|
+
elif (( $(echo "$BOUNCE_RATE <= 5" | bc -l) )); then
|
|
598
|
+
echo " ⚠️ Acceptable (target: ≤2%)"
|
|
599
|
+
else
|
|
600
|
+
echo " ❌ High - Review list quality"
|
|
601
|
+
fi
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Engagement KPIs
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
#!/bin/bash
|
|
608
|
+
# engagement-kpis.sh
|
|
609
|
+
|
|
610
|
+
CAMPAIGN_ID=$1
|
|
611
|
+
|
|
612
|
+
REPORT=$(cakemail reports campaign $CAMPAIGN_ID -f json)
|
|
613
|
+
|
|
614
|
+
echo "=== Engagement KPIs ==="
|
|
615
|
+
echo ""
|
|
616
|
+
|
|
617
|
+
# Open Rate
|
|
618
|
+
OPEN_RATE=$(echo "$REPORT" | jq -r '.open_rate')
|
|
619
|
+
echo "Open Rate: $OPEN_RATE%"
|
|
620
|
+
if (( $(echo "$OPEN_RATE >= 20" | bc -l) )); then
|
|
621
|
+
echo " ✅ Excellent (industry avg: 15-25%)"
|
|
622
|
+
elif (( $(echo "$OPEN_RATE >= 15" | bc -l) )); then
|
|
623
|
+
echo " ✓ Good"
|
|
624
|
+
else
|
|
625
|
+
echo " ⚠️ Below average - Improve subject lines"
|
|
626
|
+
fi
|
|
627
|
+
echo ""
|
|
628
|
+
|
|
629
|
+
# Click Rate
|
|
630
|
+
CLICK_RATE=$(echo "$REPORT" | jq -r '.click_rate')
|
|
631
|
+
echo "Click Rate: $CLICK_RATE%"
|
|
632
|
+
if (( $(echo "$CLICK_RATE >= 3" | bc -l) )); then
|
|
633
|
+
echo " ✅ Excellent (industry avg: 2-5%)"
|
|
634
|
+
elif (( $(echo "$CLICK_RATE >= 2" | bc -l) )); then
|
|
635
|
+
echo " ✓ Good"
|
|
636
|
+
else
|
|
637
|
+
echo " ⚠️ Below average - Improve CTAs"
|
|
638
|
+
fi
|
|
639
|
+
echo ""
|
|
640
|
+
|
|
641
|
+
# CTOR
|
|
642
|
+
UNIQUE_OPENS=$(echo "$REPORT" | jq -r '.unique_opens')
|
|
643
|
+
UNIQUE_CLICKS=$(echo "$REPORT" | jq -r '.unique_clicks')
|
|
644
|
+
CTOR=$(echo "scale=1; $UNIQUE_CLICKS * 100 / $UNIQUE_OPENS" | bc)
|
|
645
|
+
|
|
646
|
+
echo "Click-to-Open Rate: $CTOR%"
|
|
647
|
+
if (( $(echo "$CTOR >= 15" | bc -l) )); then
|
|
648
|
+
echo " ✅ Excellent (industry avg: 10-20%)"
|
|
649
|
+
elif (( $(echo "$CTOR >= 10" | bc -l) )); then
|
|
650
|
+
echo " ✓ Good"
|
|
651
|
+
else
|
|
652
|
+
echo " ⚠️ Below average - Improve content relevance"
|
|
653
|
+
fi
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### List Health KPIs
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
#!/bin/bash
|
|
660
|
+
# list-health-kpis.sh
|
|
661
|
+
|
|
662
|
+
CAMPAIGN_ID=$1
|
|
663
|
+
|
|
664
|
+
REPORT=$(cakemail reports campaign $CAMPAIGN_ID -f json)
|
|
665
|
+
|
|
666
|
+
echo "=== List Health KPIs ==="
|
|
667
|
+
echo ""
|
|
668
|
+
|
|
669
|
+
# Unsubscribe Rate
|
|
670
|
+
DELIVERED=$(echo "$REPORT" | jq -r '.delivered')
|
|
671
|
+
UNSUBSCRIBES=$(echo "$REPORT" | jq -r '.unsubscribes')
|
|
672
|
+
UNSUB_RATE=$(echo "scale=2; $UNSUBSCRIBES * 100 / $DELIVERED" | bc)
|
|
673
|
+
|
|
674
|
+
echo "Unsubscribe Rate: $UNSUB_RATE%"
|
|
675
|
+
if (( $(echo "$UNSUB_RATE <= 0.2" | bc -l) )); then
|
|
676
|
+
echo " ✅ Excellent (target: ≤0.5%)"
|
|
677
|
+
elif (( $(echo "$UNSUB_RATE <= 0.5" | bc -l) )); then
|
|
678
|
+
echo " ✓ Good"
|
|
679
|
+
elif (( $(echo "$UNSUB_RATE <= 1.0" | bc -l) )); then
|
|
680
|
+
echo " ⚠️ Elevated - Review content relevance"
|
|
681
|
+
else
|
|
682
|
+
echo " ❌ High - Major content/targeting issues"
|
|
683
|
+
fi
|
|
684
|
+
echo ""
|
|
685
|
+
|
|
686
|
+
# Spam Complaint Rate
|
|
687
|
+
SPAM=$(echo "$REPORT" | jq -r '.spam_complaints // 0')
|
|
688
|
+
SPAM_RATE=$(echo "scale=3; $SPAM * 100 / $DELIVERED" | bc)
|
|
689
|
+
|
|
690
|
+
echo "Spam Complaint Rate: $SPAM_RATE%"
|
|
691
|
+
if (( $(echo "$SPAM_RATE <= 0.1" | bc -l) )); then
|
|
692
|
+
echo " ✅ Excellent (target: ≤0.1%)"
|
|
693
|
+
else
|
|
694
|
+
echo " ❌ Concerning - Review content and list source"
|
|
695
|
+
fi
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Export and Reporting
|
|
699
|
+
|
|
700
|
+
### Export Campaign Data
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
#!/bin/bash
|
|
704
|
+
# export-campaign-data.sh
|
|
705
|
+
|
|
706
|
+
CAMPAIGN_ID=$1
|
|
707
|
+
OUTPUT_FILE="campaign-${CAMPAIGN_ID}-report-$(date +%Y%m%d).json"
|
|
708
|
+
|
|
709
|
+
echo "Exporting campaign data..."
|
|
710
|
+
|
|
711
|
+
# Get campaign details
|
|
712
|
+
cakemail campaigns get $CAMPAIGN_ID -f json > campaign-details.json
|
|
713
|
+
|
|
714
|
+
# Get campaign report
|
|
715
|
+
cakemail reports campaign $CAMPAIGN_ID -f json > campaign-report.json
|
|
716
|
+
|
|
717
|
+
# Get link data
|
|
718
|
+
cakemail reports campaign-links $CAMPAIGN_ID -f json > campaign-links.json
|
|
719
|
+
|
|
720
|
+
# Combine into single file
|
|
721
|
+
jq -s '{campaign: .[0], report: .[1], links: .[2]}' \
|
|
722
|
+
campaign-details.json \
|
|
723
|
+
campaign-report.json \
|
|
724
|
+
campaign-links.json > "$OUTPUT_FILE"
|
|
725
|
+
|
|
726
|
+
# Cleanup
|
|
727
|
+
rm campaign-details.json campaign-report.json campaign-links.json
|
|
728
|
+
|
|
729
|
+
echo "✓ Exported to: $OUTPUT_FILE"
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Generate CSV Report
|
|
733
|
+
|
|
734
|
+
```bash
|
|
735
|
+
#!/bin/bash
|
|
736
|
+
# generate-csv-report.sh
|
|
737
|
+
|
|
738
|
+
echo "campaign_id,name,date,recipients,delivered,opens,clicks,open_rate,click_rate,unsubscribes" > campaigns-report.csv
|
|
739
|
+
|
|
740
|
+
# Get all sent campaigns
|
|
741
|
+
CAMPAIGNS=$(cakemail campaigns list --filter "status==sent" -f json | jq -r '.data[].id')
|
|
742
|
+
|
|
743
|
+
for ID in $CAMPAIGNS; do
|
|
744
|
+
CAMPAIGN=$(cakemail campaigns get $ID -f json)
|
|
745
|
+
REPORT=$(cakemail reports campaign $ID -f json)
|
|
746
|
+
|
|
747
|
+
NAME=$(echo "$CAMPAIGN" | jq -r '.name' | sed 's/,/_/g')
|
|
748
|
+
DATE=$(echo "$CAMPAIGN" | jq -r '.delivered_at' | cut -d'T' -f1)
|
|
749
|
+
RECIPIENTS=$(echo "$REPORT" | jq -r '.total_recipients')
|
|
750
|
+
DELIVERED=$(echo "$REPORT" | jq -r '.delivered')
|
|
751
|
+
OPENS=$(echo "$REPORT" | jq -r '.unique_opens')
|
|
752
|
+
CLICKS=$(echo "$REPORT" | jq -r '.unique_clicks')
|
|
753
|
+
OPEN_RATE=$(echo "$REPORT" | jq -r '.open_rate')
|
|
754
|
+
CLICK_RATE=$(echo "$REPORT" | jq -r '.click_rate')
|
|
755
|
+
UNSUBSCRIBES=$(echo "$REPORT" | jq -r '.unsubscribes')
|
|
756
|
+
|
|
757
|
+
echo "$ID,$NAME,$DATE,$RECIPIENTS,$DELIVERED,$OPENS,$CLICKS,$OPEN_RATE,$CLICK_RATE,$UNSUBSCRIBES" >> campaigns-report.csv
|
|
758
|
+
done
|
|
759
|
+
|
|
760
|
+
echo "✓ Report saved to: campaigns-report.csv"
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## Best Practices
|
|
764
|
+
|
|
765
|
+
### 1. Review Reports Within 24-48 Hours
|
|
766
|
+
|
|
767
|
+
Most engagement happens in first 48 hours:
|
|
768
|
+
```bash
|
|
769
|
+
# Schedule report review
|
|
770
|
+
$ cakemail reports campaign 790 > report-24h.txt
|
|
771
|
+
# Review after 24 hours
|
|
772
|
+
# Final review after 7 days
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### 2. Track Metrics Over Time
|
|
776
|
+
|
|
777
|
+
```bash
|
|
778
|
+
# Log metrics daily for first week
|
|
779
|
+
0 12 * * * /path/to/track-daily-performance.sh 790
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
### 3. Compare Similar Campaigns
|
|
783
|
+
|
|
784
|
+
```bash
|
|
785
|
+
# Compare campaigns to same segment
|
|
786
|
+
$ ./compare-campaigns.sh 790 791
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### 4. Focus on Trends, Not Single Campaigns
|
|
790
|
+
|
|
791
|
+
```bash
|
|
792
|
+
# Monthly trend analysis
|
|
793
|
+
$ ./monthly-trends.sh 2024
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### 5. Set Benchmark Goals
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
# Document your benchmarks
|
|
800
|
+
cat > benchmarks.md << 'EOF'
|
|
801
|
+
# Campaign Benchmarks
|
|
802
|
+
|
|
803
|
+
## Newsletter
|
|
804
|
+
- Open Rate Target: 25%
|
|
805
|
+
- Click Rate Target: 4%
|
|
806
|
+
- Unsubscribe Rate Max: 0.3%
|
|
807
|
+
|
|
808
|
+
## Promotional
|
|
809
|
+
- Open Rate Target: 20%
|
|
810
|
+
- Click Rate Target: 8%
|
|
811
|
+
- Unsubscribe Rate Max: 0.5%
|
|
812
|
+
EOF
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### 6. Investigate Anomalies
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
# If metrics significantly differ
|
|
819
|
+
# Check: Send time, subject line, content, segment, list quality
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
## Troubleshooting
|
|
823
|
+
|
|
824
|
+
### Low Open Rates
|
|
825
|
+
|
|
826
|
+
**Problem:** Open rate below 15%
|
|
827
|
+
|
|
828
|
+
**Solutions:**
|
|
829
|
+
- Improve subject lines (A/B test)
|
|
830
|
+
- Check send time
|
|
831
|
+
- Verify sender reputation
|
|
832
|
+
- Clean inactive subscribers
|
|
833
|
+
- Test mobile optimization
|
|
834
|
+
|
|
835
|
+
### Low Click Rates
|
|
836
|
+
|
|
837
|
+
**Problem:** Click rate below 2%
|
|
838
|
+
|
|
839
|
+
**Solutions:**
|
|
840
|
+
- Improve CTA placement
|
|
841
|
+
- Make CTAs more prominent
|
|
842
|
+
- Verify link destinations work
|
|
843
|
+
- Increase content relevance
|
|
844
|
+
- Test different content formats
|
|
845
|
+
|
|
846
|
+
### High Unsubscribe Rate
|
|
847
|
+
|
|
848
|
+
**Problem:** Unsubscribe rate above 1%
|
|
849
|
+
|
|
850
|
+
**Solutions:**
|
|
851
|
+
- Review content relevance
|
|
852
|
+
- Reduce send frequency
|
|
853
|
+
- Improve targeting/segmentation
|
|
854
|
+
- Provide preference center
|
|
855
|
+
- Check for list purchase/rental
|
|
856
|
+
|
|
857
|
+
### High Bounce Rate
|
|
858
|
+
|
|
859
|
+
**Problem:** Bounce rate above 5%
|
|
860
|
+
|
|
861
|
+
**Solutions:**
|
|
862
|
+
- Clean list regularly
|
|
863
|
+
- Remove hard bounces immediately
|
|
864
|
+
- Use double opt-in
|
|
865
|
+
- Verify email collection process
|
|
866
|
+
- Check for spam traps
|
|
867
|
+
|