@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 +1 -24
- package/dist/client/index.d.ts +13 -16
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +15 -26
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/convex.config.js +0 -1
- package/dist/component/lib.d.ts +0 -7
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +0 -104
- package/package.json +1 -1
- package/src/client/index.ts +25 -41
- package/src/component/convex.config.ts +0 -1
- package/src/component/lib.ts +0 -121
package/README.md
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
[](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
|
|
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
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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,
|
|
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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"}
|
package/dist/client/index.js
CHANGED
|
@@ -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,
|
|
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;
|
|
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,
|
package/dist/component/lib.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/component/lib.js
CHANGED
|
@@ -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
package/src/client/index.ts
CHANGED
|
@@ -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,
|
|
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,
|
package/src/component/lib.ts
CHANGED
|
@@ -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.
|