@devwithbobby/loops 0.1.8 → 0.1.10

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.
@@ -148,11 +148,18 @@ export declare class Loops {
148
148
  /**
149
149
  * Trigger a loop for a contact
150
150
  * Loops are automated email sequences that can be triggered by events
151
+ *
152
+ * Note: Loops.so doesn't have a direct loop trigger endpoint.
153
+ * Loops are triggered through events. Make sure your loop is configured
154
+ * in the Loops dashboard to listen for events.
155
+ *
156
+ * @param options.eventName - Optional event name. If not provided, uses `loop_{loopId}`
151
157
  */
152
158
  triggerLoop(ctx: RunActionCtx, options: {
153
159
  loopId: string;
154
160
  email: string;
155
161
  dataVariables?: Record<string, any>;
162
+ eventName?: string;
156
163
  }): Promise<FunctionReturnType<Action>>;
157
164
  /**
158
165
  * For easy re-exporting.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -200,6 +200,12 @@ export class Loops {
200
200
  /**
201
201
  * Trigger a loop for a contact
202
202
  * Loops are automated email sequences that can be triggered by events
203
+ *
204
+ * Note: Loops.so doesn't have a direct loop trigger endpoint.
205
+ * Loops are triggered through events. Make sure your loop is configured
206
+ * in the Loops dashboard to listen for events.
207
+ *
208
+ * @param options.eventName - Optional event name. If not provided, uses `loop_{loopId}`
203
209
  */
204
210
  async triggerLoop(ctx, options) {
205
211
  return ctx.runAction(this.component.lib.triggerLoop, {
@@ -39,13 +39,26 @@ export declare const sendEvent: any;
39
39
  export declare const deleteContact: any;
40
40
  /**
41
41
  * Send a campaign to contacts
42
- * Campaigns are one-time email sends to a segment or list of contacts
42
+ * Note: Campaigns in Loops.so are typically managed from the dashboard.
43
+ * This function sends transactional emails to multiple contacts as a workaround.
44
+ * If you need true campaign functionality, use the Loops.so dashboard or contact their support.
43
45
  */
44
46
  export declare const sendCampaign: any;
45
47
  /**
46
48
  * Trigger a loop for a contact
47
- * Loops are automated email sequences that can be triggered by events
48
- * This is similar to sendEvent but specifically for loops
49
+ * Note: Loops in Loops.so are triggered through events, not a direct API endpoint.
50
+ * This function uses the events endpoint to trigger the loop.
51
+ * The loop must be configured in the Loops dashboard to listen for events.
52
+ *
53
+ * IMPORTANT: Loops.so doesn't have a direct /loops/trigger endpoint.
54
+ * Loops are triggered by sending events. Make sure your loop in the dashboard
55
+ * is configured to trigger on an event name (e.g., "loop_trigger").
56
+ *
57
+ * If you need to trigger a specific loop, you should:
58
+ * 1. Configure the loop in the dashboard to listen for a specific event name
59
+ * 2. Use sendEvent() with that event name instead
60
+ *
61
+ * This function is kept for backwards compatibility but works by sending an event.
49
62
  */
50
63
  export declare const triggerLoop: any;
51
64
  /**
@@ -56,7 +69,8 @@ export declare const triggerLoop: any;
56
69
  export declare const findContact: any;
57
70
  /**
58
71
  * Batch create contacts
59
- * Create multiple contacts in a single API call
72
+ * Creates multiple contacts sequentially using the single contact create endpoint.
73
+ * Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
60
74
  */
61
75
  export declare const batchCreateContacts: any;
62
76
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AA0BA;;GAEG;AACH,eAAO,MAAM,YAAY,KA6CvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAexB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAgC5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,KAwCxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU,KAiHrB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAoDxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAqD5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,KAgCpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KA8BxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,KAkFvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KA+CtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KA2DtB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KA4C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KAwC9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe,KAwC1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAgDxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAsGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KA6ClC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KA6C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KA8B/B,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AA0BA;;GAEG;AACH,eAAO,MAAM,YAAY,KA6CvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAexB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAgC5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,KAwCxB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,UAAU,KAiHrB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAoDxB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAqD5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,KAgCpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KA8BxB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,YAAY,KAiHvB,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,KAyDtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,KA2DtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,KAyD9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KAwC9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe,KAwC1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,KAgDxB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAsGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KA6ClC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KA6C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KA8B/B,CAAC"}
@@ -450,7 +450,9 @@ export const deleteContact = za({
450
450
  });
451
451
  /**
452
452
  * Send a campaign to contacts
453
- * Campaigns are one-time email sends to a segment or list of contacts
453
+ * Note: Campaigns in Loops.so are typically managed from the dashboard.
454
+ * This function sends transactional emails to multiple contacts as a workaround.
455
+ * If you need true campaign functionality, use the Loops.so dashboard or contact their support.
454
456
  */
455
457
  export const sendCampaign = za({
456
458
  args: z.object({
@@ -469,68 +471,102 @@ export const sendCampaign = za({
469
471
  returns: z.object({
470
472
  success: z.boolean(),
471
473
  messageId: z.string().optional(),
474
+ sent: z.number().optional(),
475
+ errors: z.array(z.object({
476
+ email: z.string(),
477
+ error: z.string(),
478
+ })).optional(),
472
479
  }),
473
480
  handler: async (ctx, args) => {
474
- const payload = {
475
- campaignId: args.campaignId,
476
- };
477
- if (args.emails && args.emails.length > 0) {
478
- payload.emails = args.emails;
479
- }
480
- if (args.transactionalId) {
481
- payload.transactionalId = args.transactionalId;
482
- }
483
- if (args.dataVariables) {
484
- payload.dataVariables = args.dataVariables;
481
+ // Loops.so doesn't have a campaigns API endpoint
482
+ // As a workaround, we'll send transactional emails to the specified contacts
483
+ if (!args.transactionalId) {
484
+ throw new Error("Campaigns require a transactionalId. " +
485
+ "Loops.so campaigns are managed from the dashboard. " +
486
+ "This function sends transactional emails to multiple contacts as a workaround. " +
487
+ "Please provide a transactionalId to send emails.");
485
488
  }
486
- if (args.audienceFilters) {
487
- payload.audienceFilters = args.audienceFilters;
488
- }
489
- const response = await fetch(`${LOOPS_API_BASE_URL}/campaigns/send`, {
490
- method: "POST",
491
- headers: {
492
- Authorization: `Bearer ${args.apiKey}`,
493
- "Content-Type": "application/json",
494
- },
495
- body: JSON.stringify(payload),
496
- });
497
- if (!response.ok) {
498
- const errorText = await response.text();
499
- console.error(`Loops API error [${response.status}]:`, errorText);
500
- throw sanitizeError(response.status, errorText);
489
+ if (!args.emails || args.emails.length === 0) {
490
+ // If no emails provided but audienceFilters are, we need to query contacts
491
+ if (args.audienceFilters) {
492
+ // Query contacts from our database based on filters
493
+ const contacts = await ctx.runQuery((internal.lib).countContacts, {
494
+ userGroup: args.audienceFilters.userGroup,
495
+ source: args.audienceFilters.source,
496
+ });
497
+ if (contacts === 0) {
498
+ return {
499
+ success: false,
500
+ sent: 0,
501
+ errors: [{ email: "audience", error: "No contacts found matching filters" }],
502
+ };
503
+ }
504
+ // Note: We can't get email list from countContacts, so this is a limitation
505
+ throw new Error("Campaigns with audienceFilters require emails to be specified. " +
506
+ "Please provide the emails array with contacts to send to.");
507
+ }
508
+ throw new Error("Campaigns require either emails array or audienceFilters. " +
509
+ "Please provide at least one email address or use transactional emails for single contacts.");
501
510
  }
502
- const data = (await response.json());
503
- if (args.emails && args.emails.length > 0) {
504
- for (const email of args.emails) {
511
+ // Send transactional emails to each contact as a workaround for campaigns
512
+ let sent = 0;
513
+ const errors = [];
514
+ for (const email of args.emails) {
515
+ try {
516
+ await ctx.runAction((internal.lib).sendTransactional, {
517
+ apiKey: args.apiKey,
518
+ transactionalId: args.transactionalId,
519
+ email,
520
+ dataVariables: args.dataVariables,
521
+ });
522
+ sent++;
523
+ // Log as campaign operation
505
524
  await ctx.runMutation((internal.lib).logEmailOperation, {
506
525
  operationType: "campaign",
507
526
  email,
508
527
  success: true,
509
528
  campaignId: args.campaignId,
510
- messageId: data.messageId,
529
+ transactionalId: args.transactionalId,
530
+ });
531
+ }
532
+ catch (error) {
533
+ errors.push({
534
+ email,
535
+ error: error instanceof Error ? error.message : String(error),
536
+ });
537
+ // Log failed campaign operation
538
+ await ctx.runMutation((internal.lib).logEmailOperation, {
539
+ operationType: "campaign",
540
+ email,
541
+ success: false,
542
+ campaignId: args.campaignId,
543
+ transactionalId: args.transactionalId,
544
+ metadata: { error: error instanceof Error ? error.message : String(error) },
511
545
  });
512
546
  }
513
- }
514
- else {
515
- await ctx.runMutation((internal.lib).logEmailOperation, {
516
- operationType: "campaign",
517
- email: "audience",
518
- success: true,
519
- campaignId: args.campaignId,
520
- messageId: data.messageId,
521
- metadata: { audienceFilters: args.audienceFilters },
522
- });
523
547
  }
524
548
  return {
525
- success: true,
526
- messageId: data.messageId,
549
+ success: sent > 0,
550
+ sent,
551
+ errors: errors.length > 0 ? errors : undefined,
527
552
  };
528
553
  },
529
554
  });
530
555
  /**
531
556
  * Trigger a loop for a contact
532
- * Loops are automated email sequences that can be triggered by events
533
- * This is similar to sendEvent but specifically for loops
557
+ * Note: Loops in Loops.so are triggered through events, not a direct API endpoint.
558
+ * This function uses the events endpoint to trigger the loop.
559
+ * The loop must be configured in the Loops dashboard to listen for events.
560
+ *
561
+ * IMPORTANT: Loops.so doesn't have a direct /loops/trigger endpoint.
562
+ * Loops are triggered by sending events. Make sure your loop in the dashboard
563
+ * is configured to trigger on an event name (e.g., "loop_trigger").
564
+ *
565
+ * If you need to trigger a specific loop, you should:
566
+ * 1. Configure the loop in the dashboard to listen for a specific event name
567
+ * 2. Use sendEvent() with that event name instead
568
+ *
569
+ * This function is kept for backwards compatibility but works by sending an event.
534
570
  */
535
571
  export const triggerLoop = za({
536
572
  args: z.object({
@@ -538,41 +574,53 @@ export const triggerLoop = za({
538
574
  loopId: z.string(),
539
575
  email: z.string().email(),
540
576
  dataVariables: z.record(z.string(), z.any()).optional(),
577
+ eventName: z.string().optional(), // Event name that triggers the loop
541
578
  }),
542
579
  returns: z.object({
543
580
  success: z.boolean(),
581
+ warning: z.string().optional(),
544
582
  }),
545
583
  handler: async (ctx, args) => {
546
- const response = await fetch(`${LOOPS_API_BASE_URL}/loops/trigger`, {
547
- method: "POST",
548
- headers: {
549
- Authorization: `Bearer ${args.apiKey}`,
550
- "Content-Type": "application/json",
551
- },
552
- body: JSON.stringify({
553
- loopId: args.loopId,
584
+ // Loops.so doesn't have a /loops/trigger endpoint
585
+ // Loops are triggered through events. We'll use the events endpoint.
586
+ // Default event name if not provided
587
+ const eventName = args.eventName || `loop_${args.loopId}`;
588
+ try {
589
+ // Send event to trigger the loop
590
+ await ctx.runAction((internal.lib).sendEvent, {
591
+ apiKey: args.apiKey,
554
592
  email: args.email,
555
- dataVariables: args.dataVariables,
556
- }),
557
- });
558
- if (!response.ok) {
559
- const errorText = await response.text();
560
- console.error(`Loops API error [${response.status}]:`, errorText);
593
+ eventName,
594
+ eventProperties: {
595
+ ...args.dataVariables,
596
+ loopId: args.loopId, // Include loopId in event properties
597
+ },
598
+ });
599
+ // Log as loop operation
600
+ await ctx.runMutation((internal.lib).logEmailOperation, {
601
+ operationType: "loop",
602
+ email: args.email,
603
+ success: true,
604
+ loopId: args.loopId,
605
+ eventName,
606
+ });
607
+ return {
608
+ success: true,
609
+ warning: "Loops are triggered via events. Ensure your loop is configured to listen for this event.",
610
+ };
611
+ }
612
+ catch (error) {
613
+ // Log failed loop operation
561
614
  await ctx.runMutation((internal.lib).logEmailOperation, {
562
615
  operationType: "loop",
563
616
  email: args.email,
564
617
  success: false,
565
618
  loopId: args.loopId,
619
+ eventName,
620
+ metadata: { error: error instanceof Error ? error.message : String(error) },
566
621
  });
567
- throw sanitizeError(response.status, errorText);
622
+ throw error;
568
623
  }
569
- await ctx.runMutation((internal.lib).logEmailOperation, {
570
- operationType: "loop",
571
- email: args.email,
572
- success: true,
573
- loopId: args.loopId,
574
- });
575
- return { success: true };
576
624
  },
577
625
  });
578
626
  /**
@@ -632,7 +680,8 @@ export const findContact = za({
632
680
  });
633
681
  /**
634
682
  * Batch create contacts
635
- * Create multiple contacts in a single API call
683
+ * Creates multiple contacts sequentially using the single contact create endpoint.
684
+ * Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
636
685
  */
637
686
  export const batchCreateContacts = za({
638
687
  args: z.object({
@@ -642,36 +691,52 @@ export const batchCreateContacts = za({
642
691
  returns: z.object({
643
692
  success: z.boolean(),
644
693
  created: z.number().optional(),
694
+ failed: z.number().optional(),
695
+ results: z.array(z.object({
696
+ email: z.string(),
697
+ success: z.boolean(),
698
+ error: z.string().optional(),
699
+ })).optional(),
645
700
  }),
646
701
  handler: async (ctx, args) => {
647
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/batch`, {
648
- method: "POST",
649
- headers: {
650
- Authorization: `Bearer ${args.apiKey}`,
651
- "Content-Type": "application/json",
652
- },
653
- body: JSON.stringify({ contacts: args.contacts }),
654
- });
655
- if (!response.ok) {
656
- const errorText = await response.text();
657
- console.error(`Loops API error [${response.status}]:`, errorText);
658
- throw sanitizeError(response.status, errorText);
659
- }
660
- const data = (await response.json());
702
+ let created = 0;
703
+ let failed = 0;
704
+ const results = [];
705
+ // Create contacts one by one since Loops.so doesn't have a batch endpoint
661
706
  for (const contact of args.contacts) {
662
- await ctx.runMutation((internal.lib.storeContact), {
663
- email: contact.email,
664
- firstName: contact.firstName,
665
- lastName: contact.lastName,
666
- userId: contact.userId,
667
- source: contact.source,
668
- subscribed: contact.subscribed,
669
- userGroup: contact.userGroup,
670
- });
707
+ try {
708
+ // Use the addContact function which handles create/update logic
709
+ const result = await ctx.runAction((internal.lib).addContact, {
710
+ apiKey: args.apiKey,
711
+ contact,
712
+ });
713
+ if (result.success) {
714
+ created++;
715
+ results.push({ email: contact.email, success: true });
716
+ }
717
+ else {
718
+ failed++;
719
+ results.push({
720
+ email: contact.email,
721
+ success: false,
722
+ error: "Unknown error"
723
+ });
724
+ }
725
+ }
726
+ catch (error) {
727
+ failed++;
728
+ results.push({
729
+ email: contact.email,
730
+ success: false,
731
+ error: error instanceof Error ? error.message : String(error),
732
+ });
733
+ }
671
734
  }
672
735
  return {
673
- success: true,
674
- created: data.created ?? args.contacts.length,
736
+ success: created > 0,
737
+ created,
738
+ failed,
739
+ results,
675
740
  };
676
741
  },
677
742
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devwithbobby/loops",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Convex component for integrating with Loops.so email marketing platform",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -340,6 +340,12 @@ export class Loops {
340
340
  /**
341
341
  * Trigger a loop for a contact
342
342
  * Loops are automated email sequences that can be triggered by events
343
+ *
344
+ * Note: Loops.so doesn't have a direct loop trigger endpoint.
345
+ * Loops are triggered through events. Make sure your loop is configured
346
+ * in the Loops dashboard to listen for events.
347
+ *
348
+ * @param options.eventName - Optional event name. If not provided, uses `loop_{loopId}`
343
349
  */
344
350
  async triggerLoop(
345
351
  ctx: RunActionCtx,
@@ -347,6 +353,7 @@ export class Loops {
347
353
  loopId: string;
348
354
  email: string;
349
355
  dataVariables?: Record<string, any>;
356
+ eventName?: string; // Event name that triggers the loop
350
357
  },
351
358
  ) {
352
359
  return ctx.runAction((this.component.lib as any).triggerLoop, {
@@ -486,7 +486,9 @@ export const deleteContact = za({
486
486
 
487
487
  /**
488
488
  * Send a campaign to contacts
489
- * Campaigns are one-time email sends to a segment or list of contacts
489
+ * Note: Campaigns in Loops.so are typically managed from the dashboard.
490
+ * This function sends transactional emails to multiple contacts as a workaround.
491
+ * If you need true campaign functionality, use the Loops.so dashboard or contact their support.
490
492
  */
491
493
  export const sendCampaign = za({
492
494
  args: z.object({
@@ -505,77 +507,119 @@ export const sendCampaign = za({
505
507
  returns: z.object({
506
508
  success: z.boolean(),
507
509
  messageId: z.string().optional(),
510
+ sent: z.number().optional(),
511
+ errors: z.array(z.object({
512
+ email: z.string(),
513
+ error: z.string(),
514
+ })).optional(),
508
515
  }),
509
516
  handler: async (ctx, args) => {
510
- const payload: Record<string, any> = {
511
- campaignId: args.campaignId,
512
- };
513
-
514
- if (args.emails && args.emails.length > 0) {
515
- payload.emails = args.emails;
516
- }
517
-
518
- if (args.transactionalId) {
519
- payload.transactionalId = args.transactionalId;
520
- }
521
-
522
- if (args.dataVariables) {
523
- payload.dataVariables = args.dataVariables;
524
- }
525
-
526
- if (args.audienceFilters) {
527
- payload.audienceFilters = args.audienceFilters;
517
+ // Loops.so doesn't have a campaigns API endpoint
518
+ // As a workaround, we'll send transactional emails to the specified contacts
519
+
520
+ if (!args.transactionalId) {
521
+ throw new Error(
522
+ "Campaigns require a transactionalId. " +
523
+ "Loops.so campaigns are managed from the dashboard. " +
524
+ "This function sends transactional emails to multiple contacts as a workaround. " +
525
+ "Please provide a transactionalId to send emails."
526
+ );
528
527
  }
529
528
 
530
- const response = await fetch(`${LOOPS_API_BASE_URL}/campaigns/send`, {
531
- method: "POST",
532
- headers: {
533
- Authorization: `Bearer ${args.apiKey}`,
534
- "Content-Type": "application/json",
535
- },
536
- body: JSON.stringify(payload),
537
- });
538
-
539
- if (!response.ok) {
540
- const errorText = await response.text();
541
- console.error(`Loops API error [${response.status}]:`, errorText);
542
- throw sanitizeError(response.status, errorText);
529
+ if (!args.emails || args.emails.length === 0) {
530
+ // If no emails provided but audienceFilters are, we need to query contacts
531
+ if (args.audienceFilters) {
532
+ // Query contacts from our database based on filters
533
+ const contacts = await ctx.runQuery(((internal as any).lib).countContacts as any, {
534
+ userGroup: args.audienceFilters.userGroup,
535
+ source: args.audienceFilters.source,
536
+ });
537
+
538
+ if (contacts === 0) {
539
+ return {
540
+ success: false,
541
+ sent: 0,
542
+ errors: [{ email: "audience", error: "No contacts found matching filters" }],
543
+ };
544
+ }
545
+
546
+ // Note: We can't get email list from countContacts, so this is a limitation
547
+ throw new Error(
548
+ "Campaigns with audienceFilters require emails to be specified. " +
549
+ "Please provide the emails array with contacts to send to."
550
+ );
551
+ }
552
+
553
+ throw new Error(
554
+ "Campaigns require either emails array or audienceFilters. " +
555
+ "Please provide at least one email address or use transactional emails for single contacts."
556
+ );
543
557
  }
544
558
 
545
- const data = (await response.json()) as { messageId?: string };
559
+ // Send transactional emails to each contact as a workaround for campaigns
560
+ let sent = 0;
561
+ const errors: Array<{ email: string; error: string }> = [];
546
562
 
547
- if (args.emails && args.emails.length > 0) {
548
- for (const email of args.emails) {
563
+ for (const email of args.emails) {
564
+ try {
565
+ await ctx.runAction(((internal as any).lib).sendTransactional as any, {
566
+ apiKey: args.apiKey,
567
+ transactionalId: args.transactionalId!,
568
+ email,
569
+ dataVariables: args.dataVariables,
570
+ });
571
+
572
+ sent++;
573
+
574
+ // Log as campaign operation
549
575
  await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
550
576
  operationType: "campaign",
551
577
  email,
552
578
  success: true,
553
579
  campaignId: args.campaignId,
554
- messageId: data.messageId,
580
+ transactionalId: args.transactionalId,
581
+ });
582
+ } catch (error) {
583
+ errors.push({
584
+ email,
585
+ error: error instanceof Error ? error.message : String(error),
586
+ });
587
+
588
+ // Log failed campaign operation
589
+ await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
590
+ operationType: "campaign",
591
+ email,
592
+ success: false,
593
+ campaignId: args.campaignId,
594
+ transactionalId: args.transactionalId,
595
+ metadata: { error: error instanceof Error ? error.message : String(error) },
555
596
  });
556
597
  }
557
- } else {
558
- await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
559
- operationType: "campaign",
560
- email: "audience",
561
- success: true,
562
- campaignId: args.campaignId,
563
- messageId: data.messageId,
564
- metadata: { audienceFilters: args.audienceFilters },
565
- });
566
598
  }
567
599
 
568
600
  return {
569
- success: true,
570
- messageId: data.messageId,
601
+ success: sent > 0,
602
+ sent,
603
+ errors: errors.length > 0 ? errors : undefined,
571
604
  };
572
605
  },
573
606
  });
574
607
 
575
608
  /**
576
609
  * Trigger a loop for a contact
577
- * Loops are automated email sequences that can be triggered by events
578
- * This is similar to sendEvent but specifically for loops
610
+ * Note: Loops in Loops.so are triggered through events, not a direct API endpoint.
611
+ * This function uses the events endpoint to trigger the loop.
612
+ * The loop must be configured in the Loops dashboard to listen for events.
613
+ *
614
+ * IMPORTANT: Loops.so doesn't have a direct /loops/trigger endpoint.
615
+ * Loops are triggered by sending events. Make sure your loop in the dashboard
616
+ * is configured to trigger on an event name (e.g., "loop_trigger").
617
+ *
618
+ * If you need to trigger a specific loop, you should:
619
+ * 1. Configure the loop in the dashboard to listen for a specific event name
620
+ * 2. Use sendEvent() with that event name instead
621
+ *
622
+ * This function is kept for backwards compatibility but works by sending an event.
579
623
  */
580
624
  export const triggerLoop = za({
581
625
  args: z.object({
@@ -583,46 +627,56 @@ export const triggerLoop = za({
583
627
  loopId: z.string(),
584
628
  email: z.string().email(),
585
629
  dataVariables: z.record(z.string(), z.any()).optional(),
630
+ eventName: z.string().optional(), // Event name that triggers the loop
586
631
  }),
587
632
  returns: z.object({
588
633
  success: z.boolean(),
634
+ warning: z.string().optional(),
589
635
  }),
590
636
  handler: async (ctx, args) => {
591
- const response = await fetch(`${LOOPS_API_BASE_URL}/loops/trigger`, {
592
- method: "POST",
593
- headers: {
594
- Authorization: `Bearer ${args.apiKey}`,
595
- "Content-Type": "application/json",
596
- },
597
- body: JSON.stringify({
598
- loopId: args.loopId,
637
+ // Loops.so doesn't have a /loops/trigger endpoint
638
+ // Loops are triggered through events. We'll use the events endpoint.
639
+ // Default event name if not provided
640
+ const eventName = args.eventName || `loop_${args.loopId}`;
641
+
642
+ try {
643
+ // Send event to trigger the loop
644
+ await ctx.runAction(((internal as any).lib).sendEvent as any, {
645
+ apiKey: args.apiKey,
599
646
  email: args.email,
600
- dataVariables: args.dataVariables,
601
- }),
602
- });
647
+ eventName,
648
+ eventProperties: {
649
+ ...args.dataVariables,
650
+ loopId: args.loopId, // Include loopId in event properties
651
+ },
652
+ });
603
653
 
604
- if (!response.ok) {
605
- const errorText = await response.text();
606
- console.error(`Loops API error [${response.status}]:`, errorText);
607
-
654
+ // Log as loop operation
655
+ await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
656
+ operationType: "loop",
657
+ email: args.email,
658
+ success: true,
659
+ loopId: args.loopId,
660
+ eventName,
661
+ });
662
+
663
+ return {
664
+ success: true,
665
+ warning: "Loops are triggered via events. Ensure your loop is configured to listen for this event.",
666
+ };
667
+ } catch (error) {
668
+ // Log failed loop operation
608
669
  await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
609
670
  operationType: "loop",
610
671
  email: args.email,
611
672
  success: false,
612
673
  loopId: args.loopId,
674
+ eventName,
675
+ metadata: { error: error instanceof Error ? error.message : String(error) },
613
676
  });
614
677
 
615
- throw sanitizeError(response.status, errorText);
678
+ throw error;
616
679
  }
617
-
618
- await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
619
- operationType: "loop",
620
- email: args.email,
621
- success: true,
622
- loopId: args.loopId,
623
- });
624
-
625
- return { success: true };
626
680
  },
627
681
  });
628
682
 
@@ -694,7 +748,8 @@ export const findContact = za({
694
748
 
695
749
  /**
696
750
  * Batch create contacts
697
- * Create multiple contacts in a single API call
751
+ * Creates multiple contacts sequentially using the single contact create endpoint.
752
+ * Note: Loops.so doesn't have a batch endpoint, so we create contacts one by one.
698
753
  */
699
754
  export const batchCreateContacts = za({
700
755
  args: z.object({
@@ -704,40 +759,53 @@ export const batchCreateContacts = za({
704
759
  returns: z.object({
705
760
  success: z.boolean(),
706
761
  created: z.number().optional(),
762
+ failed: z.number().optional(),
763
+ results: z.array(z.object({
764
+ email: z.string(),
765
+ success: z.boolean(),
766
+ error: z.string().optional(),
767
+ })).optional(),
707
768
  }),
708
769
  handler: async (ctx, args) => {
709
- const response = await fetch(`${LOOPS_API_BASE_URL}/contacts/batch`, {
710
- method: "POST",
711
- headers: {
712
- Authorization: `Bearer ${args.apiKey}`,
713
- "Content-Type": "application/json",
714
- },
715
- body: JSON.stringify({ contacts: args.contacts }),
716
- });
717
-
718
- if (!response.ok) {
719
- const errorText = await response.text();
720
- console.error(`Loops API error [${response.status}]:`, errorText);
721
- throw sanitizeError(response.status, errorText);
722
- }
723
-
724
- const data = (await response.json()) as { created?: number };
770
+ let created = 0;
771
+ let failed = 0;
772
+ const results: Array<{ email: string; success: boolean; error?: string }> = [];
725
773
 
774
+ // Create contacts one by one since Loops.so doesn't have a batch endpoint
726
775
  for (const contact of args.contacts) {
727
- await ctx.runMutation(((internal as any).lib.storeContact) as any, {
728
- email: contact.email,
729
- firstName: contact.firstName,
730
- lastName: contact.lastName,
731
- userId: contact.userId,
732
- source: contact.source,
733
- subscribed: contact.subscribed,
734
- userGroup: contact.userGroup,
735
- });
776
+ try {
777
+ // Use the addContact function which handles create/update logic
778
+ const result = await ctx.runAction(((internal as any).lib).addContact as any, {
779
+ apiKey: args.apiKey,
780
+ contact,
781
+ });
782
+
783
+ if (result.success) {
784
+ created++;
785
+ results.push({ email: contact.email, success: true });
786
+ } else {
787
+ failed++;
788
+ results.push({
789
+ email: contact.email,
790
+ success: false,
791
+ error: "Unknown error"
792
+ });
793
+ }
794
+ } catch (error) {
795
+ failed++;
796
+ results.push({
797
+ email: contact.email,
798
+ success: false,
799
+ error: error instanceof Error ? error.message : String(error),
800
+ });
801
+ }
736
802
  }
737
803
 
738
804
  return {
739
- success: true,
740
- created: data.created ?? args.contacts.length,
805
+ success: created > 0,
806
+ created,
807
+ failed,
808
+ results,
741
809
  };
742
810
  },
743
811
  });