@devwithbobby/loops 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@devwithbobby/loops.svg)](https://www.npmjs.com/package/@devwithbobby/loops)
4
4
 
5
- A Convex component for integrating with [Loops.so](https://loops.so) email marketing platform. Send transactional emails, manage contacts, trigger campaigns and loops, and monitor email operations with built-in spam detection and rate limiting.
5
+ A Convex component for integrating with [Loops.so](https://loops.so) email marketing platform. Send transactional emails, manage contacts, trigger loops, and monitor email operations with built-in spam detection and rate limiting.
6
6
 
7
7
  ## Features
8
8
 
9
9
  - ✅ **Contact Management** - Create, update, find, and delete contacts
10
10
  - ✅ **Transactional Emails** - Send one-off emails with templates
11
11
  - ✅ **Events** - Trigger email workflows based on events
12
- - ✅ **Campaigns** - Send campaigns to audiences or specific contacts
13
12
  - ✅ **Loops** - Trigger automated email sequences
14
13
  - ✅ **Monitoring** - Track all email operations with spam detection
15
14
  - ✅ **Rate Limiting** - Built-in rate limiting queries for abuse prevention
@@ -201,26 +200,6 @@ await loops.sendEvent(ctx, {
201
200
  });
202
201
  ```
203
202
 
204
- #### Send Campaign
205
-
206
- ```typescript
207
- // Send to specific emails
208
- await loops.sendCampaign(ctx, {
209
- campaignId: "campaign-id-from-loops",
210
- emails: ["user1@example.com", "user2@example.com"],
211
- dataVariables: { discount: "20%" },
212
- });
213
-
214
- // Send to audience
215
- await loops.sendCampaign(ctx, {
216
- campaignId: "campaign-id-from-loops",
217
- audienceFilters: {
218
- userGroup: "premium",
219
- source: "webapp",
220
- },
221
- });
222
- ```
223
-
224
203
  #### Trigger Loop (Automated Sequence)
225
204
 
226
205
  ```typescript
@@ -354,7 +333,6 @@ export const {
354
333
  updateContact,
355
334
  sendTransactional,
356
335
  sendEvent,
357
- sendCampaign,
358
336
  triggerLoop,
359
337
  countContacts,
360
338
  // ... all other functions
@@ -488,7 +466,6 @@ This component implements the following Loops.so API endpoints:
488
466
  - ✅ Count Contacts (custom implementation)
489
467
  - ✅ Send Transactional Email
490
468
  - ✅ Send Event
491
- - ✅ Send Campaign
492
469
  - ✅ Trigger Loop
493
470
 
494
471
  ## Contributing
@@ -77,6 +77,18 @@ export declare class Loops {
77
77
  source?: string;
78
78
  subscribed?: boolean;
79
79
  }): Promise<FunctionReturnType<Query>>;
80
+ /**
81
+ * List contacts with pagination and optional filters
82
+ * Returns actual contact data, not just a count
83
+ * This queries the component's local database, not Loops API
84
+ */
85
+ listContacts(ctx: RunQueryCtx, options?: {
86
+ userGroup?: string;
87
+ source?: string;
88
+ subscribed?: boolean;
89
+ limit?: number;
90
+ offset?: number;
91
+ }): Promise<FunctionReturnType<Query>>;
80
92
  /**
81
93
  * Detect spam patterns: emails sent to the same recipient too frequently
82
94
  */
@@ -131,20 +143,6 @@ export declare class Loops {
131
143
  * Delete a contact from Loops
132
144
  */
133
145
  deleteContact(ctx: RunActionCtx, email: string): Promise<FunctionReturnType<Action>>;
134
- /**
135
- * Send a campaign to contacts
136
- * Campaigns are one-time email sends to a segment or list of contacts
137
- */
138
- sendCampaign(ctx: RunActionCtx, options: {
139
- campaignId: string;
140
- emails?: string[];
141
- transactionalId?: string;
142
- dataVariables?: Record<string, any>;
143
- audienceFilters?: {
144
- userGroup?: string;
145
- source?: string;
146
- };
147
- }): Promise<FunctionReturnType<Action>>;
148
146
  /**
149
147
  * Trigger a loop for a contact
150
148
  * Loops are automated email sequences that can be triggered by events
@@ -165,7 +163,7 @@ export declare class Loops {
165
163
  * For easy re-exporting.
166
164
  * Apps can do
167
165
  * ```ts
168
- * export const { addContact, sendTransactional, sendEvent, sendCampaign, triggerLoop } = loops.api();
166
+ * export const { addContact, sendTransactional, sendEvent, triggerLoop } = loops.api();
169
167
  * ```
170
168
  */
171
169
  api(): {
@@ -174,7 +172,6 @@ export declare class Loops {
174
172
  sendTransactional: any;
175
173
  sendEvent: any;
176
174
  deleteContact: any;
177
- sendCampaign: any;
178
175
  triggerLoop: any;
179
176
  findContact: any;
180
177
  batchCreateContacts: any;
@@ -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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"}
@@ -125,6 +125,20 @@ export class Loops {
125
125
  async countContacts(ctx, options) {
126
126
  return ctx.runQuery(this.component.lib.countContacts, options ?? {});
127
127
  }
128
+ /**
129
+ * List contacts with pagination and optional filters
130
+ * Returns actual contact data, not just a count
131
+ * This queries the component's local database, not Loops API
132
+ */
133
+ async listContacts(ctx, options) {
134
+ return ctx.runQuery(this.component.lib.listContacts, {
135
+ userGroup: options?.userGroup,
136
+ source: options?.source,
137
+ subscribed: options?.subscribed,
138
+ limit: options?.limit ?? 100,
139
+ offset: options?.offset ?? 0,
140
+ });
141
+ }
128
142
  /**
129
143
  * Detect spam patterns: emails sent to the same recipient too frequently
130
144
  */
@@ -187,16 +201,6 @@ export class Loops {
187
201
  email,
188
202
  });
189
203
  }
190
- /**
191
- * Send a campaign to contacts
192
- * Campaigns are one-time email sends to a segment or list of contacts
193
- */
194
- async sendCampaign(ctx, options) {
195
- return ctx.runAction(this.component.lib.sendCampaign, {
196
- apiKey: this.apiKey,
197
- ...options,
198
- });
199
- }
200
204
  /**
201
205
  * Trigger a loop for a contact
202
206
  * Loops are automated email sequences that can be triggered by events
@@ -217,7 +221,7 @@ export class Loops {
217
221
  * For easy re-exporting.
218
222
  * Apps can do
219
223
  * ```ts
220
- * export const { addContact, sendTransactional, sendEvent, sendCampaign, triggerLoop } = loops.api();
224
+ * export const { addContact, sendTransactional, sendEvent, triggerLoop } = loops.api();
221
225
  * ```
222
226
  */
223
227
  api() {
@@ -280,21 +284,6 @@ export class Loops {
280
284
  return await this.deleteContact(ctx, args.email);
281
285
  },
282
286
  }),
283
- sendCampaign: actionGeneric({
284
- args: {
285
- campaignId: v.string(),
286
- emails: v.optional(v.array(v.string())),
287
- transactionalId: v.optional(v.string()),
288
- dataVariables: v.optional(v.any()),
289
- audienceFilters: v.optional(v.object({
290
- userGroup: v.optional(v.string()),
291
- source: v.optional(v.string()),
292
- })),
293
- },
294
- handler: async (ctx, args) => {
295
- return await this.sendCampaign(ctx, args);
296
- },
297
- }),
298
287
  triggerLoop: actionGeneric({
299
288
  args: {
300
289
  loopId: v.string(),
@@ -1 +1 @@
1
- {"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,SAAS,KAA2B,CAAC;AAwB3C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,SAAS,KAA2B,CAAC;AAuB3C,eAAe,SAAS,CAAC"}
@@ -12,7 +12,6 @@ component.export(api, {
12
12
  listContacts: api.lib.listContacts,
13
13
  sendTransactional: api.lib.sendTransactional,
14
14
  sendEvent: api.lib.sendEvent,
15
- sendCampaign: api.lib.sendCampaign,
16
15
  triggerLoop: api.lib.triggerLoop,
17
16
  deleteContact: api.lib.deleteContact,
18
17
  detectRecipientSpam: api.lib.detectRecipientSpam,
@@ -43,13 +43,6 @@ export declare const sendEvent: any;
43
43
  * Delete a contact from Loops
44
44
  */
45
45
  export declare const deleteContact: any;
46
- /**
47
- * Send a campaign to contacts
48
- * Note: Campaigns in Loops.so are typically managed from the dashboard.
49
- * This function sends transactional emails to multiple contacts as a workaround.
50
- * If you need true campaign functionality, use the Loops.so dashboard or contact their support.
51
- */
52
- export declare const sendCampaign: any;
53
46
  /**
54
47
  * Trigger a loop for a contact
55
48
  * Note: Loops in Loops.so are triggered through events, not a direct API endpoint.
@@ -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,YAAY,KA8EvB,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"}
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,YAAY,KA8EvB,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;;;;;;;;;;;;;;;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"}
@@ -528,110 +528,6 @@ export const deleteContact = za({
528
528
  return { success: true };
529
529
  },
530
530
  });
531
- /**
532
- * Send a campaign to contacts
533
- * Note: Campaigns in Loops.so are typically managed from the dashboard.
534
- * This function sends transactional emails to multiple contacts as a workaround.
535
- * If you need true campaign functionality, use the Loops.so dashboard or contact their support.
536
- */
537
- export const sendCampaign = za({
538
- args: z.object({
539
- apiKey: z.string(),
540
- campaignId: z.string(),
541
- emails: z.array(z.string().email()).optional(),
542
- transactionalId: z.string().optional(),
543
- dataVariables: z.record(z.string(), z.any()).optional(),
544
- audienceFilters: z
545
- .object({
546
- userGroup: z.string().optional(),
547
- source: z.string().optional(),
548
- })
549
- .optional(),
550
- }),
551
- returns: z.object({
552
- success: z.boolean(),
553
- messageId: z.string().optional(),
554
- sent: z.number().optional(),
555
- errors: z.array(z.object({
556
- email: z.string(),
557
- error: z.string(),
558
- })).optional(),
559
- }),
560
- handler: async (ctx, args) => {
561
- // Loops.so doesn't have a campaigns API endpoint
562
- // As a workaround, we'll send transactional emails to the specified contacts
563
- if (!args.transactionalId) {
564
- throw new Error("Campaigns require a transactionalId. " +
565
- "Loops.so campaigns are managed from the dashboard. " +
566
- "This function sends transactional emails to multiple contacts as a workaround. " +
567
- "Please provide a transactionalId to send emails.");
568
- }
569
- if (!args.emails || args.emails.length === 0) {
570
- // If no emails provided but audienceFilters are, we need to query contacts
571
- if (args.audienceFilters) {
572
- // Query contacts from our database based on filters
573
- const contacts = await ctx.runQuery((internal.lib).countContacts, {
574
- userGroup: args.audienceFilters.userGroup,
575
- source: args.audienceFilters.source,
576
- });
577
- if (contacts === 0) {
578
- return {
579
- success: false,
580
- sent: 0,
581
- errors: [{ email: "audience", error: "No contacts found matching filters" }],
582
- };
583
- }
584
- // Note: We can't get email list from countContacts, so this is a limitation
585
- throw new Error("Campaigns with audienceFilters require emails to be specified. " +
586
- "Please provide the emails array with contacts to send to.");
587
- }
588
- throw new Error("Campaigns require either emails array or audienceFilters. " +
589
- "Please provide at least one email address or use transactional emails for single contacts.");
590
- }
591
- // Send transactional emails to each contact as a workaround for campaigns
592
- let sent = 0;
593
- const errors = [];
594
- for (const email of args.emails) {
595
- try {
596
- await ctx.runAction((internal.lib).sendTransactional, {
597
- apiKey: args.apiKey,
598
- transactionalId: args.transactionalId,
599
- email,
600
- dataVariables: args.dataVariables,
601
- });
602
- sent++;
603
- // Log as campaign operation
604
- await ctx.runMutation((internal.lib).logEmailOperation, {
605
- operationType: "campaign",
606
- email,
607
- success: true,
608
- campaignId: args.campaignId,
609
- transactionalId: args.transactionalId,
610
- });
611
- }
612
- catch (error) {
613
- errors.push({
614
- email,
615
- error: error instanceof Error ? error.message : String(error),
616
- });
617
- // Log failed campaign operation
618
- await ctx.runMutation((internal.lib).logEmailOperation, {
619
- operationType: "campaign",
620
- email,
621
- success: false,
622
- campaignId: args.campaignId,
623
- transactionalId: args.transactionalId,
624
- metadata: { error: error instanceof Error ? error.message : String(error) },
625
- });
626
- }
627
- }
628
- return {
629
- success: sent > 0,
630
- sent,
631
- errors: errors.length > 0 ? errors : undefined,
632
- };
633
- },
634
- });
635
531
  /**
636
532
  * Trigger a loop for a contact
637
533
  * Note: Loops in Loops.so are triggered through events, not a direct API endpoint.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devwithbobby/loops",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Convex component for integrating with Loops.so email marketing platform",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -201,6 +201,30 @@ export class Loops {
201
201
  return ctx.runQuery((this.component.lib as any).countContacts, options ?? {});
202
202
  }
203
203
 
204
+ /**
205
+ * List contacts with pagination and optional filters
206
+ * Returns actual contact data, not just a count
207
+ * This queries the component's local database, not Loops API
208
+ */
209
+ async listContacts(
210
+ ctx: RunQueryCtx,
211
+ options?: {
212
+ userGroup?: string;
213
+ source?: string;
214
+ subscribed?: boolean;
215
+ limit?: number;
216
+ offset?: number;
217
+ },
218
+ ) {
219
+ return ctx.runQuery((this.component.lib as any).listContacts, {
220
+ userGroup: options?.userGroup,
221
+ source: options?.source,
222
+ subscribed: options?.subscribed,
223
+ limit: options?.limit ?? 100,
224
+ offset: options?.offset ?? 0,
225
+ });
226
+ }
227
+
204
228
  /**
205
229
  * Detect spam patterns: emails sent to the same recipient too frequently
206
230
  */
@@ -314,29 +338,6 @@ export class Loops {
314
338
  });
315
339
  }
316
340
 
317
- /**
318
- * Send a campaign to contacts
319
- * Campaigns are one-time email sends to a segment or list of contacts
320
- */
321
- async sendCampaign(
322
- ctx: RunActionCtx,
323
- options: {
324
- campaignId: string;
325
- emails?: string[];
326
- transactionalId?: string;
327
- dataVariables?: Record<string, any>;
328
- audienceFilters?: {
329
- userGroup?: string;
330
- source?: string;
331
- };
332
- },
333
- ) {
334
- return ctx.runAction((this.component.lib as any).sendCampaign, {
335
- apiKey: this.apiKey,
336
- ...options,
337
- });
338
- }
339
-
340
341
  /**
341
342
  * Trigger a loop for a contact
342
343
  * Loops are automated email sequences that can be triggered by events
@@ -366,7 +367,7 @@ export class Loops {
366
367
  * For easy re-exporting.
367
368
  * Apps can do
368
369
  * ```ts
369
- * export const { addContact, sendTransactional, sendEvent, sendCampaign, triggerLoop } = loops.api();
370
+ * export const { addContact, sendTransactional, sendEvent, triggerLoop } = loops.api();
370
371
  * ```
371
372
  */
372
373
  api() {
@@ -429,23 +430,6 @@ export class Loops {
429
430
  return await this.deleteContact(ctx, args.email);
430
431
  },
431
432
  }),
432
- sendCampaign: actionGeneric({
433
- args: {
434
- campaignId: v.string(),
435
- emails: v.optional(v.array(v.string())),
436
- transactionalId: v.optional(v.string()),
437
- dataVariables: v.optional(v.any()),
438
- audienceFilters: v.optional(
439
- v.object({
440
- userGroup: v.optional(v.string()),
441
- source: v.optional(v.string()),
442
- }),
443
- ),
444
- },
445
- handler: async (ctx, args) => {
446
- return await this.sendCampaign(ctx, args);
447
- },
448
- }),
449
433
  triggerLoop: actionGeneric({
450
434
  args: {
451
435
  loopId: v.string(),
@@ -13,7 +13,6 @@ const component = defineComponent("loops");
13
13
  listContacts: api.lib.listContacts,
14
14
  sendTransactional: api.lib.sendTransactional,
15
15
  sendEvent: api.lib.sendEvent,
16
- sendCampaign: api.lib.sendCampaign,
17
16
  triggerLoop: api.lib.triggerLoop,
18
17
  deleteContact: api.lib.deleteContact,
19
18
  detectRecipientSpam: api.lib.detectRecipientSpam,
@@ -569,127 +569,6 @@ export const deleteContact = za({
569
569
  },
570
570
  });
571
571
 
572
- /**
573
- * Send a campaign to contacts
574
- * Note: Campaigns in Loops.so are typically managed from the dashboard.
575
- * This function sends transactional emails to multiple contacts as a workaround.
576
- * If you need true campaign functionality, use the Loops.so dashboard or contact their support.
577
- */
578
- export const sendCampaign = za({
579
- args: z.object({
580
- apiKey: z.string(),
581
- campaignId: z.string(),
582
- emails: z.array(z.string().email()).optional(),
583
- transactionalId: z.string().optional(),
584
- dataVariables: z.record(z.string(), z.any()).optional(),
585
- audienceFilters: z
586
- .object({
587
- userGroup: z.string().optional(),
588
- source: z.string().optional(),
589
- })
590
- .optional(),
591
- }),
592
- returns: z.object({
593
- success: z.boolean(),
594
- messageId: z.string().optional(),
595
- sent: z.number().optional(),
596
- errors: z.array(z.object({
597
- email: z.string(),
598
- error: z.string(),
599
- })).optional(),
600
- }),
601
- handler: async (ctx, args) => {
602
- // Loops.so doesn't have a campaigns API endpoint
603
- // As a workaround, we'll send transactional emails to the specified contacts
604
-
605
- if (!args.transactionalId) {
606
- throw new Error(
607
- "Campaigns require a transactionalId. " +
608
- "Loops.so campaigns are managed from the dashboard. " +
609
- "This function sends transactional emails to multiple contacts as a workaround. " +
610
- "Please provide a transactionalId to send emails."
611
- );
612
- }
613
-
614
- if (!args.emails || args.emails.length === 0) {
615
- // If no emails provided but audienceFilters are, we need to query contacts
616
- if (args.audienceFilters) {
617
- // Query contacts from our database based on filters
618
- const contacts = await ctx.runQuery(((internal as any).lib).countContacts as any, {
619
- userGroup: args.audienceFilters.userGroup,
620
- source: args.audienceFilters.source,
621
- });
622
-
623
- if (contacts === 0) {
624
- return {
625
- success: false,
626
- sent: 0,
627
- errors: [{ email: "audience", error: "No contacts found matching filters" }],
628
- };
629
- }
630
-
631
- // Note: We can't get email list from countContacts, so this is a limitation
632
- throw new Error(
633
- "Campaigns with audienceFilters require emails to be specified. " +
634
- "Please provide the emails array with contacts to send to."
635
- );
636
- }
637
-
638
- throw new Error(
639
- "Campaigns require either emails array or audienceFilters. " +
640
- "Please provide at least one email address or use transactional emails for single contacts."
641
- );
642
- }
643
-
644
- // Send transactional emails to each contact as a workaround for campaigns
645
- let sent = 0;
646
- const errors: Array<{ email: string; error: string }> = [];
647
-
648
- for (const email of args.emails) {
649
- try {
650
- await ctx.runAction(((internal as any).lib).sendTransactional as any, {
651
- apiKey: args.apiKey,
652
- transactionalId: args.transactionalId!,
653
- email,
654
- dataVariables: args.dataVariables,
655
- });
656
-
657
- sent++;
658
-
659
- // Log as campaign operation
660
- await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
661
- operationType: "campaign",
662
- email,
663
- success: true,
664
- campaignId: args.campaignId,
665
- transactionalId: args.transactionalId,
666
- });
667
- } catch (error) {
668
- errors.push({
669
- email,
670
- error: error instanceof Error ? error.message : String(error),
671
- });
672
-
673
- // Log failed campaign operation
674
- await ctx.runMutation(((internal as any).lib).logEmailOperation as any, {
675
- operationType: "campaign",
676
- email,
677
- success: false,
678
- campaignId: args.campaignId,
679
- transactionalId: args.transactionalId,
680
- metadata: { error: error instanceof Error ? error.message : String(error) },
681
- });
682
- }
683
- }
684
-
685
- return {
686
- success: sent > 0,
687
- sent,
688
- errors: errors.length > 0 ? errors : undefined,
689
- };
690
- },
691
- });
692
-
693
572
  /**
694
573
  * Trigger a loop for a contact
695
574
  * Note: Loops in Loops.so are triggered through events, not a direct API endpoint.