@growth-labs/mailer 0.1.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.
- package/README.md +89 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/tracking.d.ts +3 -0
- package/dist/middleware/tracking.d.ts.map +1 -0
- package/dist/middleware/tracking.js +13 -0
- package/dist/middleware/tracking.js.map +1 -0
- package/dist/options.d.ts +160 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +51 -0
- package/dist/options.js.map +1 -0
- package/dist/queue/consumer.d.ts +8 -0
- package/dist/queue/consumer.d.ts.map +1 -0
- package/dist/queue/consumer.js +83 -0
- package/dist/queue/consumer.js.map +1 -0
- package/dist/routes/confirm.d.ts +3 -0
- package/dist/routes/confirm.d.ts.map +1 -0
- package/dist/routes/confirm.js +59 -0
- package/dist/routes/confirm.js.map +1 -0
- package/dist/routes/subscribe.d.ts +3 -0
- package/dist/routes/subscribe.d.ts.map +1 -0
- package/dist/routes/subscribe.js +87 -0
- package/dist/routes/subscribe.js.map +1 -0
- package/dist/routes/track-click.d.ts +3 -0
- package/dist/routes/track-click.d.ts.map +1 -0
- package/dist/routes/track-click.js +45 -0
- package/dist/routes/track-click.js.map +1 -0
- package/dist/routes/track-open.d.ts +3 -0
- package/dist/routes/track-open.d.ts.map +1 -0
- package/dist/routes/track-open.js +40 -0
- package/dist/routes/track-open.js.map +1 -0
- package/dist/routes/unsubscribe.d.ts +4 -0
- package/dist/routes/unsubscribe.d.ts.map +1 -0
- package/dist/routes/unsubscribe.js +81 -0
- package/dist/routes/unsubscribe.js.map +1 -0
- package/dist/routes/webhook.d.ts +3 -0
- package/dist/routes/webhook.d.ts.map +1 -0
- package/dist/routes/webhook.js +30 -0
- package/dist/routes/webhook.js.map +1 -0
- package/dist/schema.d.ts +564 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +47 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/bindings.d.ts +20 -0
- package/dist/utils/bindings.d.ts.map +1 -0
- package/dist/utils/bindings.js +19 -0
- package/dist/utils/bindings.js.map +1 -0
- package/dist/utils/bounce.d.ts +29 -0
- package/dist/utils/bounce.d.ts.map +1 -0
- package/dist/utils/bounce.js +59 -0
- package/dist/utils/bounce.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/providers.d.ts +31 -0
- package/dist/utils/providers.d.ts.map +1 -0
- package/dist/utils/providers.js +109 -0
- package/dist/utils/providers.js.map +1 -0
- package/dist/utils/scheduling.d.ts +89 -0
- package/dist/utils/scheduling.d.ts.map +1 -0
- package/dist/utils/scheduling.js +110 -0
- package/dist/utils/scheduling.js.map +1 -0
- package/dist/utils/send.d.ts +42 -0
- package/dist/utils/send.d.ts.map +1 -0
- package/dist/utils/send.js +193 -0
- package/dist/utils/send.js.map +1 -0
- package/dist/utils/subscribers.d.ts +23 -0
- package/dist/utils/subscribers.d.ts.map +1 -0
- package/dist/utils/subscribers.js +200 -0
- package/dist/utils/subscribers.js.map +1 -0
- package/dist/utils/templates.d.ts +16 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +426 -0
- package/dist/utils/templates.js.map +1 -0
- package/dist/utils/tokens.d.ts +13 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +62 -0
- package/dist/utils/tokens.js.map +1 -0
- package/dist/utils/tracking.d.ts +26 -0
- package/dist/utils/tracking.d.ts.map +1 -0
- package/dist/utils/tracking.js +49 -0
- package/dist/utils/tracking.js.map +1 -0
- package/dist/utils/urls.d.ts +7 -0
- package/dist/utils/urls.d.ts.map +1 -0
- package/dist/utils/urls.js +34 -0
- package/dist/utils/urls.js.map +1 -0
- package/dist/vite-plugin.d.ts +4 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +18 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +85 -0
- package/src/astro.d.ts +4 -0
- package/src/components/PreferenceCenter.astro +147 -0
- package/src/components/SubscribeForm.astro +161 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +101 -0
- package/src/middleware/tracking.ts +18 -0
- package/src/options.ts +65 -0
- package/src/queue/consumer.ts +99 -0
- package/src/routes/confirm.ts +68 -0
- package/src/routes/preferences.astro +137 -0
- package/src/routes/subscribe.ts +107 -0
- package/src/routes/track-click.ts +57 -0
- package/src/routes/track-open.ts +51 -0
- package/src/routes/unsubscribe.ts +96 -0
- package/src/routes/webhook.ts +48 -0
- package/src/schema.ts +56 -0
- package/src/types.ts +145 -0
- package/src/utils/bindings.ts +28 -0
- package/src/utils/bounce.ts +77 -0
- package/src/utils/index.ts +47 -0
- package/src/utils/providers.ts +141 -0
- package/src/utils/scheduling.ts +188 -0
- package/src/utils/send.ts +282 -0
- package/src/utils/subscribers.ts +277 -0
- package/src/utils/templates.ts +459 -0
- package/src/utils/tokens.ts +91 -0
- package/src/utils/tracking.ts +58 -0
- package/src/utils/urls.ts +49 -0
- package/src/virtual.d.ts +32 -0
- package/src/vite-plugin.ts +21 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export interface Subscriber {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
status: SubscriberStatus;
|
|
6
|
+
preferences: string[];
|
|
7
|
+
source: string;
|
|
8
|
+
attribution?: SubscriberAttribution;
|
|
9
|
+
softBounceCount: number;
|
|
10
|
+
subscribedAt: string;
|
|
11
|
+
confirmedAt?: string;
|
|
12
|
+
unsubscribedAt?: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export type SubscriberStatus = 'pending' | 'active' | 'unsubscribed' | 'bounced' | 'complained';
|
|
17
|
+
export interface SubscriberAttribution {
|
|
18
|
+
derivedSource: string;
|
|
19
|
+
utmSource?: string;
|
|
20
|
+
utmMedium?: string;
|
|
21
|
+
utmCampaign?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SubscriberFilter {
|
|
24
|
+
status?: SubscriberStatus[];
|
|
25
|
+
preferences?: string[];
|
|
26
|
+
subscribedAfter?: string;
|
|
27
|
+
subscribedBefore?: string;
|
|
28
|
+
source?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface EmailQueueMessage {
|
|
31
|
+
type: 'transactional' | 'campaign' | 'digest';
|
|
32
|
+
recipients: QueueRecipient[];
|
|
33
|
+
subject: string;
|
|
34
|
+
htmlTemplate: string;
|
|
35
|
+
from: string;
|
|
36
|
+
replyTo?: string;
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
campaignId?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface QueueRecipient {
|
|
41
|
+
email: string;
|
|
42
|
+
subscriberId: string;
|
|
43
|
+
trackingId: string;
|
|
44
|
+
unsubscribeToken: string;
|
|
45
|
+
preferencesToken: string;
|
|
46
|
+
}
|
|
47
|
+
export interface EmailSend {
|
|
48
|
+
id: string;
|
|
49
|
+
subscriberId: string;
|
|
50
|
+
campaignId?: string;
|
|
51
|
+
email: string;
|
|
52
|
+
subject: string;
|
|
53
|
+
type: 'transactional' | 'campaign' | 'digest';
|
|
54
|
+
status: EmailSendStatus;
|
|
55
|
+
sentAt?: string;
|
|
56
|
+
deliveredAt?: string;
|
|
57
|
+
openedAt?: string;
|
|
58
|
+
clickedAt?: string;
|
|
59
|
+
bouncedAt?: string;
|
|
60
|
+
bounceType?: 'hard' | 'soft';
|
|
61
|
+
complainedAt?: string;
|
|
62
|
+
trackingId: string;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
}
|
|
65
|
+
export type EmailSendStatus = 'queued' | 'sent' | 'delivered' | 'opened' | 'clicked' | 'bounced' | 'complained';
|
|
66
|
+
export interface EmailProvider {
|
|
67
|
+
send(email: OutboundEmail): Promise<SendResult>;
|
|
68
|
+
readonly name: string;
|
|
69
|
+
}
|
|
70
|
+
export interface OutboundEmail {
|
|
71
|
+
to: string;
|
|
72
|
+
from: string;
|
|
73
|
+
replyTo?: string;
|
|
74
|
+
subject: string;
|
|
75
|
+
html: string;
|
|
76
|
+
headers?: Record<string, string>;
|
|
77
|
+
}
|
|
78
|
+
export interface SendResult {
|
|
79
|
+
success: boolean;
|
|
80
|
+
messageId?: string;
|
|
81
|
+
error?: string;
|
|
82
|
+
retryable: boolean;
|
|
83
|
+
}
|
|
84
|
+
export type TemplateName = 'confirmation' | 'welcome' | 'campaign' | 'digest' | 'transactional' | 'unsubscribe-confirm';
|
|
85
|
+
export interface TemplateData {
|
|
86
|
+
senderName: string;
|
|
87
|
+
siteUrl: string;
|
|
88
|
+
unsubscribeUrl: string;
|
|
89
|
+
preferencesUrl: string;
|
|
90
|
+
brand: {
|
|
91
|
+
logoUrl?: string;
|
|
92
|
+
primaryColor: string;
|
|
93
|
+
accentColor: string;
|
|
94
|
+
footerText?: string;
|
|
95
|
+
};
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
}
|
|
98
|
+
export interface DigestItem {
|
|
99
|
+
title: string;
|
|
100
|
+
url: string;
|
|
101
|
+
description?: string;
|
|
102
|
+
imageUrl?: string;
|
|
103
|
+
publishedAt?: string;
|
|
104
|
+
author?: string;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qBAAqB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
type DrizzleDB = ReturnType<typeof drizzle>;
|
|
3
|
+
export interface RuntimeLocals {
|
|
4
|
+
runtime?: {
|
|
5
|
+
env: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve Cloudflare bindings from the runtime environment object.
|
|
10
|
+
*
|
|
11
|
+
* Route handlers call this with `context.locals.runtime.env` (the standard
|
|
12
|
+
* Astro 6 + @astrojs/cloudflare adapter pattern). Binding names come from
|
|
13
|
+
* the virtual config so they stay in sync with the consumer's `astro.config`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveBindings(runtimeEnv: Record<string, unknown>): {
|
|
16
|
+
db: DrizzleDB;
|
|
17
|
+
queue: Queue;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=bindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/utils/bindings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAE3C,MAAM,WAAW,aAAa;IAC7B,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACrE,EAAE,EAAE,SAAS,CAAA;IACb,KAAK,EAAE,KAAK,CAAA;CACZ,CAQA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { config } from 'virtual:growth-labs/mailer/config';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve Cloudflare bindings from the runtime environment object.
|
|
5
|
+
*
|
|
6
|
+
* Route handlers call this with `context.locals.runtime.env` (the standard
|
|
7
|
+
* Astro 6 + @astrojs/cloudflare adapter pattern). Binding names come from
|
|
8
|
+
* the virtual config so they stay in sync with the consumer's `astro.config`.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveBindings(runtimeEnv) {
|
|
11
|
+
const d1 = runtimeEnv[config.d1Binding];
|
|
12
|
+
const queue = runtimeEnv[config.queueBinding];
|
|
13
|
+
if (!d1)
|
|
14
|
+
throw new Error(`[mailer] D1 binding "${config.d1Binding}" not found`);
|
|
15
|
+
if (!queue)
|
|
16
|
+
throw new Error(`[mailer] Queue binding "${config.queueBinding}" not found`);
|
|
17
|
+
return { db: drizzle(d1), queue };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=bindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.js","sourceRoot":"","sources":["../../src/utils/bindings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAQxC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,UAAmC;IAIlE,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,CAAe,CAAA;IACrD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,YAAY,CAAU,CAAA;IAEtD,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,SAAS,aAAa,CAAC,CAAA;IAC/E,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,YAAY,aAAa,CAAC,CAAA;IAExF,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAA;AAClC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
type DrizzleDB = ReturnType<typeof drizzle>;
|
|
3
|
+
/**
|
|
4
|
+
* Handle an email bounce notification.
|
|
5
|
+
*
|
|
6
|
+
* - **Hard bounce**: immediately sets subscriber status to `bounced`.
|
|
7
|
+
* - **Soft bounce**: increments `softBounceCount`; if the count reaches 3
|
|
8
|
+
* the subscriber is marked `bounced`.
|
|
9
|
+
*
|
|
10
|
+
* No-op when the email address has no matching subscriber row.
|
|
11
|
+
*/
|
|
12
|
+
export declare function handleBounce(db: DrizzleDB, email: string, bounceType: 'hard' | 'soft'): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Handle an email complaint (spam report).
|
|
15
|
+
* Sets the subscriber status to `complained`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function handleComplaint(db: DrizzleDB, email: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Record a successful delivery for an email send.
|
|
20
|
+
* Only updates if the current status is `sent` — never downgrades
|
|
21
|
+
* from `opened` or `clicked`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function handleDelivery(db: DrizzleDB, trackingId: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Generic helper to update a `gl_email_sends` row by tracking ID.
|
|
26
|
+
*/
|
|
27
|
+
export declare function updateSendStatus(db: DrizzleDB, trackingId: string, status: string, fields: Record<string, string | null>): Promise<void>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=bounce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounce.d.ts","sourceRoot":"","sources":["../../src/utils/bounce.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAG7C,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAE3C;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CACjC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GAAG,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjF;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { eq, sql } from 'drizzle-orm';
|
|
2
|
+
import { emailSends, subscribers } from '../schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Handle an email bounce notification.
|
|
5
|
+
*
|
|
6
|
+
* - **Hard bounce**: immediately sets subscriber status to `bounced`.
|
|
7
|
+
* - **Soft bounce**: increments `softBounceCount`; if the count reaches 3
|
|
8
|
+
* the subscriber is marked `bounced`.
|
|
9
|
+
*
|
|
10
|
+
* No-op when the email address has no matching subscriber row.
|
|
11
|
+
*/
|
|
12
|
+
export async function handleBounce(db, email, bounceType) {
|
|
13
|
+
const rows = await db.select().from(subscribers).where(eq(subscribers.email, email));
|
|
14
|
+
if (rows.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const subscriber = rows[0];
|
|
17
|
+
if (bounceType === 'hard') {
|
|
18
|
+
await db.update(subscribers).set({ status: 'bounced' }).where(eq(subscribers.email, email));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const newCount = subscriber.softBounceCount + 1;
|
|
22
|
+
const updates = { softBounceCount: newCount };
|
|
23
|
+
if (newCount >= 3) {
|
|
24
|
+
updates.status = 'bounced';
|
|
25
|
+
}
|
|
26
|
+
await db.update(subscribers).set(updates).where(eq(subscribers.email, email));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Handle an email complaint (spam report).
|
|
31
|
+
* Sets the subscriber status to `complained`.
|
|
32
|
+
*/
|
|
33
|
+
export async function handleComplaint(db, email) {
|
|
34
|
+
await db.update(subscribers).set({ status: 'complained' }).where(eq(subscribers.email, email));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Record a successful delivery for an email send.
|
|
38
|
+
* Only updates if the current status is `sent` — never downgrades
|
|
39
|
+
* from `opened` or `clicked`.
|
|
40
|
+
*/
|
|
41
|
+
export async function handleDelivery(db, trackingId) {
|
|
42
|
+
await db
|
|
43
|
+
.update(emailSends)
|
|
44
|
+
.set({
|
|
45
|
+
status: 'delivered',
|
|
46
|
+
deliveredAt: new Date().toISOString(),
|
|
47
|
+
})
|
|
48
|
+
.where(sql `${emailSends.trackingId} = ${trackingId} AND ${emailSends.status} = 'sent'`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generic helper to update a `gl_email_sends` row by tracking ID.
|
|
52
|
+
*/
|
|
53
|
+
export async function updateSendStatus(db, trackingId, status, fields) {
|
|
54
|
+
await db
|
|
55
|
+
.update(emailSends)
|
|
56
|
+
.set({ status, ...fields })
|
|
57
|
+
.where(eq(emailSends.trackingId, trackingId));
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=bounce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounce.js","sourceRoot":"","sources":["../../src/utils/bounce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAErC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAItD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,EAAa,EACb,KAAa,EACb,UAA2B;IAE3B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IAEpF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAE7B,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IAE1B,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;QAC3B,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IAC5F,CAAC;SAAM,CAAC;QACP,MAAM,QAAQ,GAAG,UAAU,CAAC,eAAe,GAAG,CAAC,CAAA;QAC/C,MAAM,OAAO,GAA4B,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAA;QAEtE,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAA;QAC3B,CAAC;QAED,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IAC9E,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAa,EAAE,KAAa;IACjE,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;AAC/F,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EAAa,EAAE,UAAkB;IACrE,MAAM,EAAE;SACN,MAAM,CAAC,UAAU,CAAC;SAClB,GAAG,CAAC;QACJ,MAAM,EAAE,WAAW;QACnB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;SACD,KAAK,CAAC,GAAG,CAAA,GAAG,UAAU,CAAC,UAAU,MAAM,UAAU,QAAQ,UAAU,CAAC,MAAM,WAAW,CAAC,CAAA;AACzF,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,EAAa,EACb,UAAkB,EAClB,MAAc,EACd,MAAqC;IAErC,MAAM,EAAE;SACN,MAAM,CAAC,UAAU,CAAC;SAClB,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;SAC1B,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA;AAC/C,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { handleBounce, handleComplaint, handleDelivery, updateSendStatus, } from './bounce.js';
|
|
2
|
+
export type { CloudflareEmailSender } from './providers.js';
|
|
3
|
+
export { CloudflareEmailProvider, getFallbackProvider, getProvider, ResendFallbackProvider, sleep, } from './providers.js';
|
|
4
|
+
export type { CampaignSchedule, DigestSchedule } from './scheduling.js';
|
|
5
|
+
export { executeCampaignSchedule, executeDigestSchedule, prepareCampaign, prepareDigest, sendBatchCampaigns, } from './scheduling.js';
|
|
6
|
+
export type { MailerEnv } from './send.js';
|
|
7
|
+
export { sendCampaign, sendDigest, sendTransactional } from './send.js';
|
|
8
|
+
export { confirmSubscriber, countSubscribers, createSubscriber, getSubscriberBatch, getSubscriberByEmail, getSubscriberById, unsubscribeSubscriber, updatePreferences, } from './subscribers.js';
|
|
9
|
+
export { inlineStyles, interpolate, processConditionals, renderDigestItems, renderEmail, } from './templates.js';
|
|
10
|
+
export { generateToken, verifyToken } from './tokens.js';
|
|
11
|
+
export { injectTrackingPixel, rewriteLinksForTracking, TRANSPARENT_GIF, } from './tracking.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GAChB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EACN,uBAAuB,EACvB,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,KAAK,GACL,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACvE,OAAO,EACN,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,kBAAkB,GAClB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AACvE,OAAO,EACN,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,WAAW,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACN,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,GACf,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { handleBounce, handleComplaint, handleDelivery, updateSendStatus, } from './bounce.js';
|
|
2
|
+
export { CloudflareEmailProvider, getFallbackProvider, getProvider, ResendFallbackProvider, sleep, } from './providers.js';
|
|
3
|
+
export { executeCampaignSchedule, executeDigestSchedule, prepareCampaign, prepareDigest, sendBatchCampaigns, } from './scheduling.js';
|
|
4
|
+
export { sendCampaign, sendDigest, sendTransactional } from './send.js';
|
|
5
|
+
export { confirmSubscriber, countSubscribers, createSubscriber, getSubscriberBatch, getSubscriberByEmail, getSubscriberById, unsubscribeSubscriber, updatePreferences, } from './subscribers.js';
|
|
6
|
+
export { inlineStyles, interpolate, processConditionals, renderDigestItems, renderEmail, } from './templates.js';
|
|
7
|
+
export { generateToken, verifyToken } from './tokens.js';
|
|
8
|
+
export { injectTrackingPixel, rewriteLinksForTracking, TRANSPARENT_GIF, } from './tracking.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GAChB,MAAM,aAAa,CAAA;AAEpB,OAAO,EACN,uBAAuB,EACvB,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,KAAK,GACL,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACN,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,kBAAkB,GAClB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AACvE,OAAO,EACN,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,WAAW,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACN,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,GACf,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { EmailProvider, OutboundEmail, SendResult } from '../types.js';
|
|
2
|
+
export interface CloudflareEmailSender {
|
|
3
|
+
send(message: {
|
|
4
|
+
to: string;
|
|
5
|
+
from: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
content: {
|
|
8
|
+
type: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}[];
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare class CloudflareEmailProvider implements EmailProvider {
|
|
14
|
+
readonly name = "cloudflare";
|
|
15
|
+
private readonly sender;
|
|
16
|
+
constructor(emailSender: CloudflareEmailSender);
|
|
17
|
+
send(email: OutboundEmail): Promise<SendResult>;
|
|
18
|
+
}
|
|
19
|
+
export declare class ResendFallbackProvider implements EmailProvider {
|
|
20
|
+
readonly name = "resend";
|
|
21
|
+
private readonly apiKey;
|
|
22
|
+
constructor(apiKey: string);
|
|
23
|
+
send(email: OutboundEmail): Promise<SendResult>;
|
|
24
|
+
}
|
|
25
|
+
export declare function getProvider(emailSender: CloudflareEmailSender): EmailProvider;
|
|
26
|
+
export declare function getFallbackProvider(options: {
|
|
27
|
+
fallbackProvider: string;
|
|
28
|
+
resendApiKey?: string;
|
|
29
|
+
}): EmailProvider | null;
|
|
30
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/utils/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAI3E,MAAM,WAAW,qBAAqB;IACrC,IAAI,CAAC,OAAO,EAAE;QACb,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC1C,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAID,qBAAa,uBAAwB,YAAW,aAAa;IAC5D,QAAQ,CAAC,IAAI,gBAAe;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;gBAElC,WAAW,EAAE,qBAAqB;IAIxC,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC;CAqBrD;AAID,qBAAa,sBAAuB,YAAW,aAAa;IAC3D,QAAQ,CAAC,IAAI,YAAW;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,MAAM,EAAE,MAAM;IAIpB,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC;CA4CrD;AAID,wBAAgB,WAAW,CAAC,WAAW,EAAE,qBAAqB,GAAG,aAAa,CAE7E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC5C,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,aAAa,GAAG,IAAI,CAMvB;AAID,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ─── CloudflareEmailProvider ───
|
|
2
|
+
export class CloudflareEmailProvider {
|
|
3
|
+
name = 'cloudflare';
|
|
4
|
+
sender;
|
|
5
|
+
constructor(emailSender) {
|
|
6
|
+
this.sender = emailSender;
|
|
7
|
+
}
|
|
8
|
+
async send(email) {
|
|
9
|
+
try {
|
|
10
|
+
await this.sender.send({
|
|
11
|
+
to: email.to,
|
|
12
|
+
from: email.from,
|
|
13
|
+
subject: email.subject,
|
|
14
|
+
content: [{ type: 'text/html', value: email.html }],
|
|
15
|
+
});
|
|
16
|
+
return { success: true, retryable: false };
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
20
|
+
const isRetryable = isNetworkOrServerError(message);
|
|
21
|
+
return {
|
|
22
|
+
success: false,
|
|
23
|
+
error: message,
|
|
24
|
+
retryable: isRetryable,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// ─── ResendFallbackProvider ───
|
|
30
|
+
export class ResendFallbackProvider {
|
|
31
|
+
name = 'resend';
|
|
32
|
+
apiKey;
|
|
33
|
+
constructor(apiKey) {
|
|
34
|
+
this.apiKey = apiKey;
|
|
35
|
+
}
|
|
36
|
+
async send(email) {
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch('https://api.resend.com/emails', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
to: email.to,
|
|
46
|
+
from: email.from,
|
|
47
|
+
reply_to: email.replyTo,
|
|
48
|
+
subject: email.subject,
|
|
49
|
+
html: email.html,
|
|
50
|
+
headers: email.headers,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
const data = (await response.json());
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
messageId: data.id,
|
|
58
|
+
retryable: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
62
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: `Resend API ${response.status}: ${errorText}`,
|
|
66
|
+
retryable,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: message,
|
|
74
|
+
retryable: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ─── Factory functions ───
|
|
80
|
+
export function getProvider(emailSender) {
|
|
81
|
+
return new CloudflareEmailProvider(emailSender);
|
|
82
|
+
}
|
|
83
|
+
export function getFallbackProvider(options) {
|
|
84
|
+
if (options.fallbackProvider === 'resend' && options.resendApiKey) {
|
|
85
|
+
return new ResendFallbackProvider(options.resendApiKey);
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// ─── Helpers ───
|
|
90
|
+
export function sleep(ms) {
|
|
91
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
92
|
+
}
|
|
93
|
+
function isNetworkOrServerError(message) {
|
|
94
|
+
const patterns = [
|
|
95
|
+
'network',
|
|
96
|
+
'timeout',
|
|
97
|
+
'econnrefused',
|
|
98
|
+
'econnreset',
|
|
99
|
+
'enotfound',
|
|
100
|
+
'socket',
|
|
101
|
+
'5',
|
|
102
|
+
'server error',
|
|
103
|
+
'service unavailable',
|
|
104
|
+
'internal server',
|
|
105
|
+
];
|
|
106
|
+
const lower = message.toLowerCase();
|
|
107
|
+
return patterns.some((p) => lower.includes(p));
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/utils/providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
import type { ResolvedMailerOptions } from '../options.js';
|
|
3
|
+
import type { DigestItem, SubscriberFilter } from '../types.js';
|
|
4
|
+
import type { MailerEnv } from './send.js';
|
|
5
|
+
type DrizzleDB = ReturnType<typeof drizzle>;
|
|
6
|
+
export interface CampaignSchedule {
|
|
7
|
+
campaignId: string;
|
|
8
|
+
subject: string;
|
|
9
|
+
html?: string;
|
|
10
|
+
template?: 'campaign';
|
|
11
|
+
data?: Record<string, unknown>;
|
|
12
|
+
filter?: SubscriberFilter;
|
|
13
|
+
scheduledAt: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DigestSchedule {
|
|
16
|
+
campaignId: string;
|
|
17
|
+
subject: string;
|
|
18
|
+
items: DigestItem[];
|
|
19
|
+
introText?: string;
|
|
20
|
+
filter?: SubscriberFilter;
|
|
21
|
+
scheduledAt: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a campaign schedule entry.
|
|
25
|
+
* The consumer stores this in KV/D1/R2 and uses a Cron Trigger to
|
|
26
|
+
* call `executeCampaignSchedule()` at the appropriate time.
|
|
27
|
+
*
|
|
28
|
+
* This is a lightweight helper — it pre-generates the campaignId
|
|
29
|
+
* and validates the send would have recipients.
|
|
30
|
+
*/
|
|
31
|
+
export declare function prepareCampaign(db: DrizzleDB, params: {
|
|
32
|
+
subject: string;
|
|
33
|
+
html?: string;
|
|
34
|
+
template?: 'campaign';
|
|
35
|
+
data?: Record<string, unknown>;
|
|
36
|
+
filter?: SubscriberFilter;
|
|
37
|
+
scheduledAt: string;
|
|
38
|
+
}): Promise<CampaignSchedule & {
|
|
39
|
+
estimatedRecipients: number;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Build a digest schedule entry with recipient estimate.
|
|
43
|
+
*/
|
|
44
|
+
export declare function prepareDigest(db: DrizzleDB, params: {
|
|
45
|
+
subject: string;
|
|
46
|
+
items: DigestItem[];
|
|
47
|
+
introText?: string;
|
|
48
|
+
filter?: SubscriberFilter;
|
|
49
|
+
scheduledAt: string;
|
|
50
|
+
}): Promise<DigestSchedule & {
|
|
51
|
+
estimatedRecipients: number;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Execute a previously prepared campaign schedule.
|
|
55
|
+
* Checks that the scheduled time has arrived before sending.
|
|
56
|
+
* Returns null if not yet due.
|
|
57
|
+
*/
|
|
58
|
+
export declare function executeCampaignSchedule(env: MailerEnv, options: ResolvedMailerOptions, schedule: CampaignSchedule): Promise<{
|
|
59
|
+
campaignId: string;
|
|
60
|
+
recipientCount: number;
|
|
61
|
+
} | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Execute a previously prepared digest schedule.
|
|
64
|
+
* Checks that the scheduled time has arrived before sending.
|
|
65
|
+
* Returns null if not yet due.
|
|
66
|
+
*/
|
|
67
|
+
export declare function executeDigestSchedule(env: MailerEnv, options: ResolvedMailerOptions, schedule: DigestSchedule): Promise<{
|
|
68
|
+
campaignId: string;
|
|
69
|
+
recipientCount: number;
|
|
70
|
+
} | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Batch send helper: sends a campaign to multiple topic-segmented groups
|
|
73
|
+
* in a single call. Each segment gets its own campaign ID for tracking.
|
|
74
|
+
*
|
|
75
|
+
* Useful for sites that want to send the same content to different
|
|
76
|
+
* topic segments with different subject lines or intros.
|
|
77
|
+
*/
|
|
78
|
+
export declare function sendBatchCampaigns(env: MailerEnv, options: ResolvedMailerOptions, segments: Array<{
|
|
79
|
+
subject: string;
|
|
80
|
+
html?: string;
|
|
81
|
+
template?: 'campaign';
|
|
82
|
+
data?: Record<string, unknown>;
|
|
83
|
+
filter: SubscriberFilter;
|
|
84
|
+
}>): Promise<Array<{
|
|
85
|
+
campaignId: string;
|
|
86
|
+
recipientCount: number;
|
|
87
|
+
}>>;
|
|
88
|
+
export {};
|
|
89
|
+
//# sourceMappingURL=scheduling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduling.d.ts","sourceRoot":"","sources":["../../src/utils/scheduling.ts"],"names":[],"mappings":"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"}
|