@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,934 @@
|
|
|
1
|
+
# Campaign Links & Tracking
|
|
2
|
+
|
|
3
|
+
Master link management, click tracking, and link analytics for campaigns.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Campaign links allow you to:
|
|
8
|
+
- Track which links recipients click
|
|
9
|
+
- Analyze click-through rates
|
|
10
|
+
- Identify popular content
|
|
11
|
+
- Measure campaign engagement
|
|
12
|
+
- Optimize future campaigns
|
|
13
|
+
- Add UTM tracking parameters
|
|
14
|
+
|
|
15
|
+
Link tracking provides valuable insights into what content resonates with your audience.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### View Campaign Links
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ cakemail reports campaign-links 790
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Output:**
|
|
26
|
+
```
|
|
27
|
+
┌────────────────────────────────────────┬────────┬─────────┬─────────┐
|
|
28
|
+
│ URL │ Clicks │ Unique │ CTR │
|
|
29
|
+
├────────────────────────────────────────┼────────┼─────────┼─────────┤
|
|
30
|
+
│ https://example.com/product │ 450 │ 320 │ 15.2% │
|
|
31
|
+
│ https://example.com/blog/article │ 230 │ 180 │ 8.5% │
|
|
32
|
+
│ https://example.com/special-offer │ 180 │ 150 │ 7.1% │
|
|
33
|
+
│ https://example.com/contact │ 90 │ 75 │ 3.6% │
|
|
34
|
+
└────────────────────────────────────────┴────────┴─────────┴─────────┘
|
|
35
|
+
|
|
36
|
+
Total clicks: 950 (725 unique)
|
|
37
|
+
Overall CTR: 34.4%
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Understanding Link Tracking
|
|
41
|
+
|
|
42
|
+
### How Link Tracking Works
|
|
43
|
+
|
|
44
|
+
When tracking enabled:
|
|
45
|
+
|
|
46
|
+
**Original link:**
|
|
47
|
+
```html
|
|
48
|
+
<a href="https://example.com/product">View Product</a>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tracked link (in email):**
|
|
52
|
+
```html
|
|
53
|
+
<a href="https://tracking.cakemail.com/click/abc123def456...">View Product</a>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Process:**
|
|
57
|
+
1. Recipient clicks tracked link
|
|
58
|
+
2. Cakemail records click
|
|
59
|
+
3. Recipient redirected to actual destination
|
|
60
|
+
4. Click attributed to specific recipient
|
|
61
|
+
|
|
62
|
+
### Enable/Disable Tracking
|
|
63
|
+
|
|
64
|
+
**Enable click tracking:**
|
|
65
|
+
```bash
|
|
66
|
+
$ cakemail campaigns update 790 --track-clicks
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Disable click tracking:**
|
|
70
|
+
```bash
|
|
71
|
+
$ cakemail campaigns update 790 --no-track-clicks
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Check tracking status:**
|
|
75
|
+
```bash
|
|
76
|
+
$ cakemail campaigns get 790 -f json | jq '.settings.track_clicks'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Output:**
|
|
80
|
+
```
|
|
81
|
+
true
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Link Analytics
|
|
85
|
+
|
|
86
|
+
### View All Campaign Links
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
$ cakemail reports campaign-links 790
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Shows all links in campaign with click stats.
|
|
93
|
+
|
|
94
|
+
### Export Link Data
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
$ cakemail reports campaign-links 790 -f json > links-790.json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Output:**
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"campaign_id": 790,
|
|
104
|
+
"links": [
|
|
105
|
+
{
|
|
106
|
+
"url": "https://example.com/product",
|
|
107
|
+
"total_clicks": 450,
|
|
108
|
+
"unique_clicks": 320,
|
|
109
|
+
"ctr": 15.2
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"url": "https://example.com/blog/article",
|
|
113
|
+
"total_clicks": 230,
|
|
114
|
+
"unique_clicks": 180,
|
|
115
|
+
"ctr": 8.5
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"total_clicks": 950,
|
|
119
|
+
"unique_clicks": 725,
|
|
120
|
+
"overall_ctr": 34.4
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Analyze Link Performance
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
#!/bin/bash
|
|
128
|
+
# analyze-links.sh
|
|
129
|
+
|
|
130
|
+
CAMPAIGN_ID=$1
|
|
131
|
+
|
|
132
|
+
echo "=== Link Performance Analysis ==="
|
|
133
|
+
echo ""
|
|
134
|
+
|
|
135
|
+
# Get link data
|
|
136
|
+
LINKS=$(cakemail reports campaign-links $CAMPAIGN_ID -f json)
|
|
137
|
+
|
|
138
|
+
# Top performing link
|
|
139
|
+
TOP_LINK=$(echo "$LINKS" | jq -r '.links | sort_by(-.unique_clicks) | .[0]')
|
|
140
|
+
echo "Top Performing Link:"
|
|
141
|
+
echo " URL: $(echo "$TOP_LINK" | jq -r '.url')"
|
|
142
|
+
echo " Unique Clicks: $(echo "$TOP_LINK" | jq -r '.unique_clicks')"
|
|
143
|
+
echo " CTR: $(echo "$TOP_LINK" | jq -r '.ctr')%"
|
|
144
|
+
echo ""
|
|
145
|
+
|
|
146
|
+
# Lowest performing link
|
|
147
|
+
LOW_LINK=$(echo "$LINKS" | jq -r '.links | sort_by(.unique_clicks) | .[0]')
|
|
148
|
+
echo "Lowest Performing Link:"
|
|
149
|
+
echo " URL: $(echo "$LOW_LINK" | jq -r '.url')"
|
|
150
|
+
echo " Unique Clicks: $(echo "$LOW_LINK" | jq -r '.unique_clicks')"
|
|
151
|
+
echo " CTR: $(echo "$LOW_LINK" | jq -r '.ctr')%"
|
|
152
|
+
echo ""
|
|
153
|
+
|
|
154
|
+
# Overall stats
|
|
155
|
+
echo "Overall Performance:"
|
|
156
|
+
echo " Total Clicks: $(echo "$LINKS" | jq -r '.total_clicks')"
|
|
157
|
+
echo " Unique Clicks: $(echo "$LINKS" | jq -r '.unique_clicks')"
|
|
158
|
+
echo " CTR: $(echo "$LINKS" | jq -r '.overall_ctr')%"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Link Best Practices
|
|
162
|
+
|
|
163
|
+
### 1. Clear Call-to-Action
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<!-- Good: Clear CTA -->
|
|
167
|
+
<a href="https://example.com/product">Shop Now</a>
|
|
168
|
+
<a href="https://example.com/download">Download Guide</a>
|
|
169
|
+
<a href="https://example.com/signup">Start Free Trial</a>
|
|
170
|
+
|
|
171
|
+
<!-- Avoid: Generic text -->
|
|
172
|
+
<a href="https://example.com">Click here</a>
|
|
173
|
+
<a href="https://example.com">Read more</a>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 2. Button vs Text Links
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<!-- Button (higher visibility) -->
|
|
180
|
+
<a href="https://example.com/buy" style="background: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px;">
|
|
181
|
+
Buy Now
|
|
182
|
+
</a>
|
|
183
|
+
|
|
184
|
+
<!-- Text link (lower visibility) -->
|
|
185
|
+
<a href="https://example.com/learn-more">Learn more about our products</a>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Performance:**
|
|
189
|
+
- Buttons typically get 2-3x more clicks
|
|
190
|
+
- Use buttons for primary CTAs
|
|
191
|
+
- Use text links for secondary content
|
|
192
|
+
|
|
193
|
+
### 3. Link Placement
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<!-- Above the fold (higher CTR) -->
|
|
197
|
+
<h1>Special Offer</h1>
|
|
198
|
+
<p>Save 20% today!</p>
|
|
199
|
+
<a href="https://example.com/offer">Get Discount</a>
|
|
200
|
+
|
|
201
|
+
<!-- Multiple CTAs -->
|
|
202
|
+
<a href="https://example.com/offer">Top CTA</a>
|
|
203
|
+
<!-- Content -->
|
|
204
|
+
<a href="https://example.com/offer">Middle CTA</a>
|
|
205
|
+
<!-- More content -->
|
|
206
|
+
<a href="https://example.com/offer">Bottom CTA</a>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Best practices:**
|
|
210
|
+
- Primary CTA above the fold
|
|
211
|
+
- Repeat CTA 2-3 times for longer emails
|
|
212
|
+
- Most important link first
|
|
213
|
+
|
|
214
|
+
### 4. Link Density
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<!-- Good: Focused (2-3 main links) -->
|
|
218
|
+
<a href="https://example.com/product1">Product 1</a>
|
|
219
|
+
<a href="https://example.com/product2">Product 2</a>
|
|
220
|
+
<a href="https://example.com/shop">Shop All</a>
|
|
221
|
+
|
|
222
|
+
<!-- Avoid: Too many links (confuses readers) -->
|
|
223
|
+
<a href="...">Link 1</a> <a href="...">Link 2</a> <a href="...">Link 3</a>
|
|
224
|
+
<a href="...">Link 4</a> <a href="...">Link 5</a> <a href="...">Link 6</a>
|
|
225
|
+
<a href="...">Link 7</a> <a href="...">Link 8</a> <a href="...">Link 9</a>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Recommendations:**
|
|
229
|
+
- 2-3 primary links ideal
|
|
230
|
+
- Maximum 5-7 total links
|
|
231
|
+
- Each link should have clear purpose
|
|
232
|
+
|
|
233
|
+
## UTM Parameters
|
|
234
|
+
|
|
235
|
+
### Adding UTM Tracking
|
|
236
|
+
|
|
237
|
+
UTM parameters track campaign performance in Google Analytics:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Base URL
|
|
241
|
+
https://example.com/product
|
|
242
|
+
|
|
243
|
+
# With UTM parameters
|
|
244
|
+
https://example.com/product?utm_source=cakemail&utm_medium=email&utm_campaign=march_newsletter
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### UTM Parameter Reference
|
|
248
|
+
|
|
249
|
+
| Parameter | Purpose | Example |
|
|
250
|
+
|-----------|---------|---------|
|
|
251
|
+
| `utm_source` | Traffic source | `cakemail` |
|
|
252
|
+
| `utm_medium` | Marketing medium | `email` |
|
|
253
|
+
| `utm_campaign` | Campaign name | `march_newsletter` |
|
|
254
|
+
| `utm_content` | Content variant | `header_cta` |
|
|
255
|
+
| `utm_term` | Keyword (paid search) | `running_shoes` |
|
|
256
|
+
|
|
257
|
+
### Build UTM Links
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
#!/bin/bash
|
|
261
|
+
# build-utm-link.sh
|
|
262
|
+
|
|
263
|
+
BASE_URL="https://example.com/product"
|
|
264
|
+
SOURCE="cakemail"
|
|
265
|
+
MEDIUM="email"
|
|
266
|
+
CAMPAIGN="march_newsletter"
|
|
267
|
+
CONTENT="$1" # Pass as argument
|
|
268
|
+
|
|
269
|
+
# Build UTM link
|
|
270
|
+
UTM_LINK="${BASE_URL}?utm_source=${SOURCE}&utm_medium=${MEDIUM}&utm_campaign=${CAMPAIGN}&utm_content=${CONTENT}"
|
|
271
|
+
|
|
272
|
+
echo "UTM Link: $UTM_LINK"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Usage:**
|
|
276
|
+
```bash
|
|
277
|
+
$ ./build-utm-link.sh header_cta
|
|
278
|
+
UTM Link: https://example.com/product?utm_source=cakemail&utm_medium=email&utm_campaign=march_newsletter&utm_content=header_cta
|
|
279
|
+
|
|
280
|
+
$ ./build-utm-link.sh footer_cta
|
|
281
|
+
UTM Link: https://example.com/product?utm_source=cakemail&utm_medium=email&utm_campaign=march_newsletter&utm_content=footer_cta
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### UTM Campaign Template
|
|
285
|
+
|
|
286
|
+
```html
|
|
287
|
+
<!-- Header CTA -->
|
|
288
|
+
<a href="https://example.com/offer?utm_source=cakemail&utm_medium=email&utm_campaign=spring_sale&utm_content=header_cta">
|
|
289
|
+
Shop Spring Sale
|
|
290
|
+
</a>
|
|
291
|
+
|
|
292
|
+
<!-- Product 1 -->
|
|
293
|
+
<a href="https://example.com/product1?utm_source=cakemail&utm_medium=email&utm_campaign=spring_sale&utm_content=product1">
|
|
294
|
+
View Product 1
|
|
295
|
+
</a>
|
|
296
|
+
|
|
297
|
+
<!-- Product 2 -->
|
|
298
|
+
<a href="https://example.com/product2?utm_source=cakemail&utm_medium=email&utm_campaign=spring_sale&utm_content=product2">
|
|
299
|
+
View Product 2
|
|
300
|
+
</a>
|
|
301
|
+
|
|
302
|
+
<!-- Footer CTA -->
|
|
303
|
+
<a href="https://example.com/offer?utm_source=cakemail&utm_medium=email&utm_campaign=spring_sale&utm_content=footer_cta">
|
|
304
|
+
Shop Now
|
|
305
|
+
</a>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Bulk Add UTM Parameters
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
#!/bin/bash
|
|
312
|
+
# add-utm-to-links.sh
|
|
313
|
+
|
|
314
|
+
CAMPAIGN="march_newsletter"
|
|
315
|
+
SOURCE="cakemail"
|
|
316
|
+
MEDIUM="email"
|
|
317
|
+
|
|
318
|
+
# Original links
|
|
319
|
+
LINKS=(
|
|
320
|
+
"https://example.com/product1"
|
|
321
|
+
"https://example.com/product2"
|
|
322
|
+
"https://example.com/blog"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
echo "Links with UTM parameters:"
|
|
326
|
+
echo ""
|
|
327
|
+
|
|
328
|
+
for LINK in "${LINKS[@]}"; do
|
|
329
|
+
# Check if URL already has parameters
|
|
330
|
+
if [[ $LINK == *"?"* ]]; then
|
|
331
|
+
SEPARATOR="&"
|
|
332
|
+
else
|
|
333
|
+
SEPARATOR="?"
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
UTM="${LINK}${SEPARATOR}utm_source=${SOURCE}&utm_medium=${MEDIUM}&utm_campaign=${CAMPAIGN}"
|
|
337
|
+
echo "$UTM"
|
|
338
|
+
done
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Link Comparison
|
|
342
|
+
|
|
343
|
+
### Compare Link Performance Across Campaigns
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
#!/bin/bash
|
|
347
|
+
# compare-links.sh
|
|
348
|
+
|
|
349
|
+
CAMPAIGN_1=$1
|
|
350
|
+
CAMPAIGN_2=$2
|
|
351
|
+
|
|
352
|
+
echo "=== Link Performance Comparison ==="
|
|
353
|
+
echo ""
|
|
354
|
+
|
|
355
|
+
# Campaign 1 links
|
|
356
|
+
echo "Campaign $CAMPAIGN_1:"
|
|
357
|
+
LINKS_1=$(cakemail reports campaign-links $CAMPAIGN_1 -f json)
|
|
358
|
+
echo " Total Clicks: $(echo "$LINKS_1" | jq -r '.total_clicks')"
|
|
359
|
+
echo " CTR: $(echo "$LINKS_1" | jq -r '.overall_ctr')%"
|
|
360
|
+
echo " Top Link: $(echo "$LINKS_1" | jq -r '.links | sort_by(-.unique_clicks) | .[0].url')"
|
|
361
|
+
echo ""
|
|
362
|
+
|
|
363
|
+
# Campaign 2 links
|
|
364
|
+
echo "Campaign $CAMPAIGN_2:"
|
|
365
|
+
LINKS_2=$(cakemail reports campaign-links $CAMPAIGN_2 -f json)
|
|
366
|
+
echo " Total Clicks: $(echo "$LINKS_2" | jq -r '.total_clicks')"
|
|
367
|
+
echo " CTR: $(echo "$LINKS_2" | jq -r '.overall_ctr')%"
|
|
368
|
+
echo " Top Link: $(echo "$LINKS_2" | jq -r '.links | sort_by(-.unique_clicks) | .[0].url')"
|
|
369
|
+
echo ""
|
|
370
|
+
|
|
371
|
+
# Comparison
|
|
372
|
+
CLICKS_1=$(echo "$LINKS_1" | jq -r '.total_clicks')
|
|
373
|
+
CLICKS_2=$(echo "$LINKS_2" | jq -r '.total_clicks')
|
|
374
|
+
|
|
375
|
+
if [ $CLICKS_1 -gt $CLICKS_2 ]; then
|
|
376
|
+
PERCENT=$(echo "scale=1; ($CLICKS_1 - $CLICKS_2) * 100 / $CLICKS_2" | bc)
|
|
377
|
+
echo "Campaign $CAMPAIGN_1 had $PERCENT% more clicks"
|
|
378
|
+
else
|
|
379
|
+
PERCENT=$(echo "scale=1; ($CLICKS_2 - $CLICKS_1) * 100 / $CLICKS_1" | bc)
|
|
380
|
+
echo "Campaign $CAMPAIGN_2 had $PERCENT% more clicks"
|
|
381
|
+
fi
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Track Link Evolution
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
#!/bin/bash
|
|
388
|
+
# track-link-evolution.sh
|
|
389
|
+
|
|
390
|
+
URL="https://example.com/product"
|
|
391
|
+
|
|
392
|
+
echo "=== Link Evolution: $URL ==="
|
|
393
|
+
echo ""
|
|
394
|
+
|
|
395
|
+
# Get all campaigns
|
|
396
|
+
CAMPAIGNS=$(cakemail campaigns list --filter "status==sent" -f json | jq -r '.data[].id')
|
|
397
|
+
|
|
398
|
+
echo "Campaign | Total Clicks | Unique Clicks | CTR"
|
|
399
|
+
echo "---------|--------------|---------------|-----"
|
|
400
|
+
|
|
401
|
+
for CAMPAIGN_ID in $CAMPAIGNS; do
|
|
402
|
+
LINKS=$(cakemail reports campaign-links $CAMPAIGN_ID -f json 2>/dev/null)
|
|
403
|
+
|
|
404
|
+
# Find specific URL in campaign
|
|
405
|
+
LINK_DATA=$(echo "$LINKS" | jq --arg url "$URL" '.links[] | select(.url == $url)')
|
|
406
|
+
|
|
407
|
+
if [ -n "$LINK_DATA" ]; then
|
|
408
|
+
TOTAL=$(echo "$LINK_DATA" | jq -r '.total_clicks')
|
|
409
|
+
UNIQUE=$(echo "$LINK_DATA" | jq -r '.unique_clicks')
|
|
410
|
+
CTR=$(echo "$LINK_DATA" | jq -r '.ctr')
|
|
411
|
+
|
|
412
|
+
printf "%-8s | %-12s | %-13s | %s%%\n" "$CAMPAIGN_ID" "$TOTAL" "$UNIQUE" "$CTR"
|
|
413
|
+
fi
|
|
414
|
+
done
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Link Testing
|
|
418
|
+
|
|
419
|
+
### Test Link Before Sending
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
#!/bin/bash
|
|
423
|
+
# test-links.sh
|
|
424
|
+
|
|
425
|
+
CAMPAIGN_ID=$1
|
|
426
|
+
|
|
427
|
+
echo "=== Testing Campaign Links ==="
|
|
428
|
+
echo ""
|
|
429
|
+
|
|
430
|
+
# Send test email
|
|
431
|
+
echo "Sending test email..."
|
|
432
|
+
cakemail campaigns test $CAMPAIGN_ID -e link-tester@company.com
|
|
433
|
+
|
|
434
|
+
echo "✓ Test sent to link-tester@company.com"
|
|
435
|
+
echo ""
|
|
436
|
+
echo "Manual Link Testing Checklist:"
|
|
437
|
+
echo " ☐ All links clickable"
|
|
438
|
+
echo " ☐ Links go to correct destinations"
|
|
439
|
+
echo " ☐ UTM parameters present"
|
|
440
|
+
echo " ☐ Tracking parameters work"
|
|
441
|
+
echo " ☐ Landing pages load correctly"
|
|
442
|
+
echo " ☐ Mobile links work on phone"
|
|
443
|
+
echo " ☐ No broken links (404 errors)"
|
|
444
|
+
echo ""
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Validate Link Destinations
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
#!/bin/bash
|
|
451
|
+
# validate-links.sh
|
|
452
|
+
|
|
453
|
+
# Extract links from campaign HTML
|
|
454
|
+
CAMPAIGN_ID=$1
|
|
455
|
+
HTML=$(cakemail campaigns get $CAMPAIGN_ID -f json | jq -r '.html')
|
|
456
|
+
|
|
457
|
+
# Extract all URLs (basic regex)
|
|
458
|
+
URLS=$(echo "$HTML" | grep -oE 'href="[^"]+"' | sed 's/href="//g' | sed 's/"//g')
|
|
459
|
+
|
|
460
|
+
echo "=== Validating Campaign Links ==="
|
|
461
|
+
echo ""
|
|
462
|
+
|
|
463
|
+
for URL in $URLS; do
|
|
464
|
+
# Skip special links
|
|
465
|
+
if [[ $URL == "mailto:"* ]] || [[ $URL == "#"* ]] || [[ $URL == "{{"* ]]; then
|
|
466
|
+
continue
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
# Check URL
|
|
470
|
+
STATUS=$(curl -o /dev/null -s -w "%{http_code}" -L "$URL")
|
|
471
|
+
|
|
472
|
+
if [ "$STATUS" == "200" ]; then
|
|
473
|
+
echo "✓ $URL"
|
|
474
|
+
else
|
|
475
|
+
echo "✗ $URL (HTTP $STATUS)"
|
|
476
|
+
fi
|
|
477
|
+
done
|
|
478
|
+
|
|
479
|
+
echo ""
|
|
480
|
+
echo "Validation complete"
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Link Optimization
|
|
484
|
+
|
|
485
|
+
### A/B Test Link Text
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
#!/bin/bash
|
|
489
|
+
# ab-test-link-text.sh
|
|
490
|
+
|
|
491
|
+
LIST_ID=123
|
|
492
|
+
SENDER_ID=101
|
|
493
|
+
|
|
494
|
+
# Version A: "Shop Now"
|
|
495
|
+
HTML_A='<h1>Special Offer</h1><p>Save 20%</p><a href="https://example.com/sale?v=a">Shop Now</a>'
|
|
496
|
+
|
|
497
|
+
ID_A=$(cakemail campaigns create \
|
|
498
|
+
-n "A/B Test - Shop Now" \
|
|
499
|
+
-l $LIST_ID \
|
|
500
|
+
-s $SENDER_ID \
|
|
501
|
+
--html "$HTML_A" \
|
|
502
|
+
--subject "Special Offer Inside" \
|
|
503
|
+
-f json | jq -r '.id')
|
|
504
|
+
|
|
505
|
+
# Version B: "Get 20% Off"
|
|
506
|
+
HTML_B='<h1>Special Offer</h1><p>Save 20%</p><a href="https://example.com/sale?v=b">Get 20% Off</a>'
|
|
507
|
+
|
|
508
|
+
ID_B=$(cakemail campaigns create \
|
|
509
|
+
-n "A/B Test - Get 20% Off" \
|
|
510
|
+
-l $LIST_ID \
|
|
511
|
+
-s $SENDER_ID \
|
|
512
|
+
--html "$HTML_B" \
|
|
513
|
+
--subject "Special Offer Inside" \
|
|
514
|
+
-f json | jq -r '.id')
|
|
515
|
+
|
|
516
|
+
echo "A/B Test created:"
|
|
517
|
+
echo "Version A (Shop Now): $ID_A"
|
|
518
|
+
echo "Version B (Get 20% Off): $ID_B"
|
|
519
|
+
echo ""
|
|
520
|
+
echo "After sending, compare with:"
|
|
521
|
+
echo "cakemail reports campaign-links $ID_A"
|
|
522
|
+
echo "cakemail reports campaign-links $ID_B"
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Optimize Link Position
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
#!/bin/bash
|
|
529
|
+
# test-link-position.sh
|
|
530
|
+
|
|
531
|
+
# Test: CTA above vs below content
|
|
532
|
+
|
|
533
|
+
# Version 1: CTA above content
|
|
534
|
+
HTML_ABOVE='
|
|
535
|
+
<a href="https://example.com/offer?pos=above">Shop Now</a>
|
|
536
|
+
<h1>Product Details</h1>
|
|
537
|
+
<p>Long description...</p>
|
|
538
|
+
'
|
|
539
|
+
|
|
540
|
+
# Version 2: CTA below content
|
|
541
|
+
HTML_BELOW='
|
|
542
|
+
<h1>Product Details</h1>
|
|
543
|
+
<p>Long description...</p>
|
|
544
|
+
<a href="https://example.com/offer?pos=below">Shop Now</a>
|
|
545
|
+
'
|
|
546
|
+
|
|
547
|
+
# Create campaigns and compare CTR
|
|
548
|
+
echo "Test campaign link positions"
|
|
549
|
+
echo "Compare CTR after sending both versions"
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Link Reports
|
|
553
|
+
|
|
554
|
+
### Generate Link Report
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
#!/bin/bash
|
|
558
|
+
# link-report.sh
|
|
559
|
+
|
|
560
|
+
CAMPAIGN_ID=$1
|
|
561
|
+
OUTPUT="link-report-${CAMPAIGN_ID}.txt"
|
|
562
|
+
|
|
563
|
+
echo "=== Campaign Link Report ===" > $OUTPUT
|
|
564
|
+
echo "Campaign ID: $CAMPAIGN_ID" >> $OUTPUT
|
|
565
|
+
echo "Generated: $(date)" >> $OUTPUT
|
|
566
|
+
echo "" >> $OUTPUT
|
|
567
|
+
|
|
568
|
+
# Get campaign info
|
|
569
|
+
CAMPAIGN=$(cakemail campaigns get $CAMPAIGN_ID -f json)
|
|
570
|
+
echo "Campaign: $(echo "$CAMPAIGN" | jq -r '.name')" >> $OUTPUT
|
|
571
|
+
echo "Subject: $(echo "$CAMPAIGN" | jq -r '.subject')" >> $OUTPUT
|
|
572
|
+
echo "" >> $OUTPUT
|
|
573
|
+
|
|
574
|
+
# Get link data
|
|
575
|
+
LINKS=$(cakemail reports campaign-links $CAMPAIGN_ID -f json)
|
|
576
|
+
|
|
577
|
+
echo "=== Link Performance ===" >> $OUTPUT
|
|
578
|
+
echo "" >> $OUTPUT
|
|
579
|
+
|
|
580
|
+
# Each link
|
|
581
|
+
echo "$LINKS" | jq -r '.links[] | "URL: \(.url)\n Total Clicks: \(.total_clicks)\n Unique Clicks: \(.unique_clicks)\n CTR: \(.ctr)%\n"' >> $OUTPUT
|
|
582
|
+
|
|
583
|
+
echo "=== Overall Stats ===" >> $OUTPUT
|
|
584
|
+
echo "Total Clicks: $(echo "$LINKS" | jq -r '.total_clicks')" >> $OUTPUT
|
|
585
|
+
echo "Unique Clicks: $(echo "$LINKS" | jq -r '.unique_clicks')" >> $OUTPUT
|
|
586
|
+
echo "Overall CTR: $(echo "$LINKS" | jq -r '.overall_ctr')%" >> $OUTPUT
|
|
587
|
+
|
|
588
|
+
cat $OUTPUT
|
|
589
|
+
echo ""
|
|
590
|
+
echo "Report saved to: $OUTPUT"
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Compare Multiple Campaigns
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
#!/bin/bash
|
|
597
|
+
# compare-campaign-links.sh
|
|
598
|
+
|
|
599
|
+
echo "Campaign,Total Clicks,Unique Clicks,CTR,Top Link" > campaign-comparison.csv
|
|
600
|
+
|
|
601
|
+
# Get all sent campaigns
|
|
602
|
+
CAMPAIGNS=$(cakemail campaigns list --filter "status==sent" -f json | jq -r '.data[].id')
|
|
603
|
+
|
|
604
|
+
for ID in $CAMPAIGNS; do
|
|
605
|
+
LINKS=$(cakemail reports campaign-links $ID -f json 2>/dev/null)
|
|
606
|
+
|
|
607
|
+
if [ -n "$LINKS" ]; then
|
|
608
|
+
TOTAL=$(echo "$LINKS" | jq -r '.total_clicks')
|
|
609
|
+
UNIQUE=$(echo "$LINKS" | jq -r '.unique_clicks')
|
|
610
|
+
CTR=$(echo "$LINKS" | jq -r '.overall_ctr')
|
|
611
|
+
TOP=$(echo "$LINKS" | jq -r '.links | sort_by(-.unique_clicks) | .[0].url')
|
|
612
|
+
|
|
613
|
+
echo "$ID,$TOTAL,$UNIQUE,$CTR,$TOP" >> campaign-comparison.csv
|
|
614
|
+
fi
|
|
615
|
+
done
|
|
616
|
+
|
|
617
|
+
echo "Comparison saved to: campaign-comparison.csv"
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Special Link Types
|
|
621
|
+
|
|
622
|
+
### Unsubscribe Links
|
|
623
|
+
|
|
624
|
+
Required in every campaign:
|
|
625
|
+
|
|
626
|
+
```html
|
|
627
|
+
<!-- Automatic unsubscribe (Cakemail handles) -->
|
|
628
|
+
<a href="{{unsubscribe_url}}">Unsubscribe</a>
|
|
629
|
+
|
|
630
|
+
<!-- Custom unsubscribe page -->
|
|
631
|
+
<a href="https://example.com/unsubscribe?email={{email}}">Manage Preferences</a>
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Best practices:**
|
|
635
|
+
- Place in footer
|
|
636
|
+
- Make visible but not prominent
|
|
637
|
+
- Use clear language: "Unsubscribe" not "Click here"
|
|
638
|
+
- Include preference center option
|
|
639
|
+
|
|
640
|
+
### Social Media Links
|
|
641
|
+
|
|
642
|
+
```html
|
|
643
|
+
<!-- Social media links -->
|
|
644
|
+
<a href="https://facebook.com/yourcompany">Facebook</a>
|
|
645
|
+
<a href="https://twitter.com/yourcompany">Twitter</a>
|
|
646
|
+
<a href="https://linkedin.com/company/yourcompany">LinkedIn</a>
|
|
647
|
+
|
|
648
|
+
<!-- With UTM tracking -->
|
|
649
|
+
<a href="https://facebook.com/yourcompany?utm_source=cakemail&utm_medium=email&utm_campaign=march_newsletter&utm_content=social_facebook">
|
|
650
|
+
Facebook
|
|
651
|
+
</a>
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### View in Browser Link
|
|
655
|
+
|
|
656
|
+
```html
|
|
657
|
+
<!-- View email in web browser -->
|
|
658
|
+
<a href="{{view_in_browser_url}}">View in Browser</a>
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Use cases:**
|
|
662
|
+
- Email client rendering issues
|
|
663
|
+
- Images blocked
|
|
664
|
+
- Accessibility needs
|
|
665
|
+
- Sharing email content
|
|
666
|
+
|
|
667
|
+
## Link Click Workflows
|
|
668
|
+
|
|
669
|
+
### Workflow 1: Top Links Dashboard
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
#!/bin/bash
|
|
673
|
+
# top-links-dashboard.sh
|
|
674
|
+
|
|
675
|
+
echo "=== Top Performing Links (Last 30 Days) ==="
|
|
676
|
+
echo ""
|
|
677
|
+
|
|
678
|
+
# Get recent campaigns
|
|
679
|
+
CUTOFF=$(date -d "30 days ago" +%Y-%m-%d)
|
|
680
|
+
CAMPAIGNS=$(cakemail campaigns list \
|
|
681
|
+
--filter "status==sent;delivered_at>=$CUTOFF" \
|
|
682
|
+
-f json | jq -r '.data[].id')
|
|
683
|
+
|
|
684
|
+
# Collect all link data
|
|
685
|
+
declare -A LINK_CLICKS
|
|
686
|
+
declare -A LINK_UNIQUE
|
|
687
|
+
|
|
688
|
+
for CAMPAIGN_ID in $CAMPAIGNS; do
|
|
689
|
+
LINKS=$(cakemail reports campaign-links $CAMPAIGN_ID -f json 2>/dev/null)
|
|
690
|
+
|
|
691
|
+
if [ -n "$LINKS" ]; then
|
|
692
|
+
# Process each link
|
|
693
|
+
echo "$LINKS" | jq -r '.links[] | "\(.url)|\(.total_clicks)|\(.unique_clicks)"' | while IFS='|' read URL CLICKS UNIQUE; do
|
|
694
|
+
# Aggregate by URL
|
|
695
|
+
LINK_CLICKS[$URL]=$((${LINK_CLICKS[$URL]:-0} + $CLICKS))
|
|
696
|
+
LINK_UNIQUE[$URL]=$((${LINK_UNIQUE[$URL]:-0} + $UNIQUE))
|
|
697
|
+
done
|
|
698
|
+
fi
|
|
699
|
+
done
|
|
700
|
+
|
|
701
|
+
# Display top 10
|
|
702
|
+
echo "Rank | URL | Total Clicks | Unique Clicks"
|
|
703
|
+
echo "-----|-----|--------------|---------------"
|
|
704
|
+
|
|
705
|
+
# (Note: This is simplified - actual implementation would need sorting)
|
|
706
|
+
for URL in "${!LINK_CLICKS[@]}"; do
|
|
707
|
+
printf "%s | %d | %d\n" "$URL" "${LINK_CLICKS[$URL]}" "${LINK_UNIQUE[$URL]}"
|
|
708
|
+
done | sort -t'|' -k2 -rn | head -10 | nl
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Workflow 2: Link Health Check
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
#!/bin/bash
|
|
715
|
+
# link-health-check.sh
|
|
716
|
+
|
|
717
|
+
CAMPAIGN_ID=$1
|
|
718
|
+
|
|
719
|
+
echo "=== Link Health Check ==="
|
|
720
|
+
echo ""
|
|
721
|
+
|
|
722
|
+
# Get all links
|
|
723
|
+
LINKS=$(cakemail reports campaign-links $CAMPAIGN_ID -f json)
|
|
724
|
+
TOTAL_LINKS=$(echo "$LINKS" | jq '.links | length')
|
|
725
|
+
|
|
726
|
+
echo "Total Links: $TOTAL_LINKS"
|
|
727
|
+
echo ""
|
|
728
|
+
|
|
729
|
+
# Check for issues
|
|
730
|
+
ZERO_CLICKS=$(echo "$LINKS" | jq '[.links[] | select(.total_clicks == 0)] | length')
|
|
731
|
+
|
|
732
|
+
if [ $ZERO_CLICKS -gt 0 ]; then
|
|
733
|
+
echo "⚠️ Warning: $ZERO_CLICKS links with zero clicks"
|
|
734
|
+
echo ""
|
|
735
|
+
echo "Links with no clicks:"
|
|
736
|
+
echo "$LINKS" | jq -r '.links[] | select(.total_clicks == 0) | " • \(.url)"'
|
|
737
|
+
echo ""
|
|
738
|
+
echo "Consider:"
|
|
739
|
+
echo " • Are these links visible?"
|
|
740
|
+
echo " • Is link text clear?"
|
|
741
|
+
echo " • Is destination valuable?"
|
|
742
|
+
else
|
|
743
|
+
echo "✅ All links received clicks"
|
|
744
|
+
fi
|
|
745
|
+
|
|
746
|
+
echo ""
|
|
747
|
+
|
|
748
|
+
# Check CTR
|
|
749
|
+
OVERALL_CTR=$(echo "$LINKS" | jq -r '.overall_ctr')
|
|
750
|
+
CTR_INT=$(echo "$OVERALL_CTR" | cut -d'.' -f1)
|
|
751
|
+
|
|
752
|
+
if [ $CTR_INT -lt 5 ]; then
|
|
753
|
+
echo "⚠️ Low overall CTR: $OVERALL_CTR%"
|
|
754
|
+
echo " Consider improving link visibility or CTA text"
|
|
755
|
+
elif [ $CTR_INT -lt 15 ]; then
|
|
756
|
+
echo "✓ Moderate CTR: $OVERALL_CTR%"
|
|
757
|
+
else
|
|
758
|
+
echo "✅ Excellent CTR: $OVERALL_CTR%"
|
|
759
|
+
fi
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
### Workflow 3: Link Segment Analysis
|
|
763
|
+
|
|
764
|
+
```bash
|
|
765
|
+
#!/bin/bash
|
|
766
|
+
# link-segment-analysis.sh
|
|
767
|
+
|
|
768
|
+
CAMPAIGN_ID=$1
|
|
769
|
+
|
|
770
|
+
echo "=== Link Click Segmentation ==="
|
|
771
|
+
echo ""
|
|
772
|
+
|
|
773
|
+
# Get campaign stats
|
|
774
|
+
CAMPAIGN=$(cakemail campaigns get $CAMPAIGN_ID -f json)
|
|
775
|
+
RECIPIENTS=$(echo "$CAMPAIGN" | jq -r '.recipients_count')
|
|
776
|
+
|
|
777
|
+
# Get link data
|
|
778
|
+
LINKS=$(cakemail reports campaign-links $CAMPAIGN_ID -f json)
|
|
779
|
+
UNIQUE_CLICKERS=$(echo "$LINKS" | jq -r '.unique_clicks')
|
|
780
|
+
|
|
781
|
+
# Calculate segments
|
|
782
|
+
CLICKERS=$UNIQUE_CLICKERS
|
|
783
|
+
NON_CLICKERS=$((RECIPIENTS - UNIQUE_CLICKERS))
|
|
784
|
+
CLICK_RATE=$(echo "scale=1; $CLICKERS * 100 / $RECIPIENTS" | bc)
|
|
785
|
+
|
|
786
|
+
echo "Recipients: $RECIPIENTS"
|
|
787
|
+
echo ""
|
|
788
|
+
echo "Clicked Links: $CLICKERS ($CLICK_RATE%)"
|
|
789
|
+
echo "Did Not Click: $NON_CLICKERS"
|
|
790
|
+
echo ""
|
|
791
|
+
|
|
792
|
+
# Create segment for future targeting
|
|
793
|
+
echo "Create segment for re-engagement:"
|
|
794
|
+
echo " • Target non-clickers with different content"
|
|
795
|
+
echo " • Reward clickers with special offers"
|
|
796
|
+
echo ""
|
|
797
|
+
|
|
798
|
+
# Suggestions
|
|
799
|
+
if [ $CLICK_RATE -lt 10 ]; then
|
|
800
|
+
echo "💡 Low engagement - Consider:"
|
|
801
|
+
echo " • More compelling CTAs"
|
|
802
|
+
echo " • Better link visibility"
|
|
803
|
+
echo " • More relevant content"
|
|
804
|
+
elif [ $CLICK_RATE -gt 25 ]; then
|
|
805
|
+
echo "✅ High engagement - Replicate success:"
|
|
806
|
+
echo " • Use similar link strategies"
|
|
807
|
+
echo " • Maintain content quality"
|
|
808
|
+
fi
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
## Troubleshooting
|
|
812
|
+
|
|
813
|
+
### Links Not Tracked
|
|
814
|
+
|
|
815
|
+
**Problem:** Links not appearing in reports
|
|
816
|
+
|
|
817
|
+
**Solutions:**
|
|
818
|
+
|
|
819
|
+
```bash
|
|
820
|
+
# Check tracking enabled
|
|
821
|
+
$ cakemail campaigns get 790 -f json | jq '.settings.track_clicks'
|
|
822
|
+
|
|
823
|
+
# Enable tracking
|
|
824
|
+
$ cakemail campaigns update 790 --track-clicks
|
|
825
|
+
|
|
826
|
+
# Resend test
|
|
827
|
+
$ cakemail campaigns test 790 -e test@example.com
|
|
828
|
+
|
|
829
|
+
# Verify tracked links in test email
|
|
830
|
+
# Links should use tracking.cakemail.com domain
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### No Click Data
|
|
834
|
+
|
|
835
|
+
**Problem:** Link reports show zero clicks
|
|
836
|
+
|
|
837
|
+
**Possible causes:**
|
|
838
|
+
|
|
839
|
+
```bash
|
|
840
|
+
# 1. Campaign not sent yet
|
|
841
|
+
$ cakemail campaigns get 790 -f json | jq '.status'
|
|
842
|
+
# Status must be "sent"
|
|
843
|
+
|
|
844
|
+
# 2. Just sent - data not available yet
|
|
845
|
+
# Wait 1-2 hours after send
|
|
846
|
+
|
|
847
|
+
# 3. Links not clickable in email
|
|
848
|
+
# Check HTML formatting
|
|
849
|
+
|
|
850
|
+
# 4. Campaign went to spam
|
|
851
|
+
# Check deliverability
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
### Broken Links in Email
|
|
855
|
+
|
|
856
|
+
**Problem:** Links don't work or go to wrong destination
|
|
857
|
+
|
|
858
|
+
**Solutions:**
|
|
859
|
+
|
|
860
|
+
```bash
|
|
861
|
+
# Check original HTML
|
|
862
|
+
$ cakemail campaigns get 790 -f json | jq -r '.html' | grep 'href='
|
|
863
|
+
|
|
864
|
+
# Common issues:
|
|
865
|
+
# - Missing protocol: href="example.com" ❌
|
|
866
|
+
# - Should be: href="https://example.com" ✅
|
|
867
|
+
|
|
868
|
+
# - Relative URLs: href="/page" ❌
|
|
869
|
+
# - Should be: href="https://example.com/page" ✅
|
|
870
|
+
|
|
871
|
+
# - Broken merge tags: href="https://example.com/{{broken}}" ❌
|
|
872
|
+
# - Fix merge tag syntax ✅
|
|
873
|
+
|
|
874
|
+
# Update HTML
|
|
875
|
+
$ cakemail campaigns update 790 --html-file fixed.html
|
|
876
|
+
|
|
877
|
+
# Test again
|
|
878
|
+
$ cakemail campaigns test 790 -e test@example.com
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
### UTM Parameters Missing
|
|
882
|
+
|
|
883
|
+
**Problem:** Google Analytics not tracking email traffic
|
|
884
|
+
|
|
885
|
+
**Solutions:**
|
|
886
|
+
|
|
887
|
+
```bash
|
|
888
|
+
# Check if UTM parameters in links
|
|
889
|
+
$ cakemail campaigns get 790 -f json | jq -r '.html' | grep 'utm_'
|
|
890
|
+
|
|
891
|
+
# If missing, add UTM parameters to all links
|
|
892
|
+
# Use script or manual update
|
|
893
|
+
|
|
894
|
+
# Verify in test email
|
|
895
|
+
$ cakemail campaigns test 790 -e test@example.com
|
|
896
|
+
|
|
897
|
+
# Click link and check browser URL
|
|
898
|
+
# Should see: ?utm_source=cakemail&utm_medium=email&...
|
|
899
|
+
|
|
900
|
+
# Verify in Google Analytics
|
|
901
|
+
# Acquisition > Campaigns > All Campaigns
|
|
902
|
+
# Look for campaign name
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Click Tracking Interfering
|
|
906
|
+
|
|
907
|
+
**Problem:** Tracked links flagged by spam filters
|
|
908
|
+
|
|
909
|
+
**Solutions:**
|
|
910
|
+
|
|
911
|
+
```bash
|
|
912
|
+
# Disable tracking for specific campaign
|
|
913
|
+
$ cakemail campaigns update 790 --no-track-clicks
|
|
914
|
+
|
|
915
|
+
# Use custom tracking domain (if available)
|
|
916
|
+
# Contact Cakemail support to set up
|
|
917
|
+
|
|
918
|
+
# Use only UTM parameters without Cakemail tracking
|
|
919
|
+
# Manual URL tracking through Google Analytics
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
## Best Practices Summary
|
|
923
|
+
|
|
924
|
+
1. **Enable click tracking** - Track all campaign links for insights
|
|
925
|
+
2. **Use clear CTAs** - "Buy Now" not "Click Here"
|
|
926
|
+
3. **Limit link count** - 2-3 primary links ideal
|
|
927
|
+
4. **Add UTM parameters** - Track in Google Analytics
|
|
928
|
+
5. **Test all links** - Verify before sending
|
|
929
|
+
6. **Place primary CTA early** - Above the fold
|
|
930
|
+
7. **Use buttons for main CTAs** - Higher visibility than text
|
|
931
|
+
8. **Monitor zero-click links** - Remove or improve underperforming links
|
|
932
|
+
9. **Include required links** - Unsubscribe, view in browser
|
|
933
|
+
10. **Validate destinations** - Ensure all URLs work
|
|
934
|
+
|