@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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ulid } from 'ulidx';
|
|
2
|
+
import { sendCampaign, sendDigest } from './send.js';
|
|
3
|
+
import { countSubscribers } from './subscribers.js';
|
|
4
|
+
// ─── Schedule builder ───
|
|
5
|
+
/**
|
|
6
|
+
* Build a campaign schedule entry.
|
|
7
|
+
* The consumer stores this in KV/D1/R2 and uses a Cron Trigger to
|
|
8
|
+
* call `executeCampaignSchedule()` at the appropriate time.
|
|
9
|
+
*
|
|
10
|
+
* This is a lightweight helper — it pre-generates the campaignId
|
|
11
|
+
* and validates the send would have recipients.
|
|
12
|
+
*/
|
|
13
|
+
export async function prepareCampaign(db, params) {
|
|
14
|
+
const campaignId = ulid();
|
|
15
|
+
const filter = params.filter ?? { status: ['active'] };
|
|
16
|
+
if (!filter.status)
|
|
17
|
+
filter.status = ['active'];
|
|
18
|
+
const estimatedRecipients = await countSubscribers(db, filter);
|
|
19
|
+
return {
|
|
20
|
+
campaignId,
|
|
21
|
+
subject: params.subject,
|
|
22
|
+
html: params.html,
|
|
23
|
+
template: params.template,
|
|
24
|
+
data: params.data,
|
|
25
|
+
filter: params.filter,
|
|
26
|
+
scheduledAt: params.scheduledAt,
|
|
27
|
+
estimatedRecipients,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a digest schedule entry with recipient estimate.
|
|
32
|
+
*/
|
|
33
|
+
export async function prepareDigest(db, params) {
|
|
34
|
+
const campaignId = ulid();
|
|
35
|
+
const filter = params.filter ?? { status: ['active'] };
|
|
36
|
+
if (!filter.status)
|
|
37
|
+
filter.status = ['active'];
|
|
38
|
+
const estimatedRecipients = await countSubscribers(db, filter);
|
|
39
|
+
return {
|
|
40
|
+
campaignId,
|
|
41
|
+
subject: params.subject,
|
|
42
|
+
items: params.items,
|
|
43
|
+
introText: params.introText,
|
|
44
|
+
filter: params.filter,
|
|
45
|
+
scheduledAt: params.scheduledAt,
|
|
46
|
+
estimatedRecipients,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// ─── Execute scheduled sends ───
|
|
50
|
+
/**
|
|
51
|
+
* Execute a previously prepared campaign schedule.
|
|
52
|
+
* Checks that the scheduled time has arrived before sending.
|
|
53
|
+
* Returns null if not yet due.
|
|
54
|
+
*/
|
|
55
|
+
export async function executeCampaignSchedule(env, options, schedule) {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const scheduledTime = new Date(schedule.scheduledAt).getTime();
|
|
58
|
+
if (scheduledTime > now) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return sendCampaign(env, options, {
|
|
62
|
+
subject: schedule.subject,
|
|
63
|
+
html: schedule.html,
|
|
64
|
+
template: schedule.template,
|
|
65
|
+
data: schedule.data,
|
|
66
|
+
filter: schedule.filter,
|
|
67
|
+
campaignId: schedule.campaignId,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Execute a previously prepared digest schedule.
|
|
72
|
+
* Checks that the scheduled time has arrived before sending.
|
|
73
|
+
* Returns null if not yet due.
|
|
74
|
+
*/
|
|
75
|
+
export async function executeDigestSchedule(env, options, schedule) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const scheduledTime = new Date(schedule.scheduledAt).getTime();
|
|
78
|
+
if (scheduledTime > now) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return sendDigest(env, options, {
|
|
82
|
+
subject: schedule.subject,
|
|
83
|
+
items: schedule.items,
|
|
84
|
+
introText: schedule.introText,
|
|
85
|
+
filter: schedule.filter,
|
|
86
|
+
campaignId: schedule.campaignId,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Batch send helper: sends a campaign to multiple topic-segmented groups
|
|
91
|
+
* in a single call. Each segment gets its own campaign ID for tracking.
|
|
92
|
+
*
|
|
93
|
+
* Useful for sites that want to send the same content to different
|
|
94
|
+
* topic segments with different subject lines or intros.
|
|
95
|
+
*/
|
|
96
|
+
export async function sendBatchCampaigns(env, options, segments) {
|
|
97
|
+
const results = [];
|
|
98
|
+
for (const segment of segments) {
|
|
99
|
+
const result = await sendCampaign(env, options, {
|
|
100
|
+
subject: segment.subject,
|
|
101
|
+
html: segment.html,
|
|
102
|
+
template: segment.template,
|
|
103
|
+
data: segment.data,
|
|
104
|
+
filter: segment.filter,
|
|
105
|
+
});
|
|
106
|
+
results.push(result);
|
|
107
|
+
}
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=scheduling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduling.js","sourceRoot":"","sources":["../../src/utils/scheduling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
import type { ResolvedMailerOptions } from '../options.js';
|
|
3
|
+
import type { DigestItem, SubscriberFilter, TemplateName } from '../types.js';
|
|
4
|
+
type DrizzleDB = ReturnType<typeof drizzle>;
|
|
5
|
+
export interface MailerEnv {
|
|
6
|
+
DB: D1Database;
|
|
7
|
+
QUEUE: Queue;
|
|
8
|
+
}
|
|
9
|
+
export declare function sendTransactional(env: MailerEnv, options: ResolvedMailerOptions, params: {
|
|
10
|
+
to: string;
|
|
11
|
+
subscriberId?: string;
|
|
12
|
+
subject: string;
|
|
13
|
+
html?: string;
|
|
14
|
+
template?: TemplateName;
|
|
15
|
+
data?: Record<string, unknown>;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
trackingId: string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function sendCampaign(env: MailerEnv, options: ResolvedMailerOptions, params: {
|
|
20
|
+
subject: string;
|
|
21
|
+
html?: string;
|
|
22
|
+
template?: 'campaign';
|
|
23
|
+
data?: Record<string, unknown>;
|
|
24
|
+
filter?: SubscriberFilter;
|
|
25
|
+
campaignId?: string;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
campaignId: string;
|
|
28
|
+
recipientCount: number;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function sendDigest(env: MailerEnv, options: ResolvedMailerOptions, params: {
|
|
31
|
+
subject: string;
|
|
32
|
+
items: DigestItem[];
|
|
33
|
+
introText?: string;
|
|
34
|
+
filter?: SubscriberFilter;
|
|
35
|
+
campaignId?: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
campaignId: string;
|
|
38
|
+
recipientCount: number;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function updateSendStatus(db: DrizzleDB, trackingId: string, status: string, fields?: Record<string, string | null>): Promise<void>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,KAAK,EACX,UAAU,EAGV,gBAAgB,EAEhB,YAAY,EACZ,MAAM,aAAa,CAAA;AAOpB,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAI3C,MAAM,WAAW,SAAS;IACzB,EAAE,EAAE,UAAU,CAAA;IACd,KAAK,EAAE,KAAK,CAAA;CACZ;AAID,wBAAsB,iBAAiB,CACtC,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE;IACP,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B,GACC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAwDjC;AA8ED,wBAAsB,YAAY,CACjC,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE;IACP,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB,GACC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CAoCzD;AAID,wBAAsB,UAAU,CAC/B,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE;IACP,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB,GACC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BzD;AAID,wBAAsB,gBAAgB,CACrC,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
3
|
+
import { ulid } from 'ulidx';
|
|
4
|
+
import { emailSends } from '../schema.js';
|
|
5
|
+
import { getSubscriberBatch } from './subscribers.js';
|
|
6
|
+
import { renderDigestItems, renderEmail } from './templates.js';
|
|
7
|
+
import { generateToken } from './tokens.js';
|
|
8
|
+
import { injectTrackingPixel, rewriteLinksForTracking } from './tracking.js';
|
|
9
|
+
import { buildSubscriberManageUrls } from './urls.js';
|
|
10
|
+
// ─── sendTransactional ───
|
|
11
|
+
export async function sendTransactional(env, options, params) {
|
|
12
|
+
const db = drizzle(env.DB);
|
|
13
|
+
const trackingId = ulid();
|
|
14
|
+
const subscriberId = params.subscriberId ?? ulid();
|
|
15
|
+
// Render HTML
|
|
16
|
+
let html;
|
|
17
|
+
if (params.template) {
|
|
18
|
+
const manageUrls = await buildSubscriberManageUrls(options, params.subscriberId);
|
|
19
|
+
html = renderEmail(params.template, {
|
|
20
|
+
...params.data,
|
|
21
|
+
senderName: options.senderName,
|
|
22
|
+
siteUrl: options.siteUrl,
|
|
23
|
+
unsubscribeUrl: manageUrls.unsubscribeUrl,
|
|
24
|
+
preferencesUrl: manageUrls.preferencesUrl,
|
|
25
|
+
brand: options.brand,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (params.html) {
|
|
29
|
+
html = params.html;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error('Either template or html must be provided');
|
|
33
|
+
}
|
|
34
|
+
// Record the send
|
|
35
|
+
await db.insert(emailSends).values({
|
|
36
|
+
id: ulid(),
|
|
37
|
+
subscriberId,
|
|
38
|
+
email: params.to,
|
|
39
|
+
subject: params.subject,
|
|
40
|
+
type: 'transactional',
|
|
41
|
+
status: 'queued',
|
|
42
|
+
trackingId,
|
|
43
|
+
createdAt: new Date().toISOString(),
|
|
44
|
+
});
|
|
45
|
+
// Build and enqueue the message
|
|
46
|
+
const message = {
|
|
47
|
+
type: 'transactional',
|
|
48
|
+
recipients: [
|
|
49
|
+
{
|
|
50
|
+
email: params.to,
|
|
51
|
+
subscriberId,
|
|
52
|
+
trackingId,
|
|
53
|
+
unsubscribeToken: '',
|
|
54
|
+
preferencesToken: '',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
subject: params.subject,
|
|
58
|
+
htmlTemplate: html,
|
|
59
|
+
from: `${options.senderName} <${options.fromAddress}>`,
|
|
60
|
+
replyTo: options.replyTo,
|
|
61
|
+
};
|
|
62
|
+
await env.QUEUE.send(message);
|
|
63
|
+
return { trackingId };
|
|
64
|
+
}
|
|
65
|
+
// ─── Fan-out helper (shared by campaign + digest) ───
|
|
66
|
+
async function fanOutToSubscribers(env, db, options, params) {
|
|
67
|
+
let cursor = null;
|
|
68
|
+
let recipientCount = 0;
|
|
69
|
+
const filter = params.filter ?? { status: ['active'] };
|
|
70
|
+
if (!filter.status)
|
|
71
|
+
filter.status = ['active'];
|
|
72
|
+
while (true) {
|
|
73
|
+
const batch = await getSubscriberBatch(db, filter, options.batchSize, cursor);
|
|
74
|
+
if (batch.length === 0)
|
|
75
|
+
break;
|
|
76
|
+
const recipients = [];
|
|
77
|
+
for (const sub of batch) {
|
|
78
|
+
const trackingId = ulid();
|
|
79
|
+
const unsubscribeToken = await generateToken(options.signingSecret, {
|
|
80
|
+
subscriberId: sub.id,
|
|
81
|
+
action: 'unsubscribe',
|
|
82
|
+
});
|
|
83
|
+
const preferencesToken = await generateToken(options.signingSecret, {
|
|
84
|
+
subscriberId: sub.id,
|
|
85
|
+
action: 'preferences',
|
|
86
|
+
});
|
|
87
|
+
await db.insert(emailSends).values({
|
|
88
|
+
id: ulid(),
|
|
89
|
+
subscriberId: sub.id,
|
|
90
|
+
campaignId: params.campaignId,
|
|
91
|
+
email: sub.email,
|
|
92
|
+
subject: params.subject,
|
|
93
|
+
type: params.type,
|
|
94
|
+
status: 'queued',
|
|
95
|
+
trackingId,
|
|
96
|
+
createdAt: new Date().toISOString(),
|
|
97
|
+
});
|
|
98
|
+
recipients.push({
|
|
99
|
+
email: sub.email,
|
|
100
|
+
subscriberId: sub.id,
|
|
101
|
+
trackingId,
|
|
102
|
+
unsubscribeToken,
|
|
103
|
+
preferencesToken,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const message = {
|
|
107
|
+
type: params.type,
|
|
108
|
+
recipients,
|
|
109
|
+
subject: params.subject,
|
|
110
|
+
htmlTemplate: params.html,
|
|
111
|
+
from: `${options.senderName} <${options.fromAddress}>`,
|
|
112
|
+
replyTo: options.replyTo,
|
|
113
|
+
campaignId: params.campaignId,
|
|
114
|
+
};
|
|
115
|
+
await env.QUEUE.send(message);
|
|
116
|
+
recipientCount += batch.length;
|
|
117
|
+
cursor = batch[batch.length - 1].id;
|
|
118
|
+
}
|
|
119
|
+
return recipientCount;
|
|
120
|
+
}
|
|
121
|
+
// ─── sendCampaign ───
|
|
122
|
+
export async function sendCampaign(env, options, params) {
|
|
123
|
+
const campaignId = params.campaignId ?? ulid();
|
|
124
|
+
const db = drizzle(env.DB);
|
|
125
|
+
// Render HTML
|
|
126
|
+
let html;
|
|
127
|
+
if (params.template) {
|
|
128
|
+
html = renderEmail('campaign', {
|
|
129
|
+
...params.data,
|
|
130
|
+
senderName: options.senderName,
|
|
131
|
+
siteUrl: options.siteUrl,
|
|
132
|
+
unsubscribeUrl: '{{UNSUBSCRIBE_URL}}',
|
|
133
|
+
preferencesUrl: '{{PREFERENCES_URL}}',
|
|
134
|
+
brand: options.brand,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
else if (params.html) {
|
|
138
|
+
html = params.html;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
throw new Error('Either template or html must be provided');
|
|
142
|
+
}
|
|
143
|
+
// Inject tracking
|
|
144
|
+
const trackOpenUrl = `${options.siteUrl}${options.trackOpenPath}`;
|
|
145
|
+
const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
|
|
146
|
+
html = injectTrackingPixel(html, trackOpenUrl);
|
|
147
|
+
html = rewriteLinksForTracking(html, trackClickUrl);
|
|
148
|
+
const recipientCount = await fanOutToSubscribers(env, db, options, {
|
|
149
|
+
subject: params.subject,
|
|
150
|
+
html,
|
|
151
|
+
campaignId,
|
|
152
|
+
type: 'campaign',
|
|
153
|
+
filter: params.filter,
|
|
154
|
+
});
|
|
155
|
+
return { campaignId, recipientCount };
|
|
156
|
+
}
|
|
157
|
+
// ─── sendDigest ───
|
|
158
|
+
export async function sendDigest(env, options, params) {
|
|
159
|
+
const campaignId = params.campaignId ?? ulid();
|
|
160
|
+
const db = drizzle(env.DB);
|
|
161
|
+
// Render digest items then full template
|
|
162
|
+
const itemsHtml = renderDigestItems(params.items);
|
|
163
|
+
let html = renderEmail('digest', {
|
|
164
|
+
senderName: options.senderName,
|
|
165
|
+
siteUrl: options.siteUrl,
|
|
166
|
+
unsubscribeUrl: '{{UNSUBSCRIBE_URL}}',
|
|
167
|
+
preferencesUrl: '{{PREFERENCES_URL}}',
|
|
168
|
+
brand: options.brand,
|
|
169
|
+
introText: params.introText ?? '',
|
|
170
|
+
digestItems: itemsHtml,
|
|
171
|
+
});
|
|
172
|
+
// Inject tracking
|
|
173
|
+
const trackOpenUrl = `${options.siteUrl}${options.trackOpenPath}`;
|
|
174
|
+
const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
|
|
175
|
+
html = injectTrackingPixel(html, trackOpenUrl);
|
|
176
|
+
html = rewriteLinksForTracking(html, trackClickUrl);
|
|
177
|
+
const recipientCount = await fanOutToSubscribers(env, db, options, {
|
|
178
|
+
subject: params.subject,
|
|
179
|
+
html,
|
|
180
|
+
campaignId,
|
|
181
|
+
type: 'digest',
|
|
182
|
+
filter: params.filter,
|
|
183
|
+
});
|
|
184
|
+
return { campaignId, recipientCount };
|
|
185
|
+
}
|
|
186
|
+
// ─── updateSendStatus ───
|
|
187
|
+
export async function updateSendStatus(db, trackingId, status, fields) {
|
|
188
|
+
await db
|
|
189
|
+
.update(emailSends)
|
|
190
|
+
.set({ status, ...fields })
|
|
191
|
+
.where(eq(emailSends.trackingId, trackingId));
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
import type { Subscriber, SubscriberAttribution, SubscriberFilter } from '../types.js';
|
|
3
|
+
type DrizzleDB = ReturnType<typeof drizzle>;
|
|
4
|
+
export declare function createSubscriber(db: DrizzleDB, params: {
|
|
5
|
+
email: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
source: string;
|
|
8
|
+
preferences?: string[];
|
|
9
|
+
attribution?: SubscriberAttribution;
|
|
10
|
+
doubleOptIn?: boolean;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
subscriber: Subscriber;
|
|
13
|
+
isNew: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function confirmSubscriber(db: DrizzleDB, subscriberId: string): Promise<Subscriber>;
|
|
16
|
+
export declare function unsubscribeSubscriber(db: DrizzleDB, subscriberId: string): Promise<void>;
|
|
17
|
+
export declare function updatePreferences(db: DrizzleDB, subscriberId: string, preferences: string[]): Promise<void>;
|
|
18
|
+
export declare function getSubscriberByEmail(db: DrizzleDB, email: string): Promise<Subscriber | null>;
|
|
19
|
+
export declare function getSubscriberById(db: DrizzleDB, id: string): Promise<Subscriber | null>;
|
|
20
|
+
export declare function getSubscriberBatch(db: DrizzleDB, filter: SubscriberFilter, limit: number, cursor: string | null): Promise<Subscriber[]>;
|
|
21
|
+
export declare function countSubscribers(db: DrizzleDB, filter?: SubscriberFilter): Promise<number>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=subscribers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscribers.d.ts","sourceRoot":"","sources":["../../src/utils/subscribers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAG7C,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEtF,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AA0B3C,wBAAsB,gBAAgB,CACrC,EAAE,EAAE,SAAS,EACb,MAAM,EAAE;IACP,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;CACrB,GACC,OAAO,CAAC;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAkErD;AAID,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAoBhG;AAID,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW9F;AAID,wBAAsB,iBAAiB,CACtC,EAAE,EAAE,SAAS,EACb,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,CAUf;AAID,wBAAsB,oBAAoB,CACzC,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAK5B;AAID,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAK7F;AAID,wBAAsB,kBAAkB,CACvC,EAAE,EAAE,SAAS,EACb,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CA0CvB;AAID,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmChG"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { and, eq, gt, inArray, like, lt, or, sql } from 'drizzle-orm';
|
|
2
|
+
import { ulid } from 'ulidx';
|
|
3
|
+
import { subscribers } from '../schema.js';
|
|
4
|
+
// ─── Helpers ───
|
|
5
|
+
function rowToSubscriber(row) {
|
|
6
|
+
return {
|
|
7
|
+
id: row.id,
|
|
8
|
+
email: row.email,
|
|
9
|
+
name: row.name ?? undefined,
|
|
10
|
+
status: row.status,
|
|
11
|
+
preferences: JSON.parse(row.preferences ?? '[]'),
|
|
12
|
+
source: row.source,
|
|
13
|
+
attribution: row.attribution
|
|
14
|
+
? JSON.parse(row.attribution)
|
|
15
|
+
: undefined,
|
|
16
|
+
softBounceCount: row.softBounceCount ?? 0,
|
|
17
|
+
subscribedAt: row.subscribedAt,
|
|
18
|
+
confirmedAt: row.confirmedAt ?? undefined,
|
|
19
|
+
unsubscribedAt: row.unsubscribedAt ?? undefined,
|
|
20
|
+
createdAt: row.createdAt,
|
|
21
|
+
updatedAt: row.updatedAt,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// ─── createSubscriber ───
|
|
25
|
+
export async function createSubscriber(db, params) {
|
|
26
|
+
const doubleOptIn = params.doubleOptIn ?? true;
|
|
27
|
+
const existing = await db.select().from(subscribers).where(eq(subscribers.email, params.email));
|
|
28
|
+
if (existing.length > 0) {
|
|
29
|
+
const row = existing[0];
|
|
30
|
+
if (row.status === 'active') {
|
|
31
|
+
return { subscriber: rowToSubscriber(row), isNew: false };
|
|
32
|
+
}
|
|
33
|
+
if (row.status === 'pending') {
|
|
34
|
+
return { subscriber: rowToSubscriber(row), isNew: false };
|
|
35
|
+
}
|
|
36
|
+
if (row.status === 'bounced' || row.status === 'complained') {
|
|
37
|
+
throw new Error('Cannot re-subscribe a bounced or complained address');
|
|
38
|
+
}
|
|
39
|
+
// status === 'unsubscribed' → reactivate
|
|
40
|
+
const now = new Date().toISOString();
|
|
41
|
+
const newStatus = doubleOptIn ? 'pending' : 'active';
|
|
42
|
+
const updated = await db
|
|
43
|
+
.update(subscribers)
|
|
44
|
+
.set({
|
|
45
|
+
status: newStatus,
|
|
46
|
+
subscribedAt: now,
|
|
47
|
+
unsubscribedAt: null,
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
})
|
|
50
|
+
.where(eq(subscribers.id, row.id))
|
|
51
|
+
.returning();
|
|
52
|
+
return {
|
|
53
|
+
subscriber: rowToSubscriber(updated[0]),
|
|
54
|
+
isNew: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// New subscriber
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const id = ulid();
|
|
60
|
+
const status = doubleOptIn ? 'pending' : 'active';
|
|
61
|
+
const inserted = await db
|
|
62
|
+
.insert(subscribers)
|
|
63
|
+
.values({
|
|
64
|
+
id,
|
|
65
|
+
email: params.email,
|
|
66
|
+
name: params.name ?? null,
|
|
67
|
+
status,
|
|
68
|
+
preferences: JSON.stringify(params.preferences ?? []),
|
|
69
|
+
source: params.source,
|
|
70
|
+
attribution: params.attribution ? JSON.stringify(params.attribution) : null,
|
|
71
|
+
softBounceCount: 0,
|
|
72
|
+
subscribedAt: now,
|
|
73
|
+
confirmedAt: null,
|
|
74
|
+
unsubscribedAt: null,
|
|
75
|
+
createdAt: now,
|
|
76
|
+
updatedAt: now,
|
|
77
|
+
})
|
|
78
|
+
.returning();
|
|
79
|
+
return { subscriber: rowToSubscriber(inserted[0]), isNew: true };
|
|
80
|
+
}
|
|
81
|
+
// ─── confirmSubscriber ───
|
|
82
|
+
export async function confirmSubscriber(db, subscriberId) {
|
|
83
|
+
const rows = await db.select().from(subscribers).where(eq(subscribers.id, subscriberId));
|
|
84
|
+
if (rows.length === 0) {
|
|
85
|
+
throw new Error('Subscriber not found');
|
|
86
|
+
}
|
|
87
|
+
if (rows[0].status !== 'pending') {
|
|
88
|
+
throw new Error('Subscriber is not in pending status');
|
|
89
|
+
}
|
|
90
|
+
const now = new Date().toISOString();
|
|
91
|
+
const updated = await db
|
|
92
|
+
.update(subscribers)
|
|
93
|
+
.set({ status: 'active', confirmedAt: now, updatedAt: now })
|
|
94
|
+
.where(eq(subscribers.id, subscriberId))
|
|
95
|
+
.returning();
|
|
96
|
+
return rowToSubscriber(updated[0]);
|
|
97
|
+
}
|
|
98
|
+
// ─── unsubscribeSubscriber ───
|
|
99
|
+
export async function unsubscribeSubscriber(db, subscriberId) {
|
|
100
|
+
const now = new Date().toISOString();
|
|
101
|
+
await db
|
|
102
|
+
.update(subscribers)
|
|
103
|
+
.set({
|
|
104
|
+
status: 'unsubscribed',
|
|
105
|
+
unsubscribedAt: now,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
})
|
|
108
|
+
.where(eq(subscribers.id, subscriberId));
|
|
109
|
+
}
|
|
110
|
+
// ─── updatePreferences ───
|
|
111
|
+
export async function updatePreferences(db, subscriberId, preferences) {
|
|
112
|
+
const now = new Date().toISOString();
|
|
113
|
+
await db
|
|
114
|
+
.update(subscribers)
|
|
115
|
+
.set({
|
|
116
|
+
preferences: JSON.stringify(preferences),
|
|
117
|
+
updatedAt: now,
|
|
118
|
+
})
|
|
119
|
+
.where(eq(subscribers.id, subscriberId));
|
|
120
|
+
}
|
|
121
|
+
// ─── getSubscriberByEmail ───
|
|
122
|
+
export async function getSubscriberByEmail(db, email) {
|
|
123
|
+
const rows = await db.select().from(subscribers).where(eq(subscribers.email, email));
|
|
124
|
+
if (rows.length === 0)
|
|
125
|
+
return null;
|
|
126
|
+
return rowToSubscriber(rows[0]);
|
|
127
|
+
}
|
|
128
|
+
// ─── getSubscriberById ───
|
|
129
|
+
export async function getSubscriberById(db, id) {
|
|
130
|
+
const rows = await db.select().from(subscribers).where(eq(subscribers.id, id));
|
|
131
|
+
if (rows.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
return rowToSubscriber(rows[0]);
|
|
134
|
+
}
|
|
135
|
+
// ─── getSubscriberBatch ───
|
|
136
|
+
export async function getSubscriberBatch(db, filter, limit, cursor) {
|
|
137
|
+
const conditions = [];
|
|
138
|
+
// Status filter (default: active only)
|
|
139
|
+
const statuses = filter.status ?? ['active'];
|
|
140
|
+
conditions.push(inArray(subscribers.status, statuses));
|
|
141
|
+
// Preferences overlap — any match
|
|
142
|
+
if (filter.preferences && filter.preferences.length > 0) {
|
|
143
|
+
const prefConditions = filter.preferences.map((pref) => like(subscribers.preferences, `%"${pref}"%`));
|
|
144
|
+
const prefOr = or(...prefConditions);
|
|
145
|
+
if (prefOr)
|
|
146
|
+
conditions.push(prefOr);
|
|
147
|
+
}
|
|
148
|
+
// Date range
|
|
149
|
+
if (filter.subscribedAfter) {
|
|
150
|
+
conditions.push(gt(subscribers.subscribedAt, filter.subscribedAfter));
|
|
151
|
+
}
|
|
152
|
+
if (filter.subscribedBefore) {
|
|
153
|
+
conditions.push(lt(subscribers.subscribedAt, filter.subscribedBefore));
|
|
154
|
+
}
|
|
155
|
+
// Source filter
|
|
156
|
+
if (filter.source && filter.source.length > 0) {
|
|
157
|
+
conditions.push(inArray(subscribers.source, filter.source));
|
|
158
|
+
}
|
|
159
|
+
// Cursor-based pagination
|
|
160
|
+
if (cursor) {
|
|
161
|
+
conditions.push(gt(subscribers.id, cursor));
|
|
162
|
+
}
|
|
163
|
+
const rows = await db
|
|
164
|
+
.select()
|
|
165
|
+
.from(subscribers)
|
|
166
|
+
.where(and(...conditions))
|
|
167
|
+
.orderBy(subscribers.id)
|
|
168
|
+
.limit(limit);
|
|
169
|
+
return rows.map(rowToSubscriber);
|
|
170
|
+
}
|
|
171
|
+
// ─── countSubscribers ───
|
|
172
|
+
export async function countSubscribers(db, filter) {
|
|
173
|
+
const conditions = [];
|
|
174
|
+
if (filter) {
|
|
175
|
+
const statuses = filter.status ?? ['active'];
|
|
176
|
+
conditions.push(inArray(subscribers.status, statuses));
|
|
177
|
+
if (filter.preferences && filter.preferences.length > 0) {
|
|
178
|
+
const prefConditions = filter.preferences.map((pref) => like(subscribers.preferences, `%"${pref}"%`));
|
|
179
|
+
const prefOr = or(...prefConditions);
|
|
180
|
+
if (prefOr)
|
|
181
|
+
conditions.push(prefOr);
|
|
182
|
+
}
|
|
183
|
+
if (filter.subscribedAfter) {
|
|
184
|
+
conditions.push(gt(subscribers.subscribedAt, filter.subscribedAfter));
|
|
185
|
+
}
|
|
186
|
+
if (filter.subscribedBefore) {
|
|
187
|
+
conditions.push(lt(subscribers.subscribedAt, filter.subscribedBefore));
|
|
188
|
+
}
|
|
189
|
+
if (filter.source && filter.source.length > 0) {
|
|
190
|
+
conditions.push(inArray(subscribers.source, filter.source));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
194
|
+
const result = await db
|
|
195
|
+
.select({ count: sql `count(*)` })
|
|
196
|
+
.from(subscribers)
|
|
197
|
+
.where(whereClause);
|
|
198
|
+
return result[0].count;
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=subscribers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscribers.js","sourceRoot":"","sources":["../../src/utils/subscribers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DigestItem, TemplateData, TemplateName } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Process {{#key}}...{{/key}} conditional blocks.
|
|
4
|
+
* If the resolved value is truthy the inner content is kept;
|
|
5
|
+
* otherwise the entire block (including delimiters) is removed.
|
|
6
|
+
* Supports nested dot paths like {{#brand.logoUrl}}...{{/brand.logoUrl}}.
|
|
7
|
+
*/
|
|
8
|
+
export declare function processConditionals(template: string, data: Record<string, unknown>): string;
|
|
9
|
+
export declare function interpolate(template: string, data: Record<string, unknown>): string;
|
|
10
|
+
export declare function inlineStyles(html: string, brand?: {
|
|
11
|
+
primaryColor: string;
|
|
12
|
+
accentColor: string;
|
|
13
|
+
}): string;
|
|
14
|
+
export declare function renderDigestItems(items: DigestItem[]): string;
|
|
15
|
+
export declare function renderEmail(template: string | TemplateName, data: TemplateData): string;
|
|
16
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/utils/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAyEzE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQ3F;AAID,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiBnF;AAID,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CA0BR;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAsB7D;AAuRD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAYvF"}
|