@growth-labs/mailer 0.4.7 → 0.4.9

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.
Files changed (52) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/options.d.ts +3 -0
  6. package/dist/options.d.ts.map +1 -1
  7. package/dist/options.js +5 -0
  8. package/dist/options.js.map +1 -1
  9. package/dist/routes/track-click.d.ts.map +1 -1
  10. package/dist/routes/track-click.js +3 -2
  11. package/dist/routes/track-click.js.map +1 -1
  12. package/dist/routes/track-open.d.ts.map +1 -1
  13. package/dist/routes/track-open.js +3 -2
  14. package/dist/routes/track-open.js.map +1 -1
  15. package/dist/routes/unsubscribe.d.ts.map +1 -1
  16. package/dist/routes/unsubscribe.js +3 -4
  17. package/dist/routes/unsubscribe.js.map +1 -1
  18. package/dist/routes/webhook.d.ts.map +1 -1
  19. package/dist/routes/webhook.js +8 -1
  20. package/dist/routes/webhook.js.map +1 -1
  21. package/dist/schema/sends.d.ts +19 -0
  22. package/dist/schema/sends.d.ts.map +1 -1
  23. package/dist/schema/sends.js +2 -0
  24. package/dist/schema/sends.js.map +1 -1
  25. package/dist/types.d.ts +2 -2
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/utils/broadcast.d.ts +82 -0
  28. package/dist/utils/broadcast.d.ts.map +1 -0
  29. package/dist/utils/broadcast.js +261 -0
  30. package/dist/utils/broadcast.js.map +1 -0
  31. package/dist/utils/index.d.ts +2 -0
  32. package/dist/utils/index.d.ts.map +1 -1
  33. package/dist/utils/index.js +1 -0
  34. package/dist/utils/index.js.map +1 -1
  35. package/dist/utils/send.d.ts.map +1 -1
  36. package/dist/utils/send.js +18 -10
  37. package/dist/utils/send.js.map +1 -1
  38. package/migrations/0002_add_site_id_to_email_sends.sql +4 -0
  39. package/package.json +2 -2
  40. package/src/components/SubscribeForm.astro +78 -65
  41. package/src/index.ts +6 -0
  42. package/src/options.ts +5 -0
  43. package/src/routes/track-click.ts +3 -2
  44. package/src/routes/track-open.ts +3 -2
  45. package/src/routes/unsubscribe.ts +3 -5
  46. package/src/routes/webhook.ts +9 -1
  47. package/src/schema/sends.ts +2 -0
  48. package/src/types.ts +2 -2
  49. package/src/utils/broadcast.ts +404 -0
  50. package/src/utils/index.ts +6 -0
  51. package/src/utils/send.ts +23 -9
  52. package/src/virtual.d.ts +1 -0
@@ -0,0 +1,261 @@
1
+ import { and, eq, inArray } from 'drizzle-orm';
2
+ import { drizzle } from 'drizzle-orm/d1';
3
+ import { ulid } from 'ulidx';
4
+ import { emailSends } from '../schema/sends.js';
5
+ import { subscribers } from '../schema/subscribers.js';
6
+ import { sleep } from './providers.js';
7
+ import { generateToken } from './tokens.js';
8
+ import { injectTrackingPixel, rewriteLinksForTracking } from './tracking.js';
9
+ /**
10
+ * Statuses that mean "do not send" — the suppression list. A broadcast skips
11
+ * every recipient whose `gl_subscribers` row carries one of these. `active` is
12
+ * the ONLY sendable status; everything else (including `pending`, i.e. an
13
+ * unconfirmed double-opt-in subscriber) is suppressed so a one-off blast can
14
+ * never re-permission or re-mail a suppressed address.
15
+ */
16
+ const SUPPRESSED_STATUSES = new Set([
17
+ 'unsubscribed',
18
+ 'bounced',
19
+ 'complained',
20
+ 'pending',
21
+ ]);
22
+ function resolveProducerBindings(env, options) {
23
+ const d1 = env[options.d1Binding];
24
+ if (!d1) {
25
+ throw new Error(`[mailer] env.${options.d1Binding} is undefined; ` +
26
+ 'pass d1Binding option to the mailer integration or bind that name in wrangler.toml.');
27
+ }
28
+ const sendsBindingName = options.sendsD1Binding ?? options.d1Binding;
29
+ const sendsD1 = env[sendsBindingName];
30
+ if (!sendsD1) {
31
+ throw new Error(`[mailer] env.${sendsBindingName} is undefined; ` +
32
+ 'pass sendsD1Binding option to the mailer integration or bind that name in wrangler.toml.');
33
+ }
34
+ const queue = env[options.queueBinding];
35
+ if (!queue) {
36
+ throw new Error(`[mailer] env.${options.queueBinding} is undefined; ` +
37
+ 'pass queueBinding option to the mailer integration or bind that name in wrangler.toml.');
38
+ }
39
+ return { db: drizzle(d1), sendsDb: drizzle(sendsD1), queue };
40
+ }
41
+ function normalizeRecipient(r) {
42
+ return typeof r === 'string' ? { email: r } : r;
43
+ }
44
+ /**
45
+ * Resolve each requested recipient against `gl_subscribers`:
46
+ * - dedupe by email (a list may repeat an address)
47
+ * - attach the canonical subscriberId + status
48
+ * - drop emails with no subscriber row (cannot honour unsubscribe → suppress)
49
+ * Returns the resolved sendable-candidate set plus the suppressed count.
50
+ */
51
+ async function resolveAgainstSubscribers(db, requested) {
52
+ // Dedupe requested emails (case-insensitive, trimmed) preserving first-seen order.
53
+ const byEmail = new Map();
54
+ for (const r of requested) {
55
+ const key = r.email.trim().toLowerCase();
56
+ if (key.length === 0)
57
+ continue;
58
+ if (!byEmail.has(key))
59
+ byEmail.set(key, { ...r, email: r.email.trim() });
60
+ }
61
+ const emails = [...byEmail.values()].map((r) => r.email);
62
+ if (emails.length === 0)
63
+ return { candidates: [], suppressedCount: 0 };
64
+ // Look up subscriber rows for all requested emails.
65
+ const rows = await db
66
+ .select({
67
+ id: subscribers.id,
68
+ email: subscribers.email,
69
+ status: subscribers.status,
70
+ })
71
+ .from(subscribers)
72
+ .where(inArray(subscribers.email, emails));
73
+ const rowByEmail = new Map();
74
+ for (const row of rows) {
75
+ rowByEmail.set(row.email.toLowerCase(), {
76
+ id: row.id,
77
+ status: row.status,
78
+ });
79
+ }
80
+ const candidates = [];
81
+ let suppressedCount = 0;
82
+ for (const r of byEmail.values()) {
83
+ const row = rowByEmail.get(r.email.toLowerCase());
84
+ // No subscriber row, or a suppressed status → skip. Never send to an
85
+ // address we cannot honour an unsubscribe for, and never to a
86
+ // bounced/complained/unsubscribed/pending one.
87
+ if (!row || SUPPRESSED_STATUSES.has(row.status)) {
88
+ suppressedCount++;
89
+ continue;
90
+ }
91
+ candidates.push({ email: r.email, subscriberId: r.subscriberId ?? row.id, status: row.status });
92
+ }
93
+ return { candidates, suppressedCount };
94
+ }
95
+ /**
96
+ * Of the suppression-cleared candidates, drop any that already have a
97
+ * `gl_email_sends` row for this campaignId — the idempotency guard that makes a
98
+ * re-run a no-op for already-processed recipients.
99
+ */
100
+ async function filterAlreadySent(db, campaignId, candidates) {
101
+ if (candidates.length === 0)
102
+ return { fresh: [], alreadySentCount: 0 };
103
+ const candidateEmails = candidates.map((c) => c.email);
104
+ const existing = await db
105
+ .select({ email: emailSends.email })
106
+ .from(emailSends)
107
+ .where(and(eq(emailSends.campaignId, campaignId), inArray(emailSends.email, candidateEmails)));
108
+ const sentEmails = new Set(existing.map((row) => row.email.toLowerCase()));
109
+ const fresh = [];
110
+ let alreadySentCount = 0;
111
+ for (const c of candidates) {
112
+ if (sentEmails.has(c.email.toLowerCase())) {
113
+ alreadySentCount++;
114
+ continue;
115
+ }
116
+ fresh.push(c);
117
+ }
118
+ return { fresh, alreadySentCount };
119
+ }
120
+ /**
121
+ * Build the broadcast HTML with the tracking pixel + click-rewrite applied
122
+ * once (the per-recipient {{TRACKING_ID}} is substituted in the consumer).
123
+ */
124
+ function prepareBroadcastHtml(html, options) {
125
+ const trackOpenUrl = `${options.siteUrl}${options.trackOpenPath}`;
126
+ const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
127
+ return rewriteLinksForTracking(injectTrackingPixel(html, trackOpenUrl), trackClickUrl);
128
+ }
129
+ function chunk(items, size) {
130
+ const out = [];
131
+ for (let i = 0; i < items.length; i += size) {
132
+ out.push(items.slice(i, i + size));
133
+ }
134
+ return out;
135
+ }
136
+ /**
137
+ * Send a one-off broadcast blast (Customer.io one-off-blast parity).
138
+ *
139
+ * - Takes an explicit recipient list, or a saved-segment name resolved via
140
+ * `resolveRecipients` (segmentation is NOT built here — WS5 bead 06).
141
+ * - Enforces the suppression list: only `active` subscribers are sent;
142
+ * unsubscribed / bounced / complained / pending / unknown addresses skip.
143
+ * - Idempotent on `campaignId`: a re-run never double-sends to a recipient that
144
+ * already has a `gl_email_sends` row for the campaign.
145
+ * - Writes a per-recipient `gl_email_sends` row (`type='broadcast'`, status
146
+ * `queued`) before enqueueing.
147
+ * - Fans out in batches sized for Cloudflare queue/send-rate limits, with
148
+ * optional `messagesPerSecond` pacing.
149
+ * - `dryRun` resolves + suppresses + dedupes and returns the recipient count
150
+ * WITHOUT writing rows or enqueueing anything.
151
+ */
152
+ export async function sendBroadcast(env, options, params) {
153
+ const campaignId = params.campaignId ?? ulid();
154
+ const dryRun = params.dryRun ?? false;
155
+ const maxRecipientsPerMessage = Math.max(1, Math.min(params.maxRecipientsPerMessage ?? options.batchSize, 500));
156
+ const messagesPerSecond = params.messagesPerSecond ?? 0;
157
+ const { db, sendsDb, queue } = resolveProducerBindings(env, options);
158
+ // 1. Resolve the recipient list — explicit list and/or a resolved segment.
159
+ const requestedRaw = [...(params.recipients ?? [])];
160
+ if (params.segmentName) {
161
+ if (!params.resolveRecipients) {
162
+ throw new Error('[mailer] sendBroadcast: segmentName given without resolveRecipients; ' +
163
+ 'segmentation is resolved by the caller (WS5 bead 06), not by the mailer.');
164
+ }
165
+ const resolved = await params.resolveRecipients(params.segmentName);
166
+ requestedRaw.push(...resolved);
167
+ }
168
+ if (requestedRaw.length === 0) {
169
+ throw new Error('[mailer] sendBroadcast: no recipients or segmentName provided');
170
+ }
171
+ const requested = requestedRaw.map(normalizeRecipient);
172
+ // 2. Suppression enforcement + dedupe — reads gl_subscribers (subscribers db).
173
+ const { candidates, suppressedCount } = await resolveAgainstSubscribers(db, requested);
174
+ // 3. Idempotency — drop recipients already recorded for this campaign (sends db).
175
+ const { fresh, alreadySentCount } = await filterAlreadySent(sendsDb, campaignId, candidates);
176
+ // Dry-run: report the counts and stop before any write or enqueue.
177
+ if (dryRun) {
178
+ return {
179
+ campaignId,
180
+ recipientCount: fresh.length,
181
+ suppressedCount,
182
+ alreadySentCount,
183
+ batchCount: 0,
184
+ dryRun: true,
185
+ };
186
+ }
187
+ if (fresh.length === 0) {
188
+ return {
189
+ campaignId,
190
+ recipientCount: 0,
191
+ suppressedCount,
192
+ alreadySentCount,
193
+ batchCount: 0,
194
+ dryRun: false,
195
+ };
196
+ }
197
+ const html = prepareBroadcastHtml(params.html, options);
198
+ const now = new Date().toISOString();
199
+ // 4. Per-recipient row write + queue-recipient build.
200
+ const queueRecipients = [];
201
+ for (const recipient of fresh) {
202
+ const trackingId = ulid();
203
+ const [unsubscribeToken, preferencesToken] = await Promise.all([
204
+ generateToken(options.signingSecret, {
205
+ subscriberId: recipient.subscriberId,
206
+ action: 'unsubscribe',
207
+ }),
208
+ generateToken(options.signingSecret, {
209
+ subscriberId: recipient.subscriberId,
210
+ action: 'preferences',
211
+ }),
212
+ ]);
213
+ await sendsDb.insert(emailSends).values({
214
+ id: ulid(),
215
+ subscriberId: recipient.subscriberId,
216
+ siteId: options.siteId,
217
+ campaignId,
218
+ email: recipient.email,
219
+ subject: params.subject,
220
+ type: 'broadcast',
221
+ status: 'queued',
222
+ trackingId,
223
+ createdAt: now,
224
+ });
225
+ queueRecipients.push({
226
+ email: recipient.email,
227
+ subscriberId: recipient.subscriberId,
228
+ trackingId,
229
+ unsubscribeToken,
230
+ preferencesToken,
231
+ });
232
+ }
233
+ // 5. Batched fan-out, paced to the configured send rate.
234
+ const batches = chunk(queueRecipients, maxRecipientsPerMessage);
235
+ const intervalMs = messagesPerSecond > 0 ? Math.ceil(1000 / messagesPerSecond) : 0;
236
+ for (let i = 0; i < batches.length; i++) {
237
+ const message = {
238
+ siteId: options.siteId,
239
+ type: 'broadcast',
240
+ recipients: batches[i],
241
+ subject: params.subject,
242
+ htmlTemplate: html,
243
+ from: `${options.senderName} <${options.fromAddress}>`,
244
+ replyTo: options.replyTo,
245
+ campaignId,
246
+ };
247
+ await queue.send(message);
248
+ if (intervalMs > 0 && i < batches.length - 1) {
249
+ await sleep(intervalMs);
250
+ }
251
+ }
252
+ return {
253
+ campaignId,
254
+ recipientCount: fresh.length,
255
+ suppressedCount,
256
+ alreadySentCount,
257
+ batchCount: batches.length,
258
+ dryRun: false,
259
+ };
260
+ }
261
+ //# sourceMappingURL=broadcast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../../src/utils/broadcast.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  export { buildMailerAnalyticsDataPoint, emitMailerAnalyticsEvent, type MailerAnalyticsEvent, } from './analytics.js';
2
2
  export { handleBounce, handleComplaint, handleDelivery, updateSendStatus, } from './bounce.js';
3
+ export type { BroadcastRecipient, SendBroadcastParams, SendBroadcastResult, } from './broadcast.js';
4
+ export { sendBroadcast } from './broadcast.js';
3
5
  export type { CloudflareEmailSender } from './providers.js';
4
6
  export { CloudflareEmailProvider, getProvider, sleep } from './providers.js';
5
7
  export type { CampaignSchedule, DigestSchedule } from './scheduling.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,6BAA6B,EAC7B,wBAAwB,EACxB,KAAK,oBAAoB,GACzB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GAChB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC5E,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;AACtB,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,6BAA6B,EAC7B,wBAAwB,EACxB,KAAK,oBAAoB,GACzB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GAChB,MAAM,aAAa,CAAA;AACpB,YAAY,EACX,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,GACnB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC5E,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;AACtB,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA"}
@@ -1,5 +1,6 @@
1
1
  export { buildMailerAnalyticsDataPoint, emitMailerAnalyticsEvent, } from './analytics.js';
2
2
  export { handleBounce, handleComplaint, handleDelivery, updateSendStatus, } from './bounce.js';
3
+ export { sendBroadcast } from './broadcast.js';
3
4
  export { CloudflareEmailProvider, getProvider, sleep } from './providers.js';
4
5
  export { executeCampaignSchedule, executeDigestSchedule, prepareCampaign, prepareDigest, sendBatchCampaigns, } from './scheduling.js';
5
6
  export { sendCampaign, sendDigest, sendTransactional } from './send.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,6BAA6B,EAC7B,wBAAwB,GAExB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GAChB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAE5E,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;AACtB,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,6BAA6B,EAC7B,wBAAwB,GAExB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GAChB,MAAM,aAAa,CAAA;AAMpB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAE5E,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;AACtB,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA"}
@@ -1 +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;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AA8B/C,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,CAyDjC;AA+ED,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"}
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;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAyC/C,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,CA0DjC;AAiFD,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"}
@@ -13,16 +13,22 @@ function resolveProducerBindings(env, options) {
13
13
  throw new Error(`[mailer] env.${options.d1Binding} is undefined; ` +
14
14
  'pass d1Binding option to the mailer integration or bind that name in wrangler.toml.');
15
15
  }
16
+ const sendsBindingName = options.sendsD1Binding ?? options.d1Binding;
17
+ const sendsD1 = env[sendsBindingName];
18
+ if (!sendsD1) {
19
+ throw new Error(`[mailer] env.${sendsBindingName} is undefined; ` +
20
+ 'pass sendsD1Binding option to the mailer integration or bind that name in wrangler.toml.');
21
+ }
16
22
  const queue = env[options.queueBinding];
17
23
  if (!queue) {
18
24
  throw new Error(`[mailer] env.${options.queueBinding} is undefined; ` +
19
25
  'pass queueBinding option to the mailer integration or bind that name in wrangler.toml.');
20
26
  }
21
- return { db: drizzle(d1), queue };
27
+ return { db: drizzle(d1), sendsDb: drizzle(sendsD1), queue };
22
28
  }
23
29
  // ─── sendTransactional ───
24
30
  export async function sendTransactional(env, options, params) {
25
- const { db, queue } = resolveProducerBindings(env, options);
31
+ const { db, sendsDb, queue } = resolveProducerBindings(env, options);
26
32
  const trackingId = ulid();
27
33
  const subscriberId = params.subscriberId ?? ulid();
28
34
  // Render HTML
@@ -44,10 +50,11 @@ export async function sendTransactional(env, options, params) {
44
50
  else {
45
51
  throw new Error('Either template or html must be provided');
46
52
  }
47
- // Record the send
48
- await db.insert(emailSends).values({
53
+ // Record the send (in the sends DB — may differ from the subscribers DB)
54
+ await sendsDb.insert(emailSends).values({
49
55
  id: ulid(),
50
56
  subscriberId,
57
+ siteId: options.siteId,
51
58
  email: params.to,
52
59
  subject: params.subject,
53
60
  type: 'transactional',
@@ -77,7 +84,7 @@ export async function sendTransactional(env, options, params) {
77
84
  return { trackingId };
78
85
  }
79
86
  // ─── Fan-out helper (shared by campaign + digest) ───
80
- async function fanOutToSubscribers(queue, db, options, params) {
87
+ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
81
88
  let cursor = null;
82
89
  let recipientCount = 0;
83
90
  const filter = params.filter ?? { status: ['active'] };
@@ -98,9 +105,10 @@ async function fanOutToSubscribers(queue, db, options, params) {
98
105
  subscriberId: sub.id,
99
106
  action: 'preferences',
100
107
  });
101
- await db.insert(emailSends).values({
108
+ await sendsDb.insert(emailSends).values({
102
109
  id: ulid(),
103
110
  subscriberId: sub.id,
111
+ siteId: options.siteId,
104
112
  campaignId: params.campaignId,
105
113
  email: sub.email,
106
114
  subject: params.subject,
@@ -136,7 +144,7 @@ async function fanOutToSubscribers(queue, db, options, params) {
136
144
  // ─── sendCampaign ───
137
145
  export async function sendCampaign(env, options, params) {
138
146
  const campaignId = params.campaignId ?? ulid();
139
- const { db, queue } = resolveProducerBindings(env, options);
147
+ const { db, sendsDb, queue } = resolveProducerBindings(env, options);
140
148
  // Render HTML
141
149
  let html;
142
150
  if (params.template) {
@@ -160,7 +168,7 @@ export async function sendCampaign(env, options, params) {
160
168
  const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
161
169
  html = injectTrackingPixel(html, trackOpenUrl);
162
170
  html = rewriteLinksForTracking(html, trackClickUrl);
163
- const recipientCount = await fanOutToSubscribers(queue, db, options, {
171
+ const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, {
164
172
  subject: params.subject,
165
173
  html,
166
174
  campaignId,
@@ -172,7 +180,7 @@ export async function sendCampaign(env, options, params) {
172
180
  // ─── sendDigest ───
173
181
  export async function sendDigest(env, options, params) {
174
182
  const campaignId = params.campaignId ?? ulid();
175
- const { db, queue } = resolveProducerBindings(env, options);
183
+ const { db, sendsDb, queue } = resolveProducerBindings(env, options);
176
184
  // Render digest items then full template
177
185
  const itemsHtml = renderDigestItems(params.items);
178
186
  let html = renderEmail('digest', {
@@ -189,7 +197,7 @@ export async function sendDigest(env, options, params) {
189
197
  const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
190
198
  html = injectTrackingPixel(html, trackOpenUrl);
191
199
  html = rewriteLinksForTracking(html, trackClickUrl);
192
- const recipientCount = await fanOutToSubscribers(queue, db, options, {
200
+ const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, {
193
201
  subject: params.subject,
194
202
  html,
195
203
  campaignId,
@@ -1 +1 @@
1
- {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ -- Adds site_id to gl_email_sends so the send-record table can be centralized
2
+ -- in a realm D1 shared across sites (the mail-platform consumer model).
3
+ ALTER TABLE gl_email_sends ADD COLUMN site_id TEXT;
4
+ CREATE INDEX IF NOT EXISTS idx_sends_site_campaign ON gl_email_sends(site_id, campaign_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-labs/mailer",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -89,7 +89,7 @@
89
89
  "drizzle-kit": "^0.30.0",
90
90
  "typescript": "^5.7.0",
91
91
  "vite": "^7.3.2",
92
- "vitest": "^3.0.0"
92
+ "vitest": "^4.1.8"
93
93
  },
94
94
  "scripts": {
95
95
  "build": "tsc",
@@ -30,6 +30,7 @@ const formId = `gl-subscribe-${Math.random().toString(36).slice(2, 8)}`;
30
30
  method="POST"
31
31
  action={config.subscribePath}
32
32
  data-gl-subscribe-form
33
+ data-subscribe-path={config.subscribePath}
33
34
  >
34
35
  <div class="gl-subscribe-fields">
35
36
  {showNameField && (
@@ -87,75 +88,87 @@ const formId = `gl-subscribe-${Math.random().toString(36).slice(2, 8)}`;
87
88
  </form>
88
89
  </div>
89
90
 
90
- <script define:vars={{ subscribePath: config.subscribePath, formId }}>
91
- ;(function () {
92
- const container = document.getElementById(formId)
93
- if (!container) return
94
-
95
- const form = container.querySelector('[data-gl-subscribe-form]')
96
- const submitBtn = container.querySelector('[data-gl-submit]')
97
- const messageEl = container.querySelector('[data-gl-message]')
98
- const successEl = container.querySelector('[data-gl-success]')
99
- const errorEl = container.querySelector('[data-gl-error]')
100
-
101
- // Load Turnstile
91
+ <script>
92
+ // Hoisted (bundled) module shared by every SubscribeForm on the page.
93
+ // Previously this used `define:vars`, which Astro cannot bundle, so the
94
+ // whole handler was re-emitted inline per form instance. Now it wires all
95
+ // forms from one cached script, reading per-form config from data-* attrs.
96
+ function initGlSubscribeForms() {
97
+ const forms = document.querySelectorAll<HTMLFormElement>(
98
+ "[data-gl-subscribe-form]",
99
+ );
100
+ if (forms.length === 0) return;
101
+
102
+ // Load Turnstile once per page (de-duped) when a subscribe form exists.
102
103
  if (!document.querySelector('script[src*="turnstile"]')) {
103
- const script = document.createElement('script')
104
- script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'
105
- script.async = true
106
- document.head.appendChild(script)
104
+ const turnstile = document.createElement("script");
105
+ turnstile.src = "https://challenges.cloudflare.com/turnstile/v0/api.js";
106
+ turnstile.async = true;
107
+ document.head.appendChild(turnstile);
107
108
  }
108
109
 
109
- form?.addEventListener('submit', async function (e) {
110
- e.preventDefault()
111
- if (!form || !submitBtn) return
112
-
113
- submitBtn.setAttribute('disabled', 'true')
114
- messageEl?.setAttribute('hidden', '')
115
- successEl?.setAttribute('hidden', '')
116
- errorEl?.setAttribute('hidden', '')
117
-
118
- const formData = new FormData(form)
119
- const preferences = formData.getAll('preferences')
120
- const turnstileInput = form.querySelector(
121
- '[name="cf-turnstile-response"]',
122
- )
123
- const turnstileToken = turnstileInput?.value ?? ''
124
-
125
- try {
126
- const res = await fetch(subscribePath, {
127
- method: 'POST',
128
- headers: { 'Content-Type': 'application/json' },
129
- body: JSON.stringify({
130
- email: formData.get('email'),
131
- name: formData.get('name') || undefined,
132
- source: formData.get('source'),
133
- preferences: preferences.length ? preferences : undefined,
134
- turnstileToken,
135
- }),
136
- })
137
-
138
- const data = await res.json()
139
-
140
- messageEl?.removeAttribute('hidden')
141
- if (data.success) {
142
- successEl?.removeAttribute('hidden')
143
- form.reset()
144
- } else {
110
+ for (const form of forms) {
111
+ if (form.dataset.glWired) continue;
112
+ form.dataset.glWired = "1";
113
+
114
+ const subscribePath = form.getAttribute("data-subscribe-path") || "";
115
+ const submitBtn = form.querySelector("[data-gl-submit]");
116
+ const messageEl = form.querySelector("[data-gl-message]");
117
+ const successEl = form.querySelector("[data-gl-success]");
118
+ const errorEl = form.querySelector("[data-gl-error]");
119
+
120
+ form.addEventListener("submit", async (e) => {
121
+ e.preventDefault();
122
+ if (!submitBtn) return;
123
+
124
+ submitBtn.setAttribute("disabled", "true");
125
+ messageEl?.setAttribute("hidden", "");
126
+ successEl?.setAttribute("hidden", "");
127
+ errorEl?.setAttribute("hidden", "");
128
+
129
+ const formData = new FormData(form);
130
+ const preferences = formData.getAll("preferences");
131
+ const turnstileInput = form.querySelector<HTMLInputElement>(
132
+ '[name="cf-turnstile-response"]',
133
+ );
134
+ const turnstileToken = turnstileInput?.value ?? "";
135
+
136
+ try {
137
+ const res = await fetch(subscribePath, {
138
+ method: "POST",
139
+ headers: { "Content-Type": "application/json" },
140
+ body: JSON.stringify({
141
+ email: formData.get("email"),
142
+ name: formData.get("name") || undefined,
143
+ source: formData.get("source"),
144
+ preferences: preferences.length ? preferences : undefined,
145
+ turnstileToken,
146
+ }),
147
+ });
148
+
149
+ const data = await res.json();
150
+
151
+ messageEl?.removeAttribute("hidden");
152
+ if (data.success) {
153
+ successEl?.removeAttribute("hidden");
154
+ form.reset();
155
+ } else if (errorEl) {
156
+ errorEl.textContent = data.error || "Something went wrong";
157
+ errorEl.removeAttribute("hidden");
158
+ }
159
+ } catch {
160
+ messageEl?.removeAttribute("hidden");
145
161
  if (errorEl) {
146
- errorEl.textContent = data.error || 'Something went wrong'
147
- errorEl.removeAttribute('hidden')
162
+ errorEl.textContent = "Network error. Please try again.";
163
+ errorEl.removeAttribute("hidden");
148
164
  }
165
+ } finally {
166
+ submitBtn.removeAttribute("disabled");
149
167
  }
150
- } catch {
151
- messageEl?.removeAttribute('hidden')
152
- if (errorEl) {
153
- errorEl.textContent = 'Network error. Please try again.'
154
- errorEl.removeAttribute('hidden')
155
- }
156
- } finally {
157
- submitBtn.removeAttribute('disabled')
158
- }
159
- })
160
- })()
168
+ });
169
+ }
170
+ }
171
+
172
+ initGlSubscribeForms();
173
+ document.addEventListener("astro:page-load", initGlSubscribeForms);
161
174
  </script>