@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.
Files changed (198) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +40 -0
  3. package/.env.test.example +45 -0
  4. package/CHANGELOG.md +1031 -0
  5. package/README.md +41 -37
  6. package/audit-formats.js +128 -0
  7. package/cakemail.rb +20 -0
  8. package/dist/client.js +1 -1
  9. package/dist/client.js.map +1 -1
  10. package/dist/commands/account.js +1 -1
  11. package/dist/commands/account.js.map +1 -1
  12. package/dist/commands/attributes.js +1 -1
  13. package/dist/commands/attributes.js.map +1 -1
  14. package/dist/commands/campaigns.js +1 -1
  15. package/dist/commands/campaigns.js.map +1 -1
  16. package/dist/commands/contacts.js +1 -1
  17. package/dist/commands/contacts.js.map +1 -1
  18. package/dist/commands/emails.js +1 -1
  19. package/dist/commands/emails.js.map +1 -1
  20. package/dist/commands/interests.js +1 -1
  21. package/dist/commands/interests.js.map +1 -1
  22. package/dist/commands/lists.js +1 -1
  23. package/dist/commands/lists.js.map +1 -1
  24. package/dist/commands/logs.js +1 -1
  25. package/dist/commands/logs.js.map +1 -1
  26. package/dist/commands/reports.js +1 -1
  27. package/dist/commands/reports.js.map +1 -1
  28. package/dist/commands/segments.js +1 -1
  29. package/dist/commands/segments.js.map +1 -1
  30. package/dist/commands/senders.js +1 -1
  31. package/dist/commands/senders.js.map +1 -1
  32. package/dist/commands/suppressed.js +1 -1
  33. package/dist/commands/suppressed.js.map +1 -1
  34. package/dist/commands/tags.js +1 -1
  35. package/dist/commands/tags.js.map +1 -1
  36. package/dist/commands/templates.js +1 -1
  37. package/dist/commands/templates.js.map +1 -1
  38. package/dist/commands/transactional-templates.js +1 -1
  39. package/dist/commands/transactional-templates.js.map +1 -1
  40. package/dist/commands/webhooks.js +1 -1
  41. package/dist/commands/webhooks.js.map +1 -1
  42. package/dist/utils/config.js +2 -2
  43. package/dist/utils/config.js.map +1 -1
  44. package/dist/utils/errors.js +1 -1
  45. package/dist/utils/errors.js.map +1 -1
  46. package/dist/utils/progress.d.ts.map +1 -1
  47. package/dist/utils/progress.js +32 -4
  48. package/dist/utils/progress.js.map +1 -1
  49. package/dist/utils/spinner.d.ts +17 -0
  50. package/dist/utils/spinner.d.ts.map +1 -0
  51. package/dist/utils/spinner.js +43 -0
  52. package/dist/utils/spinner.js.map +1 -0
  53. package/docs/DOCUMENTATION-STANDARD.md +1068 -0
  54. package/docs/README.md +161 -0
  55. package/docs/developer/ARCHITECTURE.md +516 -0
  56. package/docs/developer/AUTH.md +204 -0
  57. package/docs/developer/CONTRIBUTING.md +227 -0
  58. package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
  59. package/docs/developer/PROJECT_INDEX.md +365 -0
  60. package/docs/planning/API_COVERAGE.md +1045 -0
  61. package/docs/planning/BACKLOG.md +1159 -0
  62. package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
  63. package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
  64. package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
  65. package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
  66. package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
  67. package/docs/planning/cakemail-profile-system-plan.md +1121 -0
  68. package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
  69. package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
  70. package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
  71. package/docs/user-manual/.obsidian/app.json +1 -0
  72. package/docs/user-manual/.obsidian/appearance.json +1 -0
  73. package/docs/user-manual/.obsidian/core-plugins.json +33 -0
  74. package/docs/user-manual/.obsidian/workspace.json +167 -0
  75. package/docs/user-manual/01-getting-started/01-installation.md +214 -0
  76. package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
  77. package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
  78. package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
  79. package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
  80. package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
  81. package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
  82. package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
  83. package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
  84. package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
  85. package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
  86. package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
  87. package/docs/user-manual/03-email-operations/01-senders.md +490 -0
  88. package/docs/user-manual/03-email-operations/02-templates.md +444 -0
  89. package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
  90. package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
  91. package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
  92. package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
  93. package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
  94. package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
  95. package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
  96. package/docs/user-manual/05-contact-management/01-lists.md +836 -0
  97. package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
  98. package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
  99. package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
  100. package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
  101. package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
  102. package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
  103. package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
  104. package/docs/user-manual/07-integrations/02-automation.md +326 -0
  105. package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
  106. package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
  107. package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
  108. package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
  109. package/docs/user-manual/09-command-reference/01-config.md +776 -0
  110. package/docs/user-manual/09-command-reference/02-account.md +652 -0
  111. package/docs/user-manual/09-command-reference/03-lists.md +958 -0
  112. package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
  113. package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
  114. package/docs/user-manual/09-command-reference/06-segments.md +894 -0
  115. package/docs/user-manual/09-command-reference/07-senders.md +803 -0
  116. package/docs/user-manual/09-command-reference/08-templates.md +818 -0
  117. package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
  118. package/docs/user-manual/09-command-reference/10-emails.md +807 -0
  119. package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
  120. package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
  121. package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
  122. package/docs/user-manual/09-command-reference/14-interests.md +630 -0
  123. package/docs/user-manual/09-command-reference/15-tags.md +584 -0
  124. package/docs/user-manual/09-command-reference/16-logs.md +656 -0
  125. package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
  126. package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
  127. package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
  128. package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
  129. package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
  130. package/docs/user-manual/11-appendix/04-faq.md +484 -0
  131. package/docs/user-manual/11-appendix/05-glossary.md +250 -0
  132. package/docs/user-manual/README.md +0 -0
  133. package/package.json +13 -61
  134. package/src/cli.ts +125 -0
  135. package/src/client.ts +16 -0
  136. package/src/commands/account.ts +267 -0
  137. package/src/commands/accounts.ts +78 -0
  138. package/src/commands/actions.ts +249 -0
  139. package/src/commands/attributes.ts +139 -0
  140. package/src/commands/campaign-blueprints.ts +106 -0
  141. package/src/commands/campaigns.ts +469 -0
  142. package/src/commands/config.ts +77 -0
  143. package/src/commands/contacts.ts +612 -0
  144. package/src/commands/custom-attributes.ts +127 -0
  145. package/src/commands/dkims.ts +117 -0
  146. package/src/commands/domains.ts +82 -0
  147. package/src/commands/email-apis.ts +569 -0
  148. package/src/commands/emails.ts +197 -0
  149. package/src/commands/forms.ts +283 -0
  150. package/src/commands/interests.ts +155 -0
  151. package/src/commands/links.ts +38 -0
  152. package/src/commands/lists.ts +406 -0
  153. package/src/commands/logos.ts +71 -0
  154. package/src/commands/logs.ts +386 -0
  155. package/src/commands/reports.ts +306 -0
  156. package/src/commands/segments.ts +158 -0
  157. package/src/commands/senders.ts +204 -0
  158. package/src/commands/sub-accounts.ts +271 -0
  159. package/src/commands/suppressed-emails.ts +234 -0
  160. package/src/commands/suppressed.ts +198 -0
  161. package/src/commands/system-emails.ts +85 -0
  162. package/src/commands/tags.ts +146 -0
  163. package/src/commands/tasks.ts +116 -0
  164. package/src/commands/templates.ts +189 -0
  165. package/src/commands/tokens.ts +83 -0
  166. package/src/commands/transactional-emails.ts +374 -0
  167. package/src/commands/transactional-templates.ts +385 -0
  168. package/src/commands/users.ts +506 -0
  169. package/src/commands/webhooks.ts +172 -0
  170. package/src/commands/workflow-blueprints.ts +123 -0
  171. package/src/commands/workflows.ts +265 -0
  172. package/src/types/profile.ts +93 -0
  173. package/src/utils/auth.ts +272 -0
  174. package/src/utils/config-file.ts +96 -0
  175. package/src/utils/config.ts +134 -0
  176. package/src/utils/confirm.ts +32 -0
  177. package/src/utils/defaults.ts +99 -0
  178. package/src/utils/errors.ts +116 -0
  179. package/src/utils/interactive.ts +91 -0
  180. package/src/utils/list-defaults.ts +74 -0
  181. package/src/utils/output.ts +190 -0
  182. package/src/utils/progress.ts +320 -0
  183. package/src/utils/spinner.ts +22 -0
  184. package/tests/IMPLEMENTATION_STATUS.md +258 -0
  185. package/tests/PTY_SETUP.md +118 -0
  186. package/tests/PTY_TESTING_GUIDE.md +507 -0
  187. package/tests/README.md +244 -0
  188. package/tests/fixtures/api-responses/campaigns.json +34 -0
  189. package/tests/fixtures/test-config.json +13 -0
  190. package/tests/helpers/cli-runner.ts +128 -0
  191. package/tests/helpers/mock-server.ts +301 -0
  192. package/tests/helpers/pty-runner.ts +181 -0
  193. package/tests/integration/campaigns-real-api.test.ts +196 -0
  194. package/tests/integration/setup-integration.ts +50 -0
  195. package/tests/pty/campaigns.test.ts +241 -0
  196. package/tests/setup.ts +34 -0
  197. package/tsconfig.json +15 -0
  198. 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
+