n8n-nodes-cakemail 1.0.2 → 1.0.3

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.
@@ -0,0 +1,1528 @@
1
+ import {
2
+ IExecuteFunctions,
3
+ INodeExecutionData,
4
+ INodeType,
5
+ INodeTypeDescription,
6
+ NodeOperationError,
7
+ } from 'n8n-workflow';
8
+
9
+ import { CakemailClient, CakemailError } from 'cakemail-sdk';
10
+
11
+ /**
12
+ * Cakemail n8n node
13
+ */
14
+ export class Cakemail implements INodeType {
15
+ description: INodeTypeDescription = {
16
+ displayName: 'Cakemail',
17
+ name: 'cakemail',
18
+ icon: 'file:cakemail.svg',
19
+ group: ['transform'],
20
+ version: 1,
21
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
22
+ description: 'Interact with Cakemail API',
23
+ defaults: {
24
+ name: 'Cakemail',
25
+ },
26
+ inputs: ['main'],
27
+ outputs: ['main'],
28
+ credentials: [
29
+ {
30
+ name: 'cakemailApi',
31
+ required: true,
32
+ },
33
+ ],
34
+ properties: [
35
+ // Resource selector
36
+ {
37
+ displayName: 'Resource',
38
+ name: 'resource',
39
+ type: 'options',
40
+ noDataExpression: true,
41
+ options: [
42
+ {
43
+ name: 'Account',
44
+ value: 'account',
45
+ },
46
+ {
47
+ name: 'Contact',
48
+ value: 'contact',
49
+ },
50
+ {
51
+ name: 'List',
52
+ value: 'list',
53
+ },
54
+ {
55
+ name: 'Campaign',
56
+ value: 'campaign',
57
+ },
58
+ ],
59
+ default: 'account',
60
+ required: true,
61
+ description: 'The resource to operate on',
62
+ },
63
+
64
+ // Account operations
65
+ {
66
+ displayName: 'Operation',
67
+ name: 'operation',
68
+ type: 'options',
69
+ noDataExpression: true,
70
+ displayOptions: {
71
+ show: {
72
+ resource: ['account'],
73
+ },
74
+ },
75
+ options: [
76
+ {
77
+ name: 'Get',
78
+ value: 'get',
79
+ description: 'Get current account information',
80
+ action: 'Get current account',
81
+ },
82
+ {
83
+ name: 'Get By ID',
84
+ value: 'getById',
85
+ description: 'Get account by ID',
86
+ action: 'Get account by ID',
87
+ },
88
+ {
89
+ name: 'List',
90
+ value: 'list',
91
+ description: 'List accounts',
92
+ action: 'List accounts',
93
+ },
94
+ {
95
+ name: 'Create',
96
+ value: 'create',
97
+ description: 'Create a new account',
98
+ action: 'Create account',
99
+ },
100
+ {
101
+ name: 'Update',
102
+ value: 'update',
103
+ description: 'Update an account',
104
+ action: 'Update account',
105
+ },
106
+ {
107
+ name: 'Delete',
108
+ value: 'delete',
109
+ description: 'Delete an account',
110
+ action: 'Delete account',
111
+ },
112
+ {
113
+ name: 'Get Children',
114
+ value: 'getChildren',
115
+ description: 'Get child accounts of a parent account',
116
+ action: 'Get child accounts',
117
+ },
118
+ ],
119
+ default: 'get',
120
+ required: true,
121
+ },
122
+
123
+ // Contact operations
124
+ {
125
+ displayName: 'Operation',
126
+ name: 'operation',
127
+ type: 'options',
128
+ noDataExpression: true,
129
+ displayOptions: {
130
+ show: {
131
+ resource: ['contact'],
132
+ },
133
+ },
134
+ options: [
135
+ {
136
+ name: 'Get',
137
+ value: 'get',
138
+ description: 'Get a contact by ID',
139
+ action: 'Get contact',
140
+ },
141
+ {
142
+ name: 'Get By Email',
143
+ value: 'getByEmail',
144
+ description: 'Get a contact by email',
145
+ action: 'Get contact by email',
146
+ },
147
+ {
148
+ name: 'List',
149
+ value: 'list',
150
+ description: 'List contacts',
151
+ action: 'List contacts',
152
+ },
153
+ {
154
+ name: 'Create',
155
+ value: 'create',
156
+ description: 'Create a new contact',
157
+ action: 'Create contact',
158
+ },
159
+ {
160
+ name: 'Update',
161
+ value: 'update',
162
+ description: 'Update a contact',
163
+ action: 'Update contact',
164
+ },
165
+ {
166
+ name: 'Delete',
167
+ value: 'delete',
168
+ description: 'Delete a contact',
169
+ action: 'Delete contact',
170
+ },
171
+ {
172
+ name: 'Subscribe',
173
+ value: 'subscribe',
174
+ description: 'Subscribe a contact to a list',
175
+ action: 'Subscribe contact to list',
176
+ },
177
+ {
178
+ name: 'Unsubscribe',
179
+ value: 'unsubscribe',
180
+ description: 'Unsubscribe a contact from a list',
181
+ action: 'Unsubscribe contact from list',
182
+ },
183
+ {
184
+ name: 'Bulk Import',
185
+ value: 'bulkImport',
186
+ description: 'Import multiple contacts at once',
187
+ action: 'Bulk import contacts',
188
+ },
189
+ {
190
+ name: 'Search',
191
+ value: 'search',
192
+ description: 'Advanced search with custom field filtering',
193
+ action: 'Search contacts',
194
+ },
195
+ {
196
+ name: 'Add Tags',
197
+ value: 'addTags',
198
+ description: 'Add tags to a contact',
199
+ action: 'Add tags to contact',
200
+ },
201
+ {
202
+ name: 'Remove Tags',
203
+ value: 'removeTags',
204
+ description: 'Remove tags from a contact',
205
+ action: 'Remove tags from contact',
206
+ },
207
+ ],
208
+ default: 'get',
209
+ required: true,
210
+ },
211
+
212
+ // List operations
213
+ {
214
+ displayName: 'Operation',
215
+ name: 'operation',
216
+ type: 'options',
217
+ noDataExpression: true,
218
+ displayOptions: {
219
+ show: {
220
+ resource: ['list'],
221
+ },
222
+ },
223
+ options: [
224
+ {
225
+ name: 'Get',
226
+ value: 'get',
227
+ description: 'Get a list by ID',
228
+ action: 'Get list',
229
+ },
230
+ {
231
+ name: 'List',
232
+ value: 'list',
233
+ description: 'List all lists',
234
+ action: 'List lists',
235
+ },
236
+ {
237
+ name: 'Create',
238
+ value: 'create',
239
+ description: 'Create a new list',
240
+ action: 'Create list',
241
+ },
242
+ {
243
+ name: 'Update',
244
+ value: 'update',
245
+ description: 'Update a list',
246
+ action: 'Update list',
247
+ },
248
+ {
249
+ name: 'Delete',
250
+ value: 'delete',
251
+ description: 'Delete a list',
252
+ action: 'Delete list',
253
+ },
254
+ {
255
+ name: 'Get Statistics',
256
+ value: 'getStatistics',
257
+ description: 'Get list statistics',
258
+ action: 'Get list statistics',
259
+ },
260
+ ],
261
+ default: 'get',
262
+ required: true,
263
+ },
264
+
265
+ // Campaign operations
266
+ {
267
+ displayName: 'Operation',
268
+ name: 'operation',
269
+ type: 'options',
270
+ noDataExpression: true,
271
+ displayOptions: {
272
+ show: {
273
+ resource: ['campaign'],
274
+ },
275
+ },
276
+ options: [
277
+ {
278
+ name: 'Get',
279
+ value: 'get',
280
+ description: 'Get a campaign by ID',
281
+ action: 'Get campaign',
282
+ },
283
+ {
284
+ name: 'List',
285
+ value: 'list',
286
+ description: 'List campaigns',
287
+ action: 'List campaigns',
288
+ },
289
+ {
290
+ name: 'Create',
291
+ value: 'create',
292
+ description: 'Create a new campaign',
293
+ action: 'Create campaign',
294
+ },
295
+ {
296
+ name: 'Update',
297
+ value: 'update',
298
+ description: 'Update a campaign',
299
+ action: 'Update campaign',
300
+ },
301
+ {
302
+ name: 'Delete',
303
+ value: 'delete',
304
+ description: 'Delete a campaign',
305
+ action: 'Delete campaign',
306
+ },
307
+ {
308
+ name: 'Send',
309
+ value: 'send',
310
+ description: 'Send a campaign',
311
+ action: 'Send campaign',
312
+ },
313
+ ],
314
+ default: 'get',
315
+ required: true,
316
+ },
317
+
318
+ // Multi-tenant account ID (optional for all operations)
319
+ {
320
+ displayName: 'Account ID',
321
+ name: 'accountId',
322
+ type: 'number',
323
+ default: undefined,
324
+ required: false,
325
+ description:
326
+ 'Optional: Specify an account ID for multi-tenant operations. If not provided, uses the authenticated account.',
327
+ },
328
+
329
+ // ================== Account Operation Parameters ==================
330
+
331
+ // Target Account ID (for getById, update, delete, getChildren account operations)
332
+ {
333
+ displayName: 'Target Account ID',
334
+ name: 'targetAccountId',
335
+ type: 'number',
336
+ required: true,
337
+ displayOptions: {
338
+ show: {
339
+ resource: ['account'],
340
+ operation: ['getById', 'update', 'delete', 'getChildren'],
341
+ },
342
+ },
343
+ default: 0,
344
+ description: 'The ID of the account to operate on',
345
+ },
346
+
347
+ // Account name (for create/update)
348
+ {
349
+ displayName: 'Name',
350
+ name: 'accountName',
351
+ type: 'string',
352
+ required: true,
353
+ displayOptions: {
354
+ show: {
355
+ resource: ['account'],
356
+ operation: ['create'],
357
+ },
358
+ },
359
+ default: '',
360
+ description: 'Account name',
361
+ },
362
+
363
+ // Account email (for create)
364
+ {
365
+ displayName: 'Email',
366
+ name: 'accountEmail',
367
+ type: 'string',
368
+ required: true,
369
+ displayOptions: {
370
+ show: {
371
+ resource: ['account'],
372
+ operation: ['create'],
373
+ },
374
+ },
375
+ default: '',
376
+ description: 'Primary email for the account',
377
+ },
378
+
379
+ // Account additional fields (for create/update)
380
+ {
381
+ displayName: 'Additional Fields',
382
+ name: 'accountAdditionalFields',
383
+ type: 'collection',
384
+ placeholder: 'Add Field',
385
+ default: {},
386
+ displayOptions: {
387
+ show: {
388
+ resource: ['account'],
389
+ operation: ['create', 'update'],
390
+ },
391
+ },
392
+ options: [
393
+ {
394
+ displayName: 'Name',
395
+ name: 'name',
396
+ type: 'string',
397
+ default: '',
398
+ description: 'Account name (for update)',
399
+ },
400
+ {
401
+ displayName: 'Primary Email',
402
+ name: 'primary_email',
403
+ type: 'string',
404
+ default: '',
405
+ description: 'Primary email (for update)',
406
+ },
407
+ {
408
+ displayName: 'Account Type',
409
+ name: 'account_type',
410
+ type: 'options',
411
+ options: [
412
+ { name: 'Parent', value: 'parent' },
413
+ { name: 'Child', value: 'child' },
414
+ { name: 'Standalone', value: 'standalone' },
415
+ ],
416
+ default: 'standalone',
417
+ description: 'Type of account',
418
+ },
419
+ {
420
+ displayName: 'Parent Account ID',
421
+ name: 'parent_account_id',
422
+ type: 'number',
423
+ default: 0,
424
+ description: 'Parent account ID (for child accounts)',
425
+ },
426
+ {
427
+ displayName: 'Status',
428
+ name: 'status',
429
+ type: 'options',
430
+ options: [
431
+ { name: 'Active', value: 'active' },
432
+ { name: 'Suspended', value: 'suspended' },
433
+ { name: 'Deleted', value: 'deleted' },
434
+ ],
435
+ default: 'active',
436
+ description: 'Account status (for update)',
437
+ },
438
+ {
439
+ displayName: 'Language',
440
+ name: 'language',
441
+ type: 'string',
442
+ default: 'en',
443
+ description: 'Account language (e.g., en, fr)',
444
+ },
445
+ {
446
+ displayName: 'Timezone',
447
+ name: 'timezone',
448
+ type: 'string',
449
+ default: 'America/New_York',
450
+ description: 'Account timezone',
451
+ },
452
+ {
453
+ displayName: 'Currency',
454
+ name: 'currency',
455
+ type: 'string',
456
+ default: 'USD',
457
+ description: 'Account currency',
458
+ },
459
+ {
460
+ displayName: 'Settings',
461
+ name: 'settings',
462
+ type: 'json',
463
+ default: '{}',
464
+ description: 'Account settings as JSON object',
465
+ },
466
+ ],
467
+ },
468
+
469
+ // Get Children pagination options
470
+ {
471
+ displayName: 'Options',
472
+ name: 'getChildrenOptions',
473
+ type: 'collection',
474
+ placeholder: 'Add Option',
475
+ default: {},
476
+ displayOptions: {
477
+ show: {
478
+ resource: ['account'],
479
+ operation: ['getChildren'],
480
+ },
481
+ },
482
+ options: [
483
+ {
484
+ displayName: 'Page',
485
+ name: 'page',
486
+ type: 'number',
487
+ default: 1,
488
+ description: 'Page number',
489
+ },
490
+ {
491
+ displayName: 'Per Page',
492
+ name: 'per_page',
493
+ type: 'number',
494
+ default: 20,
495
+ description: 'Results per page',
496
+ },
497
+ ],
498
+ },
499
+
500
+ // Account list filters
501
+ {
502
+ displayName: 'Filters',
503
+ name: 'accountFilters',
504
+ type: 'collection',
505
+ placeholder: 'Add Filter',
506
+ default: {},
507
+ displayOptions: {
508
+ show: {
509
+ resource: ['account'],
510
+ operation: ['list'],
511
+ },
512
+ },
513
+ options: [
514
+ {
515
+ displayName: 'Status',
516
+ name: 'status',
517
+ type: 'options',
518
+ options: [
519
+ { name: 'Active', value: 'active' },
520
+ { name: 'Suspended', value: 'suspended' },
521
+ { name: 'Deleted', value: 'deleted' },
522
+ ],
523
+ default: 'active',
524
+ description: 'Filter by account status',
525
+ },
526
+ {
527
+ displayName: 'Account Type',
528
+ name: 'account_type',
529
+ type: 'options',
530
+ options: [
531
+ { name: 'Parent', value: 'parent' },
532
+ { name: 'Child', value: 'child' },
533
+ { name: 'Standalone', value: 'standalone' },
534
+ ],
535
+ default: 'standalone',
536
+ description: 'Filter by account type',
537
+ },
538
+ {
539
+ displayName: 'Parent Account ID',
540
+ name: 'parent_account_id',
541
+ type: 'number',
542
+ default: 0,
543
+ description: 'Filter by parent account ID',
544
+ },
545
+ {
546
+ displayName: 'Search',
547
+ name: 'search',
548
+ type: 'string',
549
+ default: '',
550
+ description: 'Search by name or email',
551
+ },
552
+ {
553
+ displayName: 'Page',
554
+ name: 'page',
555
+ type: 'number',
556
+ default: 1,
557
+ description: 'Page number',
558
+ },
559
+ {
560
+ displayName: 'Per Page',
561
+ name: 'per_page',
562
+ type: 'number',
563
+ default: 20,
564
+ description: 'Results per page',
565
+ },
566
+ ],
567
+ },
568
+
569
+ // ================== List Operation Parameters ==================
570
+
571
+ // List ID (for get, update, delete list operations)
572
+ {
573
+ displayName: 'List ID',
574
+ name: 'listIdParam',
575
+ type: 'number',
576
+ required: true,
577
+ displayOptions: {
578
+ show: {
579
+ resource: ['list'],
580
+ operation: ['get', 'update', 'delete', 'getStatistics'],
581
+ },
582
+ },
583
+ default: 0,
584
+ description: 'The ID of the list',
585
+ },
586
+
587
+ // List name (for create/update)
588
+ {
589
+ displayName: 'Name',
590
+ name: 'listName',
591
+ type: 'string',
592
+ required: true,
593
+ displayOptions: {
594
+ show: {
595
+ resource: ['list'],
596
+ operation: ['create'],
597
+ },
598
+ },
599
+ default: '',
600
+ description: 'The name of the list',
601
+ },
602
+
603
+ // List additional fields
604
+ {
605
+ displayName: 'Additional Fields',
606
+ name: 'listAdditionalFields',
607
+ type: 'collection',
608
+ placeholder: 'Add Field',
609
+ default: {},
610
+ displayOptions: {
611
+ show: {
612
+ resource: ['list'],
613
+ operation: ['create', 'update'],
614
+ },
615
+ },
616
+ options: [
617
+ {
618
+ displayName: 'Description',
619
+ name: 'description',
620
+ type: 'string',
621
+ default: '',
622
+ description: 'List description',
623
+ },
624
+ {
625
+ displayName: 'Sender Name',
626
+ name: 'sender_name',
627
+ type: 'string',
628
+ default: '',
629
+ description: 'Default sender name for campaigns',
630
+ },
631
+ {
632
+ displayName: 'Sender Email',
633
+ name: 'sender_email',
634
+ type: 'string',
635
+ default: '',
636
+ description: 'Default sender email for campaigns',
637
+ },
638
+ {
639
+ displayName: 'Language',
640
+ name: 'language',
641
+ type: 'string',
642
+ default: 'en',
643
+ description: 'Default language',
644
+ },
645
+ ],
646
+ },
647
+
648
+ // List pagination
649
+ {
650
+ displayName: 'Limit',
651
+ name: 'listLimit',
652
+ type: 'number',
653
+ displayOptions: {
654
+ show: {
655
+ resource: ['list'],
656
+ operation: ['list'],
657
+ },
658
+ },
659
+ default: 20,
660
+ description: 'Number of results to return',
661
+ },
662
+
663
+ // ================== Contact Operation Parameters ==================
664
+
665
+ // Contact ID (for get, update, delete, subscribe, unsubscribe, addTags, removeTags operations)
666
+ {
667
+ displayName: 'Contact ID',
668
+ name: 'contactId',
669
+ type: 'number',
670
+ required: true,
671
+ displayOptions: {
672
+ show: {
673
+ resource: ['contact'],
674
+ operation: ['get', 'update', 'delete', 'subscribe', 'unsubscribe', 'addTags', 'removeTags'],
675
+ },
676
+ },
677
+ default: 0,
678
+ description: 'The ID of the contact',
679
+ },
680
+
681
+ // Tags (for addTags, removeTags operations)
682
+ {
683
+ displayName: 'Tags',
684
+ name: 'tagsToManage',
685
+ type: 'string',
686
+ required: true,
687
+ displayOptions: {
688
+ show: {
689
+ resource: ['contact'],
690
+ operation: ['addTags', 'removeTags'],
691
+ },
692
+ },
693
+ default: '',
694
+ placeholder: 'vip, newsletter, customer',
695
+ description: 'Comma-separated list of tags',
696
+ },
697
+
698
+ // Contact Email (for getByEmail operation)
699
+ {
700
+ displayName: 'Email',
701
+ name: 'email',
702
+ type: 'string',
703
+ required: true,
704
+ displayOptions: {
705
+ show: {
706
+ resource: ['contact'],
707
+ operation: ['getByEmail', 'create'],
708
+ },
709
+ },
710
+ default: '',
711
+ placeholder: 'user@example.com',
712
+ description: 'The email address of the contact',
713
+ },
714
+
715
+ // Contact fields for create/update
716
+ {
717
+ displayName: 'Additional Fields',
718
+ name: 'additionalFields',
719
+ type: 'collection',
720
+ placeholder: 'Add Field',
721
+ default: {},
722
+ displayOptions: {
723
+ show: {
724
+ resource: ['contact'],
725
+ operation: ['create', 'update'],
726
+ },
727
+ },
728
+ options: [
729
+ {
730
+ displayName: 'First Name',
731
+ name: 'first_name',
732
+ type: 'string',
733
+ default: '',
734
+ description: 'First name of the contact',
735
+ },
736
+ {
737
+ displayName: 'Last Name',
738
+ name: 'last_name',
739
+ type: 'string',
740
+ default: '',
741
+ description: 'Last name of the contact',
742
+ },
743
+ {
744
+ displayName: 'Company',
745
+ name: 'company',
746
+ type: 'string',
747
+ default: '',
748
+ description: 'Company name',
749
+ },
750
+ {
751
+ displayName: 'Phone',
752
+ name: 'phone',
753
+ type: 'string',
754
+ default: '',
755
+ description: 'Phone number',
756
+ },
757
+ {
758
+ displayName: 'Mobile',
759
+ name: 'mobile',
760
+ type: 'string',
761
+ default: '',
762
+ description: 'Mobile phone number',
763
+ },
764
+ {
765
+ displayName: 'Language',
766
+ name: 'language',
767
+ type: 'string',
768
+ default: 'en',
769
+ description: 'Preferred language (e.g., en, fr)',
770
+ },
771
+ {
772
+ displayName: 'Timezone',
773
+ name: 'timezone',
774
+ type: 'string',
775
+ default: 'UTC',
776
+ description: 'Timezone (e.g., America/New_York, Europe/Paris)',
777
+ },
778
+ {
779
+ displayName: 'Tags',
780
+ name: 'tags',
781
+ type: 'string',
782
+ default: '',
783
+ description: 'Comma-separated list of tags',
784
+ },
785
+ {
786
+ displayName: 'Custom Attributes',
787
+ name: 'custom_attributes',
788
+ type: 'json',
789
+ default: '{}',
790
+ description: 'JSON object with custom attribute key-value pairs',
791
+ },
792
+ {
793
+ displayName: 'List ID',
794
+ name: 'list_id',
795
+ type: 'number',
796
+ default: 0,
797
+ description: 'Subscribe contact to this list immediately (create only)',
798
+ },
799
+ ],
800
+ },
801
+
802
+ // List ID for subscribe/unsubscribe operations
803
+ {
804
+ displayName: 'List ID',
805
+ name: 'listId',
806
+ type: 'number',
807
+ required: true,
808
+ displayOptions: {
809
+ show: {
810
+ resource: ['contact'],
811
+ operation: ['subscribe', 'unsubscribe'],
812
+ },
813
+ },
814
+ default: 0,
815
+ description: 'The ID of the list',
816
+ },
817
+
818
+ // Bulk import contacts
819
+ {
820
+ displayName: 'Contacts',
821
+ name: 'contacts',
822
+ type: 'json',
823
+ required: true,
824
+ displayOptions: {
825
+ show: {
826
+ resource: ['contact'],
827
+ operation: ['bulkImport'],
828
+ },
829
+ },
830
+ default: '[]',
831
+ description: 'Array of contact objects to import. Each contact should have at least an email field.',
832
+ placeholder: '[{"email": "user1@example.com", "first_name": "John"}, {"email": "user2@example.com"}]',
833
+ },
834
+
835
+ // Advanced search parameters
836
+ {
837
+ displayName: 'Search Query',
838
+ name: 'searchQuery',
839
+ type: 'json',
840
+ required: true,
841
+ displayOptions: {
842
+ show: {
843
+ resource: ['contact'],
844
+ operation: ['search'],
845
+ },
846
+ },
847
+ default: '{}',
848
+ description: 'Advanced search query with filters and operators',
849
+ placeholder: '{"custom_attributes.company": "Acme", "tags": ["vip"], "status": "active"}',
850
+ },
851
+
852
+ {
853
+ displayName: 'Search Options',
854
+ name: 'searchOptions',
855
+ type: 'collection',
856
+ placeholder: 'Add Option',
857
+ default: {},
858
+ displayOptions: {
859
+ show: {
860
+ resource: ['contact'],
861
+ operation: ['search'],
862
+ },
863
+ },
864
+ options: [
865
+ {
866
+ displayName: 'Page',
867
+ name: 'page',
868
+ type: 'number',
869
+ default: 1,
870
+ description: 'Page number',
871
+ },
872
+ {
873
+ displayName: 'Per Page',
874
+ name: 'per_page',
875
+ type: 'number',
876
+ default: 20,
877
+ description: 'Results per page',
878
+ },
879
+ {
880
+ displayName: 'Sort By',
881
+ name: 'sort',
882
+ type: 'string',
883
+ default: 'created_on',
884
+ description: 'Field to sort by (e.g., created_on, email, first_name)',
885
+ },
886
+ {
887
+ displayName: 'Sort Order',
888
+ name: 'sort_order',
889
+ type: 'options',
890
+ options: [
891
+ { name: 'Ascending', value: 'asc' },
892
+ { name: 'Descending', value: 'desc' },
893
+ ],
894
+ default: 'desc',
895
+ description: 'Sort order',
896
+ },
897
+ ],
898
+ },
899
+
900
+ // List contacts filters
901
+ {
902
+ displayName: 'Filters',
903
+ name: 'filters',
904
+ type: 'collection',
905
+ placeholder: 'Add Filter',
906
+ default: {},
907
+ displayOptions: {
908
+ show: {
909
+ resource: ['contact'],
910
+ operation: ['list'],
911
+ },
912
+ },
913
+ options: [
914
+ {
915
+ displayName: 'Status',
916
+ name: 'status',
917
+ type: 'options',
918
+ options: [
919
+ {
920
+ name: 'Active',
921
+ value: 'active',
922
+ },
923
+ {
924
+ name: 'Unsubscribed',
925
+ value: 'unsubscribed',
926
+ },
927
+ {
928
+ name: 'Bounced',
929
+ value: 'bounced',
930
+ },
931
+ {
932
+ name: 'Complained',
933
+ value: 'complained',
934
+ },
935
+ ],
936
+ default: 'active',
937
+ description: 'Filter by contact status',
938
+ },
939
+ {
940
+ displayName: 'List ID',
941
+ name: 'list_id',
942
+ type: 'number',
943
+ default: 0,
944
+ description: 'Filter by list membership',
945
+ },
946
+ {
947
+ displayName: 'Search',
948
+ name: 'search',
949
+ type: 'string',
950
+ default: '',
951
+ description: 'Search by email, name, etc.',
952
+ },
953
+ {
954
+ displayName: 'Tags',
955
+ name: 'tags',
956
+ type: 'string',
957
+ default: '',
958
+ description: 'Comma-separated list of tags to filter by',
959
+ },
960
+ {
961
+ displayName: 'Page',
962
+ name: 'page',
963
+ type: 'number',
964
+ default: 1,
965
+ description: 'Page number for pagination',
966
+ },
967
+ {
968
+ displayName: 'Per Page',
969
+ name: 'per_page',
970
+ type: 'number',
971
+ default: 20,
972
+ description: 'Number of results per page',
973
+ },
974
+ ],
975
+ },
976
+ ],
977
+ };
978
+
979
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
980
+ const items = this.getInputData();
981
+ const returnData: INodeExecutionData[] = [];
982
+
983
+ // Get credentials
984
+ const credentials = await this.getCredentials('cakemailApi');
985
+
986
+ // Initialize Cakemail client
987
+ const client = new CakemailClient({
988
+ email: credentials.email as string,
989
+ password: credentials.password as string,
990
+ baseURL: (credentials.baseURL as string) || undefined,
991
+ });
992
+
993
+ // Process each item
994
+ for (let i = 0; i < items.length; i++) {
995
+ try {
996
+ const resource = this.getNodeParameter('resource', i) as string;
997
+ const operation = this.getNodeParameter('operation', i) as string;
998
+ const accountId = this.getNodeParameter('accountId', i, undefined) as
999
+ | number
1000
+ | undefined;
1001
+
1002
+ const options = accountId ? { accountId } : undefined;
1003
+
1004
+ let responseData: any;
1005
+
1006
+ // Route to appropriate resource handler
1007
+ const node = new Cakemail();
1008
+ if (resource === 'account') {
1009
+ responseData = await node.executeAccountOperation.call(
1010
+ this,
1011
+ client,
1012
+ operation,
1013
+ i,
1014
+ options
1015
+ );
1016
+ } else if (resource === 'contact') {
1017
+ responseData = await node.executeContactOperation.call(
1018
+ this,
1019
+ client,
1020
+ operation,
1021
+ i,
1022
+ options
1023
+ );
1024
+ } else if (resource === 'list') {
1025
+ responseData = await node.executeListOperation.call(
1026
+ this,
1027
+ client,
1028
+ operation,
1029
+ i,
1030
+ options
1031
+ );
1032
+ } else if (resource === 'campaign') {
1033
+ responseData = await node.executeCampaignOperation.call(
1034
+ this,
1035
+ client,
1036
+ operation,
1037
+ i,
1038
+ options
1039
+ );
1040
+ }
1041
+
1042
+ returnData.push({
1043
+ json: responseData,
1044
+ pairedItem: { item: i },
1045
+ });
1046
+ } catch (error: any) {
1047
+ if (this.continueOnFail()) {
1048
+ returnData.push({
1049
+ json: {
1050
+ error: error?.message || 'Unknown error',
1051
+ },
1052
+ pairedItem: { item: i },
1053
+ });
1054
+ continue;
1055
+ }
1056
+
1057
+ // Convert Cakemail errors to n8n errors
1058
+ if (error instanceof CakemailError) {
1059
+ throw new NodeOperationError(this.getNode(), error.message, {
1060
+ itemIndex: i,
1061
+ description: error.cause?.message,
1062
+ });
1063
+ }
1064
+
1065
+ throw error;
1066
+ }
1067
+ }
1068
+
1069
+ return [returnData];
1070
+ }
1071
+
1072
+ /**
1073
+ * Execute account operations
1074
+ */
1075
+ private async executeAccountOperation(
1076
+ this: IExecuteFunctions,
1077
+ client: CakemailClient,
1078
+ operation: string,
1079
+ itemIndex: number,
1080
+ options?: any
1081
+ ): Promise<any> {
1082
+ if (operation === 'get') {
1083
+ return await client.accounts.getSelf();
1084
+ }
1085
+
1086
+ if (operation === 'getById') {
1087
+ const targetAccountId = this.getNodeParameter(
1088
+ 'targetAccountId',
1089
+ itemIndex
1090
+ ) as number;
1091
+ return await client.accounts.get(targetAccountId, options);
1092
+ }
1093
+
1094
+ if (operation === 'list') {
1095
+ const filters = this.getNodeParameter(
1096
+ 'accountFilters',
1097
+ itemIndex,
1098
+ {}
1099
+ ) as any;
1100
+ return await client.accounts.list(filters, options);
1101
+ }
1102
+
1103
+ if (operation === 'create') {
1104
+ const name = this.getNodeParameter('accountName', itemIndex) as string;
1105
+ const email = this.getNodeParameter('accountEmail', itemIndex) as string;
1106
+ const additionalFields = this.getNodeParameter(
1107
+ 'accountAdditionalFields',
1108
+ itemIndex,
1109
+ {}
1110
+ ) as any;
1111
+
1112
+ // Parse settings if provided as JSON string
1113
+ if (additionalFields.settings) {
1114
+ try {
1115
+ additionalFields.settings =
1116
+ typeof additionalFields.settings === 'string'
1117
+ ? JSON.parse(additionalFields.settings)
1118
+ : additionalFields.settings;
1119
+ } catch (error: any) {
1120
+ throw new NodeOperationError(
1121
+ this.getNode(),
1122
+ `Invalid settings JSON: ${error.message}`,
1123
+ { itemIndex }
1124
+ );
1125
+ }
1126
+ }
1127
+
1128
+ const accountData = {
1129
+ name,
1130
+ primary_email: email,
1131
+ ...additionalFields,
1132
+ };
1133
+
1134
+ return await client.accounts.create(accountData, options);
1135
+ }
1136
+
1137
+ if (operation === 'update') {
1138
+ const targetAccountId = this.getNodeParameter(
1139
+ 'targetAccountId',
1140
+ itemIndex
1141
+ ) as number;
1142
+ const additionalFields = this.getNodeParameter(
1143
+ 'accountAdditionalFields',
1144
+ itemIndex,
1145
+ {}
1146
+ ) as any;
1147
+
1148
+ // Parse settings if provided as JSON string
1149
+ if (additionalFields.settings) {
1150
+ try {
1151
+ additionalFields.settings =
1152
+ typeof additionalFields.settings === 'string'
1153
+ ? JSON.parse(additionalFields.settings)
1154
+ : additionalFields.settings;
1155
+ } catch (error: any) {
1156
+ throw new NodeOperationError(
1157
+ this.getNode(),
1158
+ `Invalid settings JSON: ${error.message}`,
1159
+ { itemIndex }
1160
+ );
1161
+ }
1162
+ }
1163
+
1164
+ return await client.accounts.update(
1165
+ targetAccountId,
1166
+ additionalFields,
1167
+ options
1168
+ );
1169
+ }
1170
+
1171
+ if (operation === 'delete') {
1172
+ const targetAccountId = this.getNodeParameter(
1173
+ 'targetAccountId',
1174
+ itemIndex
1175
+ ) as number;
1176
+ await client.accounts.delete(targetAccountId, options);
1177
+ return { success: true, accountId: targetAccountId };
1178
+ }
1179
+
1180
+ if (operation === 'getChildren') {
1181
+ const targetAccountId = this.getNodeParameter(
1182
+ 'targetAccountId',
1183
+ itemIndex
1184
+ ) as number;
1185
+ const childrenOptions = this.getNodeParameter(
1186
+ 'getChildrenOptions',
1187
+ itemIndex,
1188
+ {}
1189
+ ) as any;
1190
+ return await client.accounts.getChildren(
1191
+ targetAccountId,
1192
+ childrenOptions,
1193
+ options
1194
+ );
1195
+ }
1196
+
1197
+ throw new NodeOperationError(
1198
+ this.getNode(),
1199
+ `The operation "${operation}" is not yet implemented for account resource`,
1200
+ { itemIndex }
1201
+ );
1202
+ }
1203
+
1204
+ /**
1205
+ * Execute contact operations
1206
+ */
1207
+ private async executeContactOperation(
1208
+ this: IExecuteFunctions,
1209
+ client: CakemailClient,
1210
+ operation: string,
1211
+ itemIndex: number,
1212
+ options?: any
1213
+ ): Promise<any> {
1214
+ if (operation === 'get') {
1215
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1216
+ return await client.contacts.get(contactId, options);
1217
+ }
1218
+
1219
+ if (operation === 'getByEmail') {
1220
+ const email = this.getNodeParameter('email', itemIndex) as string;
1221
+ return await client.contacts.getByEmail(email, options);
1222
+ }
1223
+
1224
+ if (operation === 'list') {
1225
+ const filters = this.getNodeParameter('filters', itemIndex, {}) as any;
1226
+
1227
+ // Convert comma-separated tags to array
1228
+ if (filters.tags && typeof filters.tags === 'string') {
1229
+ filters.tags = filters.tags.split(',').map((t: string) => t.trim());
1230
+ }
1231
+
1232
+ return await client.contacts.list(filters, options);
1233
+ }
1234
+
1235
+ if (operation === 'create') {
1236
+ const email = this.getNodeParameter('email', itemIndex) as string;
1237
+ const additionalFields = this.getNodeParameter(
1238
+ 'additionalFields',
1239
+ itemIndex,
1240
+ {}
1241
+ ) as any;
1242
+
1243
+ // Build create request
1244
+ const createData: any = {
1245
+ email,
1246
+ ...additionalFields,
1247
+ };
1248
+
1249
+ // Convert comma-separated tags to array
1250
+ if (createData.tags && typeof createData.tags === 'string') {
1251
+ createData.tags = createData.tags.split(',').map((t: string) => t.trim());
1252
+ }
1253
+
1254
+ // Parse custom attributes JSON
1255
+ if (createData.custom_attributes && typeof createData.custom_attributes === 'string') {
1256
+ try {
1257
+ createData.custom_attributes = JSON.parse(createData.custom_attributes);
1258
+ } catch (error) {
1259
+ throw new NodeOperationError(
1260
+ this.getNode(),
1261
+ 'Invalid JSON in custom_attributes field',
1262
+ { itemIndex }
1263
+ );
1264
+ }
1265
+ }
1266
+
1267
+ return await client.contacts.create(createData, options);
1268
+ }
1269
+
1270
+ if (operation === 'update') {
1271
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1272
+ const additionalFields = this.getNodeParameter(
1273
+ 'additionalFields',
1274
+ itemIndex,
1275
+ {}
1276
+ ) as any;
1277
+
1278
+ // Convert comma-separated tags to array
1279
+ if (additionalFields.tags && typeof additionalFields.tags === 'string') {
1280
+ additionalFields.tags = additionalFields.tags
1281
+ .split(',')
1282
+ .map((t: string) => t.trim());
1283
+ }
1284
+
1285
+ // Parse custom attributes JSON
1286
+ if (additionalFields.custom_attributes && typeof additionalFields.custom_attributes === 'string') {
1287
+ try {
1288
+ additionalFields.custom_attributes = JSON.parse(
1289
+ additionalFields.custom_attributes
1290
+ );
1291
+ } catch (error) {
1292
+ throw new NodeOperationError(
1293
+ this.getNode(),
1294
+ 'Invalid JSON in custom_attributes field',
1295
+ { itemIndex }
1296
+ );
1297
+ }
1298
+ }
1299
+
1300
+ return await client.contacts.update(contactId, additionalFields, options);
1301
+ }
1302
+
1303
+ if (operation === 'delete') {
1304
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1305
+ await client.contacts.delete(contactId, options);
1306
+ return { success: true, contactId };
1307
+ }
1308
+
1309
+ if (operation === 'subscribe') {
1310
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1311
+ const listId = this.getNodeParameter('listId', itemIndex) as number;
1312
+ return await client.contacts.subscribe(contactId, listId, options);
1313
+ }
1314
+
1315
+ if (operation === 'unsubscribe') {
1316
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1317
+ const listId = this.getNodeParameter('listId', itemIndex) as number;
1318
+ return await client.contacts.unsubscribe(contactId, listId, options);
1319
+ }
1320
+
1321
+ if (operation === 'bulkImport') {
1322
+ const contactsParam = this.getNodeParameter('contacts', itemIndex) as string;
1323
+
1324
+ let contacts: any[];
1325
+ try {
1326
+ contacts = typeof contactsParam === 'string'
1327
+ ? JSON.parse(contactsParam)
1328
+ : contactsParam;
1329
+
1330
+ if (!Array.isArray(contacts)) {
1331
+ throw new Error('Contacts must be an array');
1332
+ }
1333
+ } catch (error: any) {
1334
+ throw new NodeOperationError(
1335
+ this.getNode(),
1336
+ `Invalid contacts JSON: ${error.message}`,
1337
+ { itemIndex }
1338
+ );
1339
+ }
1340
+
1341
+ // Validate that all contacts have email
1342
+ const invalidContacts = contacts.filter((c, i) => !c.email);
1343
+ if (invalidContacts.length > 0) {
1344
+ throw new NodeOperationError(
1345
+ this.getNode(),
1346
+ `All contacts must have an email field. Found ${invalidContacts.length} contacts without email.`,
1347
+ { itemIndex }
1348
+ );
1349
+ }
1350
+
1351
+ // Import contacts one by one and collect results
1352
+ const results = {
1353
+ total: contacts.length,
1354
+ successful: 0,
1355
+ failed: 0,
1356
+ errors: [] as Array<{ index: number; email: string; error: string }>,
1357
+ };
1358
+
1359
+ for (let i = 0; i < contacts.length; i++) {
1360
+ try {
1361
+ await client.contacts.create(contacts[i], options);
1362
+ results.successful++;
1363
+ } catch (error: any) {
1364
+ results.failed++;
1365
+ results.errors.push({
1366
+ index: i,
1367
+ email: contacts[i].email,
1368
+ error: error.message || 'Unknown error',
1369
+ });
1370
+ }
1371
+ }
1372
+
1373
+ return results;
1374
+ }
1375
+
1376
+ if (operation === 'search') {
1377
+ const searchQueryParam = this.getNodeParameter('searchQuery', itemIndex) as string;
1378
+ const searchOptions = this.getNodeParameter('searchOptions', itemIndex, {}) as any;
1379
+
1380
+ let searchQuery: any;
1381
+ try {
1382
+ searchQuery = typeof searchQueryParam === 'string'
1383
+ ? JSON.parse(searchQueryParam)
1384
+ : searchQueryParam;
1385
+ } catch (error: any) {
1386
+ throw new NodeOperationError(
1387
+ this.getNode(),
1388
+ `Invalid search query JSON: ${error.message}`,
1389
+ { itemIndex }
1390
+ );
1391
+ }
1392
+
1393
+ // Build search parameters
1394
+ const searchParams: any = {
1395
+ ...searchOptions,
1396
+ filter: searchQuery,
1397
+ };
1398
+
1399
+ // Handle sort order
1400
+ if (searchOptions.sort && searchOptions.sort_order) {
1401
+ searchParams.sort = `${searchOptions.sort}:${searchOptions.sort_order}`;
1402
+ delete searchParams.sort_order;
1403
+ }
1404
+
1405
+ return await client.contacts.list(searchParams, options);
1406
+ }
1407
+
1408
+ if (operation === 'addTags') {
1409
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1410
+ const tagsString = this.getNodeParameter('tagsToManage', itemIndex) as string;
1411
+ const tags = tagsString.split(',').map((t: string) => t.trim()).filter((t: string) => t);
1412
+
1413
+ if (tags.length === 0) {
1414
+ throw new NodeOperationError(
1415
+ this.getNode(),
1416
+ 'At least one tag must be provided',
1417
+ { itemIndex }
1418
+ );
1419
+ }
1420
+
1421
+ return await client.contacts.addTags(contactId, tags, options);
1422
+ }
1423
+
1424
+ if (operation === 'removeTags') {
1425
+ const contactId = this.getNodeParameter('contactId', itemIndex) as number;
1426
+ const tagsString = this.getNodeParameter('tagsToManage', itemIndex) as string;
1427
+ const tags = tagsString.split(',').map((t: string) => t.trim()).filter((t: string) => t);
1428
+
1429
+ if (tags.length === 0) {
1430
+ throw new NodeOperationError(
1431
+ this.getNode(),
1432
+ 'At least one tag must be provided',
1433
+ { itemIndex }
1434
+ );
1435
+ }
1436
+
1437
+ return await client.contacts.removeTags(contactId, tags, options);
1438
+ }
1439
+
1440
+ throw new NodeOperationError(
1441
+ this.getNode(),
1442
+ `The operation "${operation}" is not yet implemented for contact resource`,
1443
+ { itemIndex }
1444
+ );
1445
+ }
1446
+
1447
+ /**
1448
+ * Execute list operations
1449
+ */
1450
+ private async executeListOperation(
1451
+ this: IExecuteFunctions,
1452
+ client: CakemailClient,
1453
+ operation: string,
1454
+ itemIndex: number,
1455
+ options?: any
1456
+ ): Promise<any> {
1457
+ if (operation === 'get') {
1458
+ const listId = this.getNodeParameter('listIdParam', itemIndex) as number;
1459
+ return await client.lists.get(listId, options);
1460
+ }
1461
+
1462
+ if (operation === 'list') {
1463
+ const limit = this.getNodeParameter('listLimit', itemIndex, 20) as number;
1464
+ return await client.lists.list({ per_page: limit }, options);
1465
+ }
1466
+
1467
+ if (operation === 'create') {
1468
+ const name = this.getNodeParameter('listName', itemIndex) as string;
1469
+ const additionalFields = this.getNodeParameter(
1470
+ 'listAdditionalFields',
1471
+ itemIndex,
1472
+ {}
1473
+ ) as any;
1474
+
1475
+ const createData = {
1476
+ name,
1477
+ ...additionalFields,
1478
+ };
1479
+
1480
+ return await client.lists.create(createData, options);
1481
+ }
1482
+
1483
+ if (operation === 'update') {
1484
+ const listId = this.getNodeParameter('listIdParam', itemIndex) as number;
1485
+ const additionalFields = this.getNodeParameter(
1486
+ 'listAdditionalFields',
1487
+ itemIndex,
1488
+ {}
1489
+ ) as any;
1490
+
1491
+ return await client.lists.update(listId, additionalFields, options);
1492
+ }
1493
+
1494
+ if (operation === 'delete') {
1495
+ const listId = this.getNodeParameter('listIdParam', itemIndex) as number;
1496
+ await client.lists.delete(listId, options);
1497
+ return { success: true, listId };
1498
+ }
1499
+
1500
+ if (operation === 'getStatistics') {
1501
+ const listId = this.getNodeParameter('listIdParam', itemIndex) as number;
1502
+ return await client.lists.getStatistics(listId, options);
1503
+ }
1504
+
1505
+ throw new NodeOperationError(
1506
+ this.getNode(),
1507
+ `The operation "${operation}" is not yet implemented for list resource`,
1508
+ { itemIndex }
1509
+ );
1510
+ }
1511
+
1512
+ /**
1513
+ * Execute campaign operations
1514
+ */
1515
+ private async executeCampaignOperation(
1516
+ this: IExecuteFunctions,
1517
+ client: CakemailClient,
1518
+ operation: string,
1519
+ itemIndex: number,
1520
+ options?: any
1521
+ ): Promise<any> {
1522
+ throw new NodeOperationError(
1523
+ this.getNode(),
1524
+ `Campaign operations are not yet implemented`,
1525
+ { itemIndex }
1526
+ );
1527
+ }
1528
+ }