@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,672 @@
1
+ # Scripting Patterns & Best Practices
2
+
3
+ Advanced patterns and techniques for building robust automation scripts with the Cakemail CLI.
4
+
5
+ ## Overview
6
+
7
+ Learn to:
8
+ - Build reliable automation scripts
9
+ - Handle errors gracefully
10
+ - Implement retry logic
11
+ - Process data efficiently
12
+ - Create reusable functions
13
+ - Follow best practices
14
+
15
+ ## Basic Script Structure
16
+
17
+ ### Complete Script Template
18
+
19
+ ```bash
20
+ #!/bin/bash
21
+
22
+ # Script: campaign-automation.sh
23
+ # Description: Automated campaign workflow
24
+ # Usage: ./campaign-automation.sh <list-id>
25
+
26
+ set -euo pipefail # Exit on error, undefined vars, pipe failures
27
+
28
+ # Configuration
29
+ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ readonly LOG_FILE="${SCRIPT_DIR}/automation.log"
31
+ readonly ERROR_EMAIL="admin@company.com"
32
+
33
+ # Functions
34
+ log() {
35
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
36
+ }
37
+
38
+ error_exit() {
39
+ log "ERROR: $1"
40
+ echo "Script failed: $1" | mail -s "Automation Error" "$ERROR_EMAIL"
41
+ exit 1
42
+ }
43
+
44
+ # Main script
45
+ main() {
46
+ local list_id="$1"
47
+
48
+ log "Starting automation for list $list_id"
49
+
50
+ # Your logic here
51
+ if ! cakemail lists get "$list_id" &>/dev/null; then
52
+ error_exit "List $list_id not found"
53
+ fi
54
+
55
+ log "Automation completed successfully"
56
+ }
57
+
58
+ # Validate arguments
59
+ if [ $# -ne 1 ]; then
60
+ echo "Usage: $0 <list-id>"
61
+ exit 1
62
+ fi
63
+
64
+ main "$@"
65
+ ```
66
+
67
+ ## Error Handling Patterns
68
+
69
+ ### Retry Logic
70
+
71
+ ```bash
72
+ #!/bin/bash
73
+
74
+ # Retry function with exponential backoff
75
+ retry() {
76
+ local max_attempts=3
77
+ local attempt=1
78
+ local delay=2
79
+
80
+ while [ $attempt -le $max_attempts ]; do
81
+ if "$@"; then
82
+ return 0
83
+ else
84
+ if [ $attempt -lt $max_attempts ]; then
85
+ log "Attempt $attempt failed. Retrying in ${delay}s..."
86
+ sleep $delay
87
+ delay=$((delay * 2)) # Exponential backoff
88
+ ((attempt++))
89
+ else
90
+ log "All $max_attempts attempts failed"
91
+ return 1
92
+ fi
93
+ fi
94
+ done
95
+ }
96
+
97
+ # Usage
98
+ if retry cakemail campaigns create -n "Test" -l 123 -s 101; then
99
+ log "Campaign created successfully"
100
+ else
101
+ error_exit "Failed to create campaign after retries"
102
+ fi
103
+ ```
104
+
105
+ ### Graceful Degradation
106
+
107
+ ```bash
108
+ #!/bin/bash
109
+
110
+ # Try operation, fallback if it fails
111
+ create_campaign_with_fallback() {
112
+ local list_id="$1"
113
+ local template_id="$2"
114
+ local fallback_template="$3"
115
+
116
+ # Try with preferred template
117
+ if campaign_id=$(cakemail campaigns create \
118
+ -n "Newsletter" \
119
+ -l "$list_id" \
120
+ -s 101 \
121
+ --template "$template_id" \
122
+ -f json 2>/dev/null | jq -r '.id'); then
123
+ echo "$campaign_id"
124
+ return 0
125
+ fi
126
+
127
+ # Fallback to basic template
128
+ log "Primary template failed, using fallback"
129
+ campaign_id=$(cakemail campaigns create \
130
+ -n "Newsletter" \
131
+ -l "$list_id" \
132
+ -s 101 \
133
+ --template "$fallback_template" \
134
+ -f json | jq -r '.id')
135
+ echo "$campaign_id"
136
+ }
137
+ ```
138
+
139
+ ### Cleanup on Exit
140
+
141
+ ```bash
142
+ #!/bin/bash
143
+
144
+ # Temporary files
145
+ temp_files=()
146
+
147
+ cleanup() {
148
+ log "Cleaning up temporary files..."
149
+ for file in "${temp_files[@]}"; do
150
+ [ -f "$file" ] && rm -f "$file"
151
+ done
152
+ }
153
+
154
+ # Register cleanup
155
+ trap cleanup EXIT
156
+
157
+ # Create temp file
158
+ temp_file=$(mktemp)
159
+ temp_files+=("$temp_file")
160
+
161
+ # Use temp file
162
+ cakemail contacts export 123 > "$temp_file"
163
+
164
+ # Cleanup happens automatically on exit
165
+ ```
166
+
167
+ ## Data Processing Patterns
168
+
169
+ ### Batch Processing
170
+
171
+ ```bash
172
+ #!/bin/bash
173
+
174
+ # Process items in batches
175
+ batch_process() {
176
+ local items=("$@")
177
+ local batch_size=10
178
+ local total=${#items[@]}
179
+ local processed=0
180
+
181
+ for ((i=0; i<total; i+=batch_size)); do
182
+ local batch=("${items[@]:i:batch_size}")
183
+
184
+ log "Processing batch $((i/batch_size + 1)) (${#batch[@]} items)"
185
+
186
+ for item in "${batch[@]}"; do
187
+ process_item "$item"
188
+ ((processed++))
189
+ done
190
+
191
+ # Progress update
192
+ local percent=$((processed * 100 / total))
193
+ log "Progress: $processed/$total ($percent%)"
194
+
195
+ # Rate limiting
196
+ sleep 1
197
+ done
198
+ }
199
+
200
+ process_item() {
201
+ local email="$1"
202
+ cakemail contacts add 123 -e "$email"
203
+ }
204
+
205
+ # Read emails from file
206
+ mapfile -t emails < emails.txt
207
+ batch_process "${emails[@]}"
208
+ ```
209
+
210
+ ### Parallel Processing
211
+
212
+ ```bash
213
+ #!/bin/bash
214
+
215
+ # Process items in parallel (with limit)
216
+ parallel_process() {
217
+ local max_jobs=5
218
+ local items=("$@")
219
+
220
+ for item in "${items[@]}"; do
221
+ # Wait if too many jobs running
222
+ while [ $(jobs -r | wc -l) -ge $max_jobs ]; do
223
+ sleep 0.1
224
+ done
225
+
226
+ # Process in background
227
+ process_item "$item" &
228
+ done
229
+
230
+ # Wait for all jobs to complete
231
+ wait
232
+ }
233
+
234
+ process_item() {
235
+ local campaign_id="$1"
236
+ cakemail reports campaign "$campaign_id" -f json > "report-${campaign_id}.json"
237
+ }
238
+
239
+ # Process all campaigns in parallel
240
+ campaigns=(790 791 792 793 794)
241
+ parallel_process "${campaigns[@]}"
242
+ ```
243
+
244
+ ### Streaming Large Datasets
245
+
246
+ ```bash
247
+ #!/bin/bash
248
+
249
+ # Stream process large CSV without loading into memory
250
+ stream_process_csv() {
251
+ local input_file="$1"
252
+ local line_count=0
253
+
254
+ # Skip header
255
+ tail -n +2 "$input_file" | while IFS=, read -r email first_name last_name; do
256
+ # Process each line
257
+ cakemail contacts add 123 -e "$email" -f "$first_name" -l "$last_name"
258
+
259
+ ((line_count++))
260
+ if [ $((line_count % 100)) -eq 0 ]; then
261
+ log "Processed $line_count contacts"
262
+ fi
263
+ done
264
+
265
+ log "Total processed: $line_count contacts"
266
+ }
267
+
268
+ stream_process_csv large-contact-list.csv
269
+ ```
270
+
271
+ ## Reusable Functions
272
+
273
+ ### Configuration Loading
274
+
275
+ ```bash
276
+ #!/bin/bash
277
+
278
+ # Load configuration from file
279
+ load_config() {
280
+ local config_file="${1:-.cakemail.conf}"
281
+
282
+ if [ ! -f "$config_file" ]; then
283
+ error_exit "Config file not found: $config_file"
284
+ fi
285
+
286
+ # Source config file
287
+ # shellcheck source=/dev/null
288
+ source "$config_file"
289
+
290
+ # Validate required variables
291
+ : "${LIST_ID:?LIST_ID not set in config}"
292
+ : "${SENDER_ID:?SENDER_ID not set in config}"
293
+ }
294
+
295
+ # Config file: .cakemail.conf
296
+ # LIST_ID=123
297
+ # SENDER_ID=101
298
+ # TEMPLATE_ID=201
299
+
300
+ load_config
301
+ ```
302
+
303
+ ### JSON Parsing
304
+
305
+ ```bash
306
+ #!/bin/bash
307
+
308
+ # Extract values from JSON responses
309
+ get_json_value() {
310
+ local json="$1"
311
+ local key="$2"
312
+ echo "$json" | jq -r ".$key"
313
+ }
314
+
315
+ # Get multiple values
316
+ parse_campaign_response() {
317
+ local response="$1"
318
+
319
+ campaign_id=$(get_json_value "$response" "id")
320
+ campaign_name=$(get_json_value "$response" "name")
321
+ campaign_status=$(get_json_value "$response" "status")
322
+
323
+ echo "ID: $campaign_id, Name: $campaign_name, Status: $campaign_status"
324
+ }
325
+
326
+ # Usage
327
+ response=$(cakemail campaigns get 790 -f json)
328
+ parse_campaign_response "$response"
329
+ ```
330
+
331
+ ### Date Handling
332
+
333
+ ```bash
334
+ #!/bin/bash
335
+
336
+ # Calculate dates for filtering
337
+ get_date_range() {
338
+ local days_ago="$1"
339
+
340
+ # Start date (N days ago)
341
+ start_date=$(date -d "$days_ago days ago" +%Y-%m-%d)
342
+
343
+ # End date (today)
344
+ end_date=$(date +%Y-%m-%d)
345
+
346
+ echo "$start_date $end_date"
347
+ }
348
+
349
+ # Get campaigns from last 30 days
350
+ read -r start end <<< "$(get_date_range 30)"
351
+ cakemail campaigns list --filter "delivered_at>=$start;delivered_at<=$end"
352
+ ```
353
+
354
+ ### Progress Indicators
355
+
356
+ ```bash
357
+ #!/bin/bash
358
+
359
+ # Show progress bar
360
+ show_progress() {
361
+ local current="$1"
362
+ local total="$2"
363
+ local width=50
364
+
365
+ local percent=$((current * 100 / total))
366
+ local filled=$((width * current / total))
367
+ local empty=$((width - filled))
368
+
369
+ printf "\rProgress: [%${filled}s%${empty}s] %d%%" | tr ' ' '='
370
+ printf "%${empty}s %d/%d" "" "$current" "$total"
371
+
372
+ if [ "$current" -eq "$total" ]; then
373
+ echo ""
374
+ fi
375
+ }
376
+
377
+ # Usage
378
+ total=100
379
+ for i in $(seq 1 $total); do
380
+ # Process item
381
+ sleep 0.1
382
+ show_progress $i $total
383
+ done
384
+ ```
385
+
386
+ ## Validation Patterns
387
+
388
+ ### Input Validation
389
+
390
+ ```bash
391
+ #!/bin/bash
392
+
393
+ # Validate email format
394
+ validate_email() {
395
+ local email="$1"
396
+ local regex='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
397
+
398
+ if [[ ! $email =~ $regex ]]; then
399
+ return 1
400
+ fi
401
+ return 0
402
+ }
403
+
404
+ # Validate required parameters
405
+ validate_params() {
406
+ local list_id="$1"
407
+ local sender_id="$2"
408
+
409
+ if [ -z "$list_id" ]; then
410
+ error_exit "List ID is required"
411
+ fi
412
+
413
+ if ! [[ $list_id =~ ^[0-9]+$ ]]; then
414
+ error_exit "List ID must be numeric"
415
+ fi
416
+
417
+ # Verify list exists
418
+ if ! cakemail lists get "$list_id" &>/dev/null; then
419
+ error_exit "List $list_id not found"
420
+ fi
421
+ }
422
+ ```
423
+
424
+ ### Pre-flight Checks
425
+
426
+ ```bash
427
+ #!/bin/bash
428
+
429
+ # Run pre-flight checks before main operations
430
+ preflight_checks() {
431
+ log "Running pre-flight checks..."
432
+
433
+ # Check CLI is installed
434
+ if ! command -v cakemail &>/dev/null; then
435
+ error_exit "Cakemail CLI not found. Install with: npm install -g @cakemail-org/cakemail-cli"
436
+ fi
437
+
438
+ # Check authentication
439
+ if ! cakemail account test &>/dev/null; then
440
+ error_exit "Authentication failed. Run: cakemail config init"
441
+ fi
442
+
443
+ # Check dependencies
444
+ for cmd in jq curl; do
445
+ if ! command -v $cmd &>/dev/null; then
446
+ error_exit "Required command not found: $cmd"
447
+ fi
448
+ done
449
+
450
+ # Check disk space
451
+ local available=$(df -BG . | tail -1 | awk '{print $4}' | tr -d 'G')
452
+ if [ "$available" -lt 1 ]; then
453
+ error_exit "Insufficient disk space (need at least 1GB)"
454
+ fi
455
+
456
+ log "All pre-flight checks passed"
457
+ }
458
+ ```
459
+
460
+ ## Logging Patterns
461
+
462
+ ### Structured Logging
463
+
464
+ ```bash
465
+ #!/bin/bash
466
+
467
+ # Log levels
468
+ readonly LOG_LEVEL_DEBUG=0
469
+ readonly LOG_LEVEL_INFO=1
470
+ readonly LOG_LEVEL_WARN=2
471
+ readonly LOG_LEVEL_ERROR=3
472
+
473
+ # Current log level
474
+ LOG_LEVEL=${LOG_LEVEL:-$LOG_LEVEL_INFO}
475
+
476
+ log_debug() { [ $LOG_LEVEL -le $LOG_LEVEL_DEBUG ] && log "DEBUG: $*"; }
477
+ log_info() { [ $LOG_LEVEL -le $LOG_LEVEL_INFO ] && log "INFO: $*"; }
478
+ log_warn() { [ $LOG_LEVEL -le $LOG_LEVEL_WARN ] && log "WARN: $*"; }
479
+ log_error() { [ $LOG_LEVEL -le $LOG_LEVEL_ERROR ] && log "ERROR: $*"; }
480
+
481
+ # Usage
482
+ LOG_LEVEL=$LOG_LEVEL_DEBUG # Show all logs
483
+ log_debug "Debug message"
484
+ log_info "Info message"
485
+ log_warn "Warning message"
486
+ log_error "Error message"
487
+ ```
488
+
489
+ ### Log Rotation
490
+
491
+ ```bash
492
+ #!/bin/bash
493
+
494
+ # Rotate log file if too large
495
+ rotate_log() {
496
+ local log_file="$1"
497
+ local max_size=$((10 * 1024 * 1024)) # 10MB
498
+
499
+ if [ -f "$log_file" ]; then
500
+ local size=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null)
501
+
502
+ if [ "$size" -gt "$max_size" ]; then
503
+ local timestamp=$(date +%Y%m%d-%H%M%S)
504
+ mv "$log_file" "${log_file}.${timestamp}"
505
+ log "Log rotated: ${log_file}.${timestamp}"
506
+ fi
507
+ fi
508
+ }
509
+
510
+ # Rotate before starting
511
+ rotate_log "$LOG_FILE"
512
+ ```
513
+
514
+ ## Testing Patterns
515
+
516
+ ### Dry Run Mode
517
+
518
+ ```bash
519
+ #!/bin/bash
520
+
521
+ # Support dry-run mode
522
+ DRY_RUN=${DRY_RUN:-false}
523
+
524
+ execute() {
525
+ local command="$*"
526
+
527
+ if [ "$DRY_RUN" = true ]; then
528
+ log "[DRY RUN] Would execute: $command"
529
+ return 0
530
+ else
531
+ log "Executing: $command"
532
+ eval "$command"
533
+ fi
534
+ }
535
+
536
+ # Usage
537
+ execute cakemail campaigns create -n "Test" -l 123 -s 101
538
+
539
+ # Run in dry-run mode
540
+ DRY_RUN=true ./script.sh
541
+ ```
542
+
543
+ ### Mock Responses
544
+
545
+ ```bash
546
+ #!/bin/bash
547
+
548
+ # Mock mode for testing
549
+ MOCK_MODE=${MOCK_MODE:-false}
550
+
551
+ cakemail_wrapper() {
552
+ if [ "$MOCK_MODE" = true ]; then
553
+ # Return mock data
554
+ case "$1" in
555
+ campaigns)
556
+ echo '{"id": 999, "name": "Mock Campaign", "status": "draft"}'
557
+ ;;
558
+ lists)
559
+ echo '{"id": 123, "name": "Mock List", "contacts_count": 100}'
560
+ ;;
561
+ *)
562
+ echo '{"success": true}'
563
+ ;;
564
+ esac
565
+ else
566
+ # Execute real command
567
+ cakemail "$@"
568
+ fi
569
+ }
570
+
571
+ # Use wrapper instead of direct calls
572
+ response=$(cakemail_wrapper campaigns get 790 -f json)
573
+ ```
574
+
575
+ ## Performance Patterns
576
+
577
+ ### Caching Results
578
+
579
+ ```bash
580
+ #!/bin/bash
581
+
582
+ # Cache results to avoid redundant API calls
583
+ CACHE_DIR=".cache"
584
+ mkdir -p "$CACHE_DIR"
585
+
586
+ cached_call() {
587
+ local cache_key="$1"
588
+ shift
589
+ local command="$*"
590
+ local cache_file="$CACHE_DIR/$cache_key"
591
+ local cache_ttl=3600 # 1 hour
592
+
593
+ # Check if cached and not expired
594
+ if [ -f "$cache_file" ]; then
595
+ local age=$(($(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file")))
596
+ if [ "$age" -lt "$cache_ttl" ]; then
597
+ cat "$cache_file"
598
+ return 0
599
+ fi
600
+ fi
601
+
602
+ # Execute and cache
603
+ eval "$command" | tee "$cache_file"
604
+ }
605
+
606
+ # Usage - list will be cached for 1 hour
607
+ lists=$(cached_call "lists" cakemail lists list -f json)
608
+ ```
609
+
610
+ ### Rate Limiting
611
+
612
+ ```bash
613
+ #!/bin/bash
614
+
615
+ # Rate limiter using token bucket algorithm
616
+ declare -A rate_limit_tokens
617
+ declare -A rate_limit_last_refill
618
+
619
+ rate_limit() {
620
+ local key="${1:-default}"
621
+ local max_tokens=10
622
+ local refill_rate=1 # tokens per second
623
+ local current_time=$(date +%s)
624
+
625
+ # Initialize if needed
626
+ if [ -z "${rate_limit_tokens[$key]}" ]; then
627
+ rate_limit_tokens[$key]=$max_tokens
628
+ rate_limit_last_refill[$key]=$current_time
629
+ fi
630
+
631
+ # Refill tokens
632
+ local elapsed=$((current_time - rate_limit_last_refill[$key]))
633
+ local new_tokens=$((elapsed * refill_rate))
634
+ if [ $new_tokens -gt 0 ]; then
635
+ rate_limit_tokens[$key]=$((rate_limit_tokens[$key] + new_tokens))
636
+ if [ ${rate_limit_tokens[$key]} -gt $max_tokens ]; then
637
+ rate_limit_tokens[$key]=$max_tokens
638
+ fi
639
+ rate_limit_last_refill[$key]=$current_time
640
+ fi
641
+
642
+ # Check if token available
643
+ if [ ${rate_limit_tokens[$key]} -gt 0 ]; then
644
+ rate_limit_tokens[$key]=$((rate_limit_tokens[$key] - 1))
645
+ return 0
646
+ else
647
+ # Wait for token
648
+ sleep 1
649
+ rate_limit "$key"
650
+ fi
651
+ }
652
+
653
+ # Usage
654
+ for i in {1..20}; do
655
+ rate_limit "api_calls"
656
+ cakemail campaigns list
657
+ done
658
+ ```
659
+
660
+ ## Best Practices
661
+
662
+ 1. **Use strict mode**: `set -euo pipefail`
663
+ 2. **Validate all inputs**: Check before processing
664
+ 3. **Handle errors gracefully**: Don't fail silently
665
+ 4. **Log everything**: Comprehensive logging
666
+ 5. **Use functions**: Modular, reusable code
667
+ 6. **Quote variables**: Prevent word splitting
668
+ 7. **Check exit codes**: Test command success
669
+ 8. **Clean up resources**: Temp files, background jobs
670
+ 9. **Document scripts**: Clear comments
671
+ 10. **Test thoroughly**: Dry-run and mock modes
672
+