@growth-labs/mailer 0.4.0 → 0.4.1
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/dist/_internal/schema-probe.d.ts +6 -0
- package/dist/_internal/schema-probe.d.ts.map +1 -1
- package/dist/_internal/schema-probe.js +36 -0
- package/dist/_internal/schema-probe.js.map +1 -1
- package/dist/queue/consumer.d.ts +9 -3
- package/dist/queue/consumer.d.ts.map +1 -1
- package/dist/queue/consumer.js +37 -18
- package/dist/queue/consumer.js.map +1 -1
- package/dist/types.d.ts +5 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/analytics.d.ts +7 -3
- package/dist/utils/analytics.d.ts.map +1 -1
- package/dist/utils/analytics.js.map +1 -1
- package/package.json +1 -1
- package/src/_internal/schema-probe.ts +41 -0
- package/src/queue/consumer.ts +96 -49
- package/src/types.ts +5 -8
- package/src/utils/analytics.ts +8 -4
|
@@ -20,6 +20,12 @@ export declare function _resetSchemaProbeCache(): void;
|
|
|
20
20
|
* than silent.
|
|
21
21
|
*/
|
|
22
22
|
export declare function probeMailerSchema(db: D1DatabaseLike | undefined, d1Binding: string): Promise<ProbeResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Probe only `gl_email_sends`. Realm queue consumers keep subscriber tables in
|
|
25
|
+
* each site D1, so the realm D1 must be allowed to contain sends without
|
|
26
|
+
* subscribers.
|
|
27
|
+
*/
|
|
28
|
+
export declare function probeMailerSendsSchema(db: D1DatabaseLike | undefined, d1Binding: string): Promise<ProbeResult>;
|
|
23
29
|
/**
|
|
24
30
|
* Helper: build the standard 503 response mailer routes return on schema
|
|
25
31
|
* miss. The body shape is stable so observability dashboards can match on
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-probe.d.ts","sourceRoot":"","sources":["../../src/_internal/schema-probe.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAElE,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,CAAA;AAEzC,UAAU,mBAAmB;IAC5B,KAAK,CAAC,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;CACvC;AAED,UAAU,cAAc;IACvB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAAA;CAC3C;
|
|
1
|
+
{"version":3,"file":"schema-probe.d.ts","sourceRoot":"","sources":["../../src/_internal/schema-probe.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAElE,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,CAAA;AAEzC,UAAU,mBAAmB;IAC5B,KAAK,CAAC,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;CACvC;AAED,UAAU,cAAc;IACvB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAAA;CAC3C;AAKD,4DAA4D;AAC5D,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACtC,EAAE,EAAE,cAAc,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAkBtB;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC3C,EAAE,EAAE,cAAc,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAiBtB;AA4BD;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAQhD"}
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
// body, observability gets one loud error per instance startup.
|
|
10
10
|
export const GL_MAILER_SCHEMA_MISSING = 'GL_MAILER_SCHEMA_MISSING';
|
|
11
11
|
let _cached = null;
|
|
12
|
+
let _sendsCached = null;
|
|
12
13
|
/** @internal Reset the module-scoped cache — tests only. */
|
|
13
14
|
export function _resetSchemaProbeCache() {
|
|
14
15
|
_cached = null;
|
|
16
|
+
_sendsCached = null;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Probe the configured D1 binding for `gl_subscribers` AND `gl_email_sends`.
|
|
@@ -43,6 +45,31 @@ export async function probeMailerSchema(db, d1Binding) {
|
|
|
43
45
|
return _cached;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Probe only `gl_email_sends`. Realm queue consumers keep subscriber tables in
|
|
50
|
+
* each site D1, so the realm D1 must be allowed to contain sends without
|
|
51
|
+
* subscribers.
|
|
52
|
+
*/
|
|
53
|
+
export async function probeMailerSendsSchema(db, d1Binding) {
|
|
54
|
+
if (_sendsCached)
|
|
55
|
+
return _sendsCached;
|
|
56
|
+
if (!db) {
|
|
57
|
+
logSendsSchemaMissing(d1Binding, 'D1 binding is not bound');
|
|
58
|
+
_sendsCached = { ok: false };
|
|
59
|
+
return _sendsCached;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await db.prepare('SELECT 1 FROM gl_email_sends LIMIT 1').first();
|
|
63
|
+
_sendsCached = { ok: true };
|
|
64
|
+
return _sendsCached;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
logSendsSchemaMissing(d1Binding, message);
|
|
69
|
+
_sendsCached = { ok: false };
|
|
70
|
+
return _sendsCached;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
46
73
|
function logSchemaMissing(d1Binding, underlying) {
|
|
47
74
|
console.error(`[${GL_MAILER_SCHEMA_MISSING}] @growth-labs/mailer: D1 binding "${d1Binding}" is ` +
|
|
48
75
|
'missing one or both of the gl_subscribers / gl_email_sends tables.\n' +
|
|
@@ -54,6 +81,15 @@ function logSchemaMissing(d1Binding, underlying) {
|
|
|
54
81
|
'Mailer routes return 503 until the schema is present. ' +
|
|
55
82
|
`Underlying error: ${underlying}`);
|
|
56
83
|
}
|
|
84
|
+
function logSendsSchemaMissing(d1Binding, underlying) {
|
|
85
|
+
console.error(`[${GL_MAILER_SCHEMA_MISSING}] @growth-labs/mailer: D1 binding "${d1Binding}" is ` +
|
|
86
|
+
'missing the gl_email_sends table.\n' +
|
|
87
|
+
'Remediation:\n' +
|
|
88
|
+
' 1. Add a sends-only migration for @growth-labs/mailer/schema/sends\n' +
|
|
89
|
+
` 2. Run: pnpm exec wrangler d1 migrations apply ${d1Binding} --remote\n` +
|
|
90
|
+
'Realm queue consumers ack messages until the schema is present. ' +
|
|
91
|
+
`Underlying error: ${underlying}`);
|
|
92
|
+
}
|
|
57
93
|
/**
|
|
58
94
|
* Helper: build the standard 503 response mailer routes return on schema
|
|
59
95
|
* miss. The body shape is stable so observability dashboards can match on
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-probe.js","sourceRoot":"","sources":["../../src/_internal/schema-probe.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAC9E,6DAA6D;AAC7D,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,2EAA2E;AAC3E,gEAAgE;AAEhE,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAA;AAYlE,IAAI,OAAO,GAAuB,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"schema-probe.js","sourceRoot":"","sources":["../../src/_internal/schema-probe.ts"],"names":[],"mappings":"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"}
|
package/dist/queue/consumer.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export
|
|
1
|
+
import type { EmailQueueMessage, SiteConfigLookup } from '../types.js';
|
|
2
|
+
export type { SiteConfigLookup, SiteMailerConfig } from '../types.js';
|
|
3
|
+
export interface MailerConsumerOptions {
|
|
4
|
+
d1Binding: string;
|
|
5
|
+
senderBinding: string;
|
|
6
|
+
analyticsBinding?: string;
|
|
7
|
+
siteConfigLookup: SiteConfigLookup;
|
|
8
|
+
}
|
|
9
|
+
export declare function handleEmailQueue(batch: MessageBatch<EmailQueueMessage>, env: Record<string, unknown>, options: MailerConsumerOptions): Promise<void>;
|
|
4
10
|
//# sourceMappingURL=consumer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../../src/queue/consumer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../../src/queue/consumer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAoB,MAAM,aAAa,CAAA;AAMxF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAErE,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,gBAAgB,CAAA;CAClC;AAuBD,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,YAAY,CAAC,iBAAiB,CAAC,EACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA8Jf"}
|
package/dist/queue/consumer.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { drizzle } from 'drizzle-orm/d1';
|
|
2
|
-
import {
|
|
2
|
+
import { probeMailerSendsSchema } from '../_internal/schema-probe.js';
|
|
3
3
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js';
|
|
4
4
|
import { updateSendStatus } from '../utils/bounce.js';
|
|
5
5
|
import { CloudflareEmailProvider, sleep } from '../utils/providers.js';
|
|
6
|
-
function
|
|
6
|
+
function applySiteConfig(config) {
|
|
7
7
|
return {
|
|
8
|
-
siteUrl:
|
|
9
|
-
unsubscribePath:
|
|
10
|
-
preferencesPath:
|
|
8
|
+
siteUrl: config.siteUrl,
|
|
9
|
+
unsubscribePath: config.unsubscribePath ?? '/api/newsletter/unsubscribe',
|
|
10
|
+
preferencesPath: config.preferencesPath ?? '/email/preferences',
|
|
11
|
+
senderName: config.senderName,
|
|
12
|
+
fromAddress: config.fromAddress,
|
|
13
|
+
replyTo: config.replyTo,
|
|
11
14
|
};
|
|
12
15
|
}
|
|
13
16
|
export async function handleEmailQueue(batch, env, options) {
|
|
@@ -25,7 +28,7 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
25
28
|
// GL_MAILER_SCHEMA_MISSING and we ack every message in the batch without
|
|
26
29
|
// retry. Re-queueing without the schema would cycle indefinitely and burn
|
|
27
30
|
// Cloudflare Queue retry budget.
|
|
28
|
-
const schemaProbe = await
|
|
31
|
+
const schemaProbe = await probeMailerSendsSchema(d1, options.d1Binding);
|
|
29
32
|
if (!schemaProbe.ok) {
|
|
30
33
|
console.error('[mailer] gl_email_sends not found in env.' +
|
|
31
34
|
options.d1Binding +
|
|
@@ -41,20 +44,29 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
41
44
|
const db = drizzle(d1);
|
|
42
45
|
const provider = new CloudflareEmailProvider(sender);
|
|
43
46
|
for (const message of batch.messages) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
const siteConfig = await options.siteConfigLookup(message.body.siteId, env);
|
|
48
|
+
if (!siteConfig) {
|
|
49
|
+
console.error(`[mailer] No SiteMailerConfig for siteId=${message.body.siteId}; acking without send.`);
|
|
50
|
+
message.ack();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const effective = applySiteConfig(siteConfig);
|
|
48
54
|
const { recipients, htmlTemplate, subject, from, replyTo, headers, type } = message.body;
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
if (type !== 'transactional' && !effective.siteUrl) {
|
|
56
|
+
console.error(`[mailer] SiteMailerConfig for siteId=${message.body.siteId} is missing siteUrl; acking without send.`);
|
|
57
|
+
message.ack();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const effectiveFrom = effective.fromAddress
|
|
61
|
+
? formatFromHeader(effective.senderName ?? effective.fromAddress, effective.fromAddress)
|
|
51
62
|
: from;
|
|
52
|
-
const effectiveReplyTo =
|
|
63
|
+
const effectiveReplyTo = effective.replyTo ?? replyTo;
|
|
53
64
|
for (const recipient of recipients) {
|
|
54
65
|
let html = htmlTemplate;
|
|
55
66
|
if (type !== 'transactional') {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
67
|
+
const siteUrl = effective.siteUrl ?? '';
|
|
68
|
+
const unsubscribeUrl = `${siteUrl}${effective.unsubscribePath}?token=${recipient.unsubscribeToken}`;
|
|
69
|
+
const preferencesUrl = `${siteUrl}${effective.preferencesPath}?token=${recipient.preferencesToken}`;
|
|
58
70
|
html = html
|
|
59
71
|
.replaceAll('{{TRACKING_ID}}', recipient.trackingId)
|
|
60
72
|
.replaceAll('{{UNSUBSCRIBE_URL}}', unsubscribeUrl)
|
|
@@ -63,7 +75,7 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
63
75
|
const recipientHeaders = type !== 'transactional'
|
|
64
76
|
? {
|
|
65
77
|
...headers,
|
|
66
|
-
'List-Unsubscribe': `<${effective.siteUrl}${effective.unsubscribePath}?token=${recipient.unsubscribeToken}>`,
|
|
78
|
+
'List-Unsubscribe': `<${effective.siteUrl ?? ''}${effective.unsubscribePath}?token=${recipient.unsubscribeToken}>`,
|
|
67
79
|
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
|
|
68
80
|
}
|
|
69
81
|
: headers;
|
|
@@ -94,7 +106,7 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
94
106
|
await updateSendStatus(db, recipient.trackingId, 'sent', {
|
|
95
107
|
sentAt: new Date().toISOString(),
|
|
96
108
|
});
|
|
97
|
-
emitMailerAnalyticsEvent(options, env, 'newsletter_sent', {
|
|
109
|
+
emitMailerAnalyticsEvent(analyticsOptions(options, effective, message.body.siteId), env, 'newsletter_sent', {
|
|
98
110
|
contentSlug: recipient.trackingId,
|
|
99
111
|
label: {
|
|
100
112
|
trackingId: recipient.trackingId,
|
|
@@ -111,7 +123,7 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
111
123
|
bouncedAt: new Date().toISOString(),
|
|
112
124
|
bounceType: 'hard',
|
|
113
125
|
});
|
|
114
|
-
emitMailerAnalyticsEvent(options, env, 'newsletter_send_failed', {
|
|
126
|
+
emitMailerAnalyticsEvent(analyticsOptions(options, effective, message.body.siteId), env, 'newsletter_send_failed', {
|
|
115
127
|
contentSlug: recipient.trackingId,
|
|
116
128
|
label: {
|
|
117
129
|
trackingId: recipient.trackingId,
|
|
@@ -132,4 +144,11 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
132
144
|
function formatFromHeader(senderName, fromAddress) {
|
|
133
145
|
return `${senderName} <${fromAddress}>`;
|
|
134
146
|
}
|
|
147
|
+
function analyticsOptions(options, effective, siteId) {
|
|
148
|
+
return {
|
|
149
|
+
siteUrl: effective.siteUrl ?? `https://${siteId}`,
|
|
150
|
+
analyticsEnabled: Boolean(options.analyticsBinding),
|
|
151
|
+
analyticsBinding: options.analyticsBinding ?? 'ANALYTICS',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
135
154
|
//# sourceMappingURL=consumer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consumer.js","sourceRoot":"","sources":["../../src/queue/consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"consumer.js","sourceRoot":"","sources":["../../src/queue/consumer.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -39,12 +39,9 @@ export interface EmailQueueMessage {
|
|
|
39
39
|
campaignId?: string;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* Per-site config
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Bindings (d1Binding, queueBinding, senderBinding) and route paths
|
|
47
|
-
* are NOT overridable per-site; they're realm-wide.
|
|
42
|
+
* Per-site config resolved at consumer time. Realm consumers keep bindings in
|
|
43
|
+
* their own Worker config and load sender / URL metadata from this object for
|
|
44
|
+
* every queued message.
|
|
48
45
|
*/
|
|
49
46
|
export interface SiteMailerConfig {
|
|
50
47
|
siteUrl?: string;
|
|
@@ -63,8 +60,8 @@ export interface SiteMailerConfig {
|
|
|
63
60
|
}
|
|
64
61
|
/**
|
|
65
62
|
* Per-message lookup, called by the realm consumer with the message's
|
|
66
|
-
* `siteId`. Returns the per-site config to apply, or `null`
|
|
67
|
-
*
|
|
63
|
+
* `siteId`. Returns the per-site config to apply, or `null` when the consumer
|
|
64
|
+
* should ack the message without sending because the registry entry is absent.
|
|
68
65
|
*/
|
|
69
66
|
export type SiteConfigLookup = (siteId: string, env: Record<string, unknown>) => Promise<SiteMailerConfig | null>;
|
|
70
67
|
export interface QueueRecipient {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,gBAAgB,CAAA;IACxB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,SAAS,GAAG,YAAY,CAAA;AAE/F,MAAM,WAAW,qBAAqB;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAID,MAAM,WAAW,gBAAgB;IAChC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB;AAID,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAA;IAC7C,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAID
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ResolvedMailerOptions } from '../options.js';
|
|
2
1
|
export type MailerAnalyticsEvent = 'newsletter_subscribed' | 'newsletter_confirmed' | 'newsletter_unsubscribed' | 'newsletter_opened' | 'newsletter_clicked' | 'newsletter_webhook_received' | 'newsletter_delivered' | 'newsletter_bounced' | 'newsletter_complained' | 'newsletter_sent' | 'newsletter_send_failed';
|
|
3
2
|
interface AnalyticsContext {
|
|
4
3
|
locals?: {
|
|
@@ -7,6 +6,11 @@ interface AnalyticsContext {
|
|
|
7
6
|
};
|
|
8
7
|
};
|
|
9
8
|
}
|
|
9
|
+
export interface MailerAnalyticsRuntimeOptions {
|
|
10
|
+
siteUrl: string;
|
|
11
|
+
analyticsEnabled: boolean;
|
|
12
|
+
analyticsBinding: string;
|
|
13
|
+
}
|
|
10
14
|
interface EmitMailerAnalyticsOptions {
|
|
11
15
|
request?: Request;
|
|
12
16
|
context?: AnalyticsContext;
|
|
@@ -14,8 +18,8 @@ interface EmitMailerAnalyticsOptions {
|
|
|
14
18
|
contentSlug?: string;
|
|
15
19
|
eventValue?: number;
|
|
16
20
|
}
|
|
17
|
-
export declare function emitMailerAnalyticsEvent(options:
|
|
18
|
-
export declare function buildMailerAnalyticsDataPoint(options:
|
|
21
|
+
export declare function emitMailerAnalyticsEvent(options: MailerAnalyticsRuntimeOptions, bindingsEnv: Record<string, unknown>, eventName: MailerAnalyticsEvent, emitOptions?: EmitMailerAnalyticsOptions): boolean;
|
|
22
|
+
export declare function buildMailerAnalyticsDataPoint(options: MailerAnalyticsRuntimeOptions, eventName: MailerAnalyticsEvent, emitOptions?: EmitMailerAnalyticsOptions): {
|
|
19
23
|
blobs: string[];
|
|
20
24
|
doubles: number[];
|
|
21
25
|
indexes: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAC7B,uBAAuB,GACvB,sBAAsB,GACtB,yBAAyB,GACzB,mBAAmB,GACnB,oBAAoB,GACpB,6BAA6B,GAC7B,sBAAsB,GACtB,oBAAoB,GACpB,uBAAuB,GACvB,iBAAiB,GACjB,wBAAwB,CAAA;AAM3B,UAAU,gBAAgB;IACzB,MAAM,CAAC,EAAE;QACR,SAAS,CAAC,EAAE;YACX,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;SAC1C,CAAA;KACD,CAAA;CACD;AAED,MAAM,WAAW,6BAA6B;IAC7C,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,OAAO,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;CACxB;AAED,UAAU,0BAA0B;IACnC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,6BAA6B,EACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,SAAS,EAAE,oBAAoB,EAC/B,WAAW,GAAE,0BAA+B,GAC1C,OAAO,CAeT;AAED,wBAAgB,6BAA6B,CAC5C,OAAO,EAAE,6BAA6B,EACtC,SAAS,EAAE,oBAAoB,EAC/B,WAAW,GAAE,0BAA+B,GAC1C;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CA+B3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -21,10 +21,12 @@ interface D1DatabaseLike {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
let _cached: ProbeResult | null = null
|
|
24
|
+
let _sendsCached: ProbeResult | null = null
|
|
24
25
|
|
|
25
26
|
/** @internal Reset the module-scoped cache — tests only. */
|
|
26
27
|
export function _resetSchemaProbeCache(): void {
|
|
27
28
|
_cached = null
|
|
29
|
+
_sendsCached = null
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -59,6 +61,33 @@ export async function probeMailerSchema(
|
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Probe only `gl_email_sends`. Realm queue consumers keep subscriber tables in
|
|
66
|
+
* each site D1, so the realm D1 must be allowed to contain sends without
|
|
67
|
+
* subscribers.
|
|
68
|
+
*/
|
|
69
|
+
export async function probeMailerSendsSchema(
|
|
70
|
+
db: D1DatabaseLike | undefined,
|
|
71
|
+
d1Binding: string,
|
|
72
|
+
): Promise<ProbeResult> {
|
|
73
|
+
if (_sendsCached) return _sendsCached
|
|
74
|
+
if (!db) {
|
|
75
|
+
logSendsSchemaMissing(d1Binding, 'D1 binding is not bound')
|
|
76
|
+
_sendsCached = { ok: false }
|
|
77
|
+
return _sendsCached
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await db.prepare('SELECT 1 FROM gl_email_sends LIMIT 1').first()
|
|
81
|
+
_sendsCached = { ok: true }
|
|
82
|
+
return _sendsCached
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
85
|
+
logSendsSchemaMissing(d1Binding, message)
|
|
86
|
+
_sendsCached = { ok: false }
|
|
87
|
+
return _sendsCached
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
function logSchemaMissing(d1Binding: string, underlying: string): void {
|
|
63
92
|
console.error(
|
|
64
93
|
`[${GL_MAILER_SCHEMA_MISSING}] @growth-labs/mailer: D1 binding "${d1Binding}" is ` +
|
|
@@ -73,6 +102,18 @@ function logSchemaMissing(d1Binding: string, underlying: string): void {
|
|
|
73
102
|
)
|
|
74
103
|
}
|
|
75
104
|
|
|
105
|
+
function logSendsSchemaMissing(d1Binding: string, underlying: string): void {
|
|
106
|
+
console.error(
|
|
107
|
+
`[${GL_MAILER_SCHEMA_MISSING}] @growth-labs/mailer: D1 binding "${d1Binding}" is ` +
|
|
108
|
+
'missing the gl_email_sends table.\n' +
|
|
109
|
+
'Remediation:\n' +
|
|
110
|
+
' 1. Add a sends-only migration for @growth-labs/mailer/schema/sends\n' +
|
|
111
|
+
` 2. Run: pnpm exec wrangler d1 migrations apply ${d1Binding} --remote\n` +
|
|
112
|
+
'Realm queue consumers ack messages until the schema is present. ' +
|
|
113
|
+
`Underlying error: ${underlying}`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
76
117
|
/**
|
|
77
118
|
* Helper: build the standard 503 response mailer routes return on schema
|
|
78
119
|
* miss. The body shape is stable so observability dashboards can match on
|
package/src/queue/consumer.ts
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
import { drizzle } from 'drizzle-orm/d1'
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import type { EmailQueueMessage, SiteMailerConfig } from '../types.js'
|
|
2
|
+
import { probeMailerSendsSchema } from '../_internal/schema-probe.js'
|
|
3
|
+
import type { EmailQueueMessage, SiteConfigLookup, SiteMailerConfig } from '../types.js'
|
|
5
4
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js'
|
|
6
5
|
import { updateSendStatus } from '../utils/bounce.js'
|
|
7
6
|
import type { CloudflareEmailSender } from '../utils/providers.js'
|
|
8
7
|
import { CloudflareEmailProvider, sleep } from '../utils/providers.js'
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
export type { SiteConfigLookup, SiteMailerConfig } from '../types.js'
|
|
10
|
+
|
|
11
|
+
export interface MailerConsumerOptions {
|
|
12
|
+
d1Binding: string
|
|
13
|
+
senderBinding: string
|
|
14
|
+
analyticsBinding?: string
|
|
15
|
+
siteConfigLookup: SiteConfigLookup
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Effective options for a single message, resolved from `siteConfigLookup`. */
|
|
14
19
|
interface EffectiveOptions {
|
|
15
|
-
siteUrl
|
|
20
|
+
siteUrl?: string
|
|
16
21
|
unsubscribePath: string
|
|
17
22
|
preferencesPath: string
|
|
23
|
+
senderName?: string
|
|
24
|
+
fromAddress?: string
|
|
25
|
+
replyTo?: string
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
function
|
|
21
|
-
options: ResolvedMailerOptions,
|
|
22
|
-
override: SiteMailerConfig | null,
|
|
23
|
-
): EffectiveOptions {
|
|
28
|
+
function applySiteConfig(config: SiteMailerConfig): EffectiveOptions {
|
|
24
29
|
return {
|
|
25
|
-
siteUrl:
|
|
26
|
-
unsubscribePath:
|
|
27
|
-
preferencesPath:
|
|
30
|
+
siteUrl: config.siteUrl,
|
|
31
|
+
unsubscribePath: config.unsubscribePath ?? '/api/newsletter/unsubscribe',
|
|
32
|
+
preferencesPath: config.preferencesPath ?? '/email/preferences',
|
|
33
|
+
senderName: config.senderName,
|
|
34
|
+
fromAddress: config.fromAddress,
|
|
35
|
+
replyTo: config.replyTo,
|
|
28
36
|
}
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
export async function handleEmailQueue(
|
|
32
40
|
batch: MessageBatch<EmailQueueMessage>,
|
|
33
41
|
env: Record<string, unknown>,
|
|
34
|
-
options:
|
|
42
|
+
options: MailerConsumerOptions,
|
|
35
43
|
): Promise<void> {
|
|
36
44
|
const d1 = env[options.d1Binding] as D1Database | undefined
|
|
37
45
|
if (!d1) {
|
|
@@ -53,7 +61,7 @@ export async function handleEmailQueue(
|
|
|
53
61
|
// GL_MAILER_SCHEMA_MISSING and we ack every message in the batch without
|
|
54
62
|
// retry. Re-queueing without the schema would cycle indefinitely and burn
|
|
55
63
|
// Cloudflare Queue retry budget.
|
|
56
|
-
const schemaProbe = await
|
|
64
|
+
const schemaProbe = await probeMailerSendsSchema(d1, options.d1Binding)
|
|
57
65
|
if (!schemaProbe.ok) {
|
|
58
66
|
console.error(
|
|
59
67
|
'[mailer] gl_email_sends not found in env.' +
|
|
@@ -73,23 +81,36 @@ export async function handleEmailQueue(
|
|
|
73
81
|
const provider = new CloudflareEmailProvider(sender)
|
|
74
82
|
|
|
75
83
|
for (const message of batch.messages) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
const siteConfig = await options.siteConfigLookup(message.body.siteId, env)
|
|
85
|
+
if (!siteConfig) {
|
|
86
|
+
console.error(
|
|
87
|
+
`[mailer] No SiteMailerConfig for siteId=${message.body.siteId}; acking without send.`,
|
|
88
|
+
)
|
|
89
|
+
message.ack()
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
const effective = applySiteConfig(siteConfig)
|
|
81
93
|
|
|
82
94
|
const { recipients, htmlTemplate, subject, from, replyTo, headers, type } = message.body
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
if (type !== 'transactional' && !effective.siteUrl) {
|
|
96
|
+
console.error(
|
|
97
|
+
`[mailer] SiteMailerConfig for siteId=${message.body.siteId} is missing siteUrl; acking without send.`,
|
|
98
|
+
)
|
|
99
|
+
message.ack()
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const effectiveFrom = effective.fromAddress
|
|
104
|
+
? formatFromHeader(effective.senderName ?? effective.fromAddress, effective.fromAddress)
|
|
85
105
|
: from
|
|
86
|
-
const effectiveReplyTo =
|
|
106
|
+
const effectiveReplyTo = effective.replyTo ?? replyTo
|
|
87
107
|
|
|
88
108
|
for (const recipient of recipients) {
|
|
89
109
|
let html = htmlTemplate
|
|
90
110
|
if (type !== 'transactional') {
|
|
91
|
-
const
|
|
92
|
-
const
|
|
111
|
+
const siteUrl = effective.siteUrl ?? ''
|
|
112
|
+
const unsubscribeUrl = `${siteUrl}${effective.unsubscribePath}?token=${recipient.unsubscribeToken}`
|
|
113
|
+
const preferencesUrl = `${siteUrl}${effective.preferencesPath}?token=${recipient.preferencesToken}`
|
|
93
114
|
html = html
|
|
94
115
|
.replaceAll('{{TRACKING_ID}}', recipient.trackingId)
|
|
95
116
|
.replaceAll('{{UNSUBSCRIBE_URL}}', unsubscribeUrl)
|
|
@@ -100,7 +121,7 @@ export async function handleEmailQueue(
|
|
|
100
121
|
type !== 'transactional'
|
|
101
122
|
? {
|
|
102
123
|
...headers,
|
|
103
|
-
'List-Unsubscribe': `<${effective.siteUrl}${effective.unsubscribePath}?token=${recipient.unsubscribeToken}>`,
|
|
124
|
+
'List-Unsubscribe': `<${effective.siteUrl ?? ''}${effective.unsubscribePath}?token=${recipient.unsubscribeToken}>`,
|
|
104
125
|
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
|
|
105
126
|
}
|
|
106
127
|
: headers
|
|
@@ -133,34 +154,44 @@ export async function handleEmailQueue(
|
|
|
133
154
|
await updateSendStatus(db, recipient.trackingId, 'sent', {
|
|
134
155
|
sentAt: new Date().toISOString(),
|
|
135
156
|
})
|
|
136
|
-
emitMailerAnalyticsEvent(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
157
|
+
emitMailerAnalyticsEvent(
|
|
158
|
+
analyticsOptions(options, effective, message.body.siteId),
|
|
159
|
+
env,
|
|
160
|
+
'newsletter_sent',
|
|
161
|
+
{
|
|
162
|
+
contentSlug: recipient.trackingId,
|
|
163
|
+
label: {
|
|
164
|
+
trackingId: recipient.trackingId,
|
|
165
|
+
subscriberId: recipient.subscriberId,
|
|
166
|
+
email: recipient.email,
|
|
167
|
+
campaignId: message.body.campaignId,
|
|
168
|
+
type,
|
|
169
|
+
siteId: message.body.siteId,
|
|
170
|
+
},
|
|
145
171
|
},
|
|
146
|
-
|
|
172
|
+
)
|
|
147
173
|
} else {
|
|
148
174
|
await updateSendStatus(db, recipient.trackingId, 'bounced', {
|
|
149
175
|
bouncedAt: new Date().toISOString(),
|
|
150
176
|
bounceType: 'hard',
|
|
151
177
|
})
|
|
152
|
-
emitMailerAnalyticsEvent(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
emitMailerAnalyticsEvent(
|
|
179
|
+
analyticsOptions(options, effective, message.body.siteId),
|
|
180
|
+
env,
|
|
181
|
+
'newsletter_send_failed',
|
|
182
|
+
{
|
|
183
|
+
contentSlug: recipient.trackingId,
|
|
184
|
+
label: {
|
|
185
|
+
trackingId: recipient.trackingId,
|
|
186
|
+
subscriberId: recipient.subscriberId,
|
|
187
|
+
email: recipient.email,
|
|
188
|
+
campaignId: message.body.campaignId,
|
|
189
|
+
type,
|
|
190
|
+
siteId: message.body.siteId,
|
|
191
|
+
error: result.error,
|
|
192
|
+
},
|
|
162
193
|
},
|
|
163
|
-
|
|
194
|
+
)
|
|
164
195
|
console.error(`[mailer] Send failed for ${recipient.email}: ${result.error}`)
|
|
165
196
|
}
|
|
166
197
|
}
|
|
@@ -172,3 +203,19 @@ export async function handleEmailQueue(
|
|
|
172
203
|
function formatFromHeader(senderName: string, fromAddress: string): string {
|
|
173
204
|
return `${senderName} <${fromAddress}>`
|
|
174
205
|
}
|
|
206
|
+
|
|
207
|
+
function analyticsOptions(
|
|
208
|
+
options: MailerConsumerOptions,
|
|
209
|
+
effective: EffectiveOptions,
|
|
210
|
+
siteId: string,
|
|
211
|
+
): {
|
|
212
|
+
siteUrl: string
|
|
213
|
+
analyticsEnabled: boolean
|
|
214
|
+
analyticsBinding: string
|
|
215
|
+
} {
|
|
216
|
+
return {
|
|
217
|
+
siteUrl: effective.siteUrl ?? `https://${siteId}`,
|
|
218
|
+
analyticsEnabled: Boolean(options.analyticsBinding),
|
|
219
|
+
analyticsBinding: options.analyticsBinding ?? 'ANALYTICS',
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -52,12 +52,9 @@ export interface EmailQueueMessage {
|
|
|
52
52
|
// ─── Realm-level multi-tenant config lookup ───
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Per-site config
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* Bindings (d1Binding, queueBinding, senderBinding) and route paths
|
|
60
|
-
* are NOT overridable per-site; they're realm-wide.
|
|
55
|
+
* Per-site config resolved at consumer time. Realm consumers keep bindings in
|
|
56
|
+
* their own Worker config and load sender / URL metadata from this object for
|
|
57
|
+
* every queued message.
|
|
61
58
|
*/
|
|
62
59
|
export interface SiteMailerConfig {
|
|
63
60
|
siteUrl?: string
|
|
@@ -77,8 +74,8 @@ export interface SiteMailerConfig {
|
|
|
77
74
|
|
|
78
75
|
/**
|
|
79
76
|
* Per-message lookup, called by the realm consumer with the message's
|
|
80
|
-
* `siteId`. Returns the per-site config to apply, or `null`
|
|
81
|
-
*
|
|
77
|
+
* `siteId`. Returns the per-site config to apply, or `null` when the consumer
|
|
78
|
+
* should ack the message without sending because the registry entry is absent.
|
|
82
79
|
*/
|
|
83
80
|
export type SiteConfigLookup = (
|
|
84
81
|
siteId: string,
|
package/src/utils/analytics.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { ResolvedMailerOptions } from '../options.js'
|
|
2
|
-
|
|
3
1
|
export type MailerAnalyticsEvent =
|
|
4
2
|
| 'newsletter_subscribed'
|
|
5
3
|
| 'newsletter_confirmed'
|
|
@@ -25,6 +23,12 @@ interface AnalyticsContext {
|
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
|
|
26
|
+
export interface MailerAnalyticsRuntimeOptions {
|
|
27
|
+
siteUrl: string
|
|
28
|
+
analyticsEnabled: boolean
|
|
29
|
+
analyticsBinding: string
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
interface EmitMailerAnalyticsOptions {
|
|
29
33
|
request?: Request
|
|
30
34
|
context?: AnalyticsContext
|
|
@@ -34,7 +38,7 @@ interface EmitMailerAnalyticsOptions {
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export function emitMailerAnalyticsEvent(
|
|
37
|
-
options:
|
|
41
|
+
options: MailerAnalyticsRuntimeOptions,
|
|
38
42
|
bindingsEnv: Record<string, unknown>,
|
|
39
43
|
eventName: MailerAnalyticsEvent,
|
|
40
44
|
emitOptions: EmitMailerAnalyticsOptions = {},
|
|
@@ -56,7 +60,7 @@ export function emitMailerAnalyticsEvent(
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
export function buildMailerAnalyticsDataPoint(
|
|
59
|
-
options:
|
|
63
|
+
options: MailerAnalyticsRuntimeOptions,
|
|
60
64
|
eventName: MailerAnalyticsEvent,
|
|
61
65
|
emitOptions: EmitMailerAnalyticsOptions = {},
|
|
62
66
|
): { blobs: string[]; doubles: number[]; indexes: string[] } {
|