@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.
Files changed (132) hide show
  1. package/README.md +89 -0
  2. package/dist/components/index.d.ts +3 -0
  3. package/dist/components/index.d.ts.map +1 -0
  4. package/dist/components/index.js +3 -0
  5. package/dist/components/index.js.map +1 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +65 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/middleware/tracking.d.ts +3 -0
  11. package/dist/middleware/tracking.d.ts.map +1 -0
  12. package/dist/middleware/tracking.js +13 -0
  13. package/dist/middleware/tracking.js.map +1 -0
  14. package/dist/options.d.ts +160 -0
  15. package/dist/options.d.ts.map +1 -0
  16. package/dist/options.js +51 -0
  17. package/dist/options.js.map +1 -0
  18. package/dist/queue/consumer.d.ts +8 -0
  19. package/dist/queue/consumer.d.ts.map +1 -0
  20. package/dist/queue/consumer.js +83 -0
  21. package/dist/queue/consumer.js.map +1 -0
  22. package/dist/routes/confirm.d.ts +3 -0
  23. package/dist/routes/confirm.d.ts.map +1 -0
  24. package/dist/routes/confirm.js +59 -0
  25. package/dist/routes/confirm.js.map +1 -0
  26. package/dist/routes/subscribe.d.ts +3 -0
  27. package/dist/routes/subscribe.d.ts.map +1 -0
  28. package/dist/routes/subscribe.js +87 -0
  29. package/dist/routes/subscribe.js.map +1 -0
  30. package/dist/routes/track-click.d.ts +3 -0
  31. package/dist/routes/track-click.d.ts.map +1 -0
  32. package/dist/routes/track-click.js +45 -0
  33. package/dist/routes/track-click.js.map +1 -0
  34. package/dist/routes/track-open.d.ts +3 -0
  35. package/dist/routes/track-open.d.ts.map +1 -0
  36. package/dist/routes/track-open.js +40 -0
  37. package/dist/routes/track-open.js.map +1 -0
  38. package/dist/routes/unsubscribe.d.ts +4 -0
  39. package/dist/routes/unsubscribe.d.ts.map +1 -0
  40. package/dist/routes/unsubscribe.js +81 -0
  41. package/dist/routes/unsubscribe.js.map +1 -0
  42. package/dist/routes/webhook.d.ts +3 -0
  43. package/dist/routes/webhook.d.ts.map +1 -0
  44. package/dist/routes/webhook.js +30 -0
  45. package/dist/routes/webhook.js.map +1 -0
  46. package/dist/schema.d.ts +564 -0
  47. package/dist/schema.d.ts.map +1 -0
  48. package/dist/schema.js +47 -0
  49. package/dist/schema.js.map +1 -0
  50. package/dist/types.d.ts +106 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +3 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/utils/bindings.d.ts +20 -0
  55. package/dist/utils/bindings.d.ts.map +1 -0
  56. package/dist/utils/bindings.js +19 -0
  57. package/dist/utils/bindings.js.map +1 -0
  58. package/dist/utils/bounce.d.ts +29 -0
  59. package/dist/utils/bounce.d.ts.map +1 -0
  60. package/dist/utils/bounce.js +59 -0
  61. package/dist/utils/bounce.js.map +1 -0
  62. package/dist/utils/index.d.ts +12 -0
  63. package/dist/utils/index.d.ts.map +1 -0
  64. package/dist/utils/index.js +9 -0
  65. package/dist/utils/index.js.map +1 -0
  66. package/dist/utils/providers.d.ts +31 -0
  67. package/dist/utils/providers.d.ts.map +1 -0
  68. package/dist/utils/providers.js +109 -0
  69. package/dist/utils/providers.js.map +1 -0
  70. package/dist/utils/scheduling.d.ts +89 -0
  71. package/dist/utils/scheduling.d.ts.map +1 -0
  72. package/dist/utils/scheduling.js +110 -0
  73. package/dist/utils/scheduling.js.map +1 -0
  74. package/dist/utils/send.d.ts +42 -0
  75. package/dist/utils/send.d.ts.map +1 -0
  76. package/dist/utils/send.js +193 -0
  77. package/dist/utils/send.js.map +1 -0
  78. package/dist/utils/subscribers.d.ts +23 -0
  79. package/dist/utils/subscribers.d.ts.map +1 -0
  80. package/dist/utils/subscribers.js +200 -0
  81. package/dist/utils/subscribers.js.map +1 -0
  82. package/dist/utils/templates.d.ts +16 -0
  83. package/dist/utils/templates.d.ts.map +1 -0
  84. package/dist/utils/templates.js +426 -0
  85. package/dist/utils/templates.js.map +1 -0
  86. package/dist/utils/tokens.d.ts +13 -0
  87. package/dist/utils/tokens.d.ts.map +1 -0
  88. package/dist/utils/tokens.js +62 -0
  89. package/dist/utils/tokens.js.map +1 -0
  90. package/dist/utils/tracking.d.ts +26 -0
  91. package/dist/utils/tracking.d.ts.map +1 -0
  92. package/dist/utils/tracking.js +49 -0
  93. package/dist/utils/tracking.js.map +1 -0
  94. package/dist/utils/urls.d.ts +7 -0
  95. package/dist/utils/urls.d.ts.map +1 -0
  96. package/dist/utils/urls.js +34 -0
  97. package/dist/utils/urls.js.map +1 -0
  98. package/dist/vite-plugin.d.ts +4 -0
  99. package/dist/vite-plugin.d.ts.map +1 -0
  100. package/dist/vite-plugin.js +18 -0
  101. package/dist/vite-plugin.js.map +1 -0
  102. package/package.json +85 -0
  103. package/src/astro.d.ts +4 -0
  104. package/src/components/PreferenceCenter.astro +147 -0
  105. package/src/components/SubscribeForm.astro +161 -0
  106. package/src/components/index.ts +2 -0
  107. package/src/index.ts +101 -0
  108. package/src/middleware/tracking.ts +18 -0
  109. package/src/options.ts +65 -0
  110. package/src/queue/consumer.ts +99 -0
  111. package/src/routes/confirm.ts +68 -0
  112. package/src/routes/preferences.astro +137 -0
  113. package/src/routes/subscribe.ts +107 -0
  114. package/src/routes/track-click.ts +57 -0
  115. package/src/routes/track-open.ts +51 -0
  116. package/src/routes/unsubscribe.ts +96 -0
  117. package/src/routes/webhook.ts +48 -0
  118. package/src/schema.ts +56 -0
  119. package/src/types.ts +145 -0
  120. package/src/utils/bindings.ts +28 -0
  121. package/src/utils/bounce.ts +77 -0
  122. package/src/utils/index.ts +47 -0
  123. package/src/utils/providers.ts +141 -0
  124. package/src/utils/scheduling.ts +188 -0
  125. package/src/utils/send.ts +282 -0
  126. package/src/utils/subscribers.ts +277 -0
  127. package/src/utils/templates.ts +459 -0
  128. package/src/utils/tokens.ts +91 -0
  129. package/src/utils/tracking.ts +58 -0
  130. package/src/utils/urls.ts +49 -0
  131. package/src/virtual.d.ts +32 -0
  132. 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"}