@growth-labs/mailer 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- 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
- package/dist/schema.d.ts +0 -564
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js +0 -47
- package/dist/schema.js.map +0 -1
package/README.md
CHANGED
|
@@ -46,6 +46,12 @@ mailer({
|
|
|
46
46
|
})
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Production patterns
|
|
50
|
+
|
|
51
|
+
**`from` vs `replyTo`.** A common pattern is `fromAddress: 'noreply@mail.site.com'` paired with `replyTo: 'hello@site.com'`. The `from` lives on a domain you've verified with Cloudflare Email Sending (typically a `mail.` subdomain); `replyTo` points at a real human inbox readers can write back to. `replyTo` is also a per-message override on `sendTransactional` and a per-site field on `SiteMailerConfig` (realm pattern, since 0.4.0).
|
|
52
|
+
|
|
53
|
+
**Don't bake fallbacks for `fromAddress`.** The schema requires a valid email (`z.string().email()`) and ships no default — that's intentional. A consumer-side fallback like `process.env.MAILER_FROM_ADDRESS ?? 'hello@site.com'` is a foot-gun: if the env var is missing at deploy time the fallback domain probably isn't verified with Cloudflare Email Sending, every send returns `unauthorized_sender`, and the queue retries until the budget kills the message. Fail loudly at build/deploy when the env var is missing instead — the schema can't catch your fallback.
|
|
54
|
+
|
|
49
55
|
## What It Injects
|
|
50
56
|
|
|
51
57
|
**Routes:**
|
|
@@ -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":"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;AACtC,IAAI,YAAY,GAAuB,IAAI,CAAA;AAE3C,4DAA4D;AAC5D,MAAM,UAAU,sBAAsB;IACrC,OAAO,GAAG,IAAI,CAAA;IACd,YAAY,GAAG,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,EAA8B,EAC9B,SAAiB;IAEjB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAA;IAC3B,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,gBAAgB,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAA;QACtD,OAAO,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;QACvB,OAAO,OAAO,CAAA;IACf,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,KAAK,EAAE,CAAA;QAChE,MAAM,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,KAAK,EAAE,CAAA;QAChE,OAAO,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACtB,OAAO,OAAO,CAAA;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChE,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACpC,OAAO,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;QACvB,OAAO,OAAO,CAAA;IACf,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,EAA8B,EAC9B,SAAiB;IAEjB,IAAI,YAAY;QAAE,OAAO,YAAY,CAAA;IACrC,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,qBAAqB,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAA;QAC3D,YAAY,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;QAC5B,OAAO,YAAY,CAAA;IACpB,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,KAAK,EAAE,CAAA;QAChE,YAAY,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QAC3B,OAAO,YAAY,CAAA;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChE,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACzC,YAAY,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;QAC5B,OAAO,YAAY,CAAA;IACpB,CAAC;AACF,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAiB,EAAE,UAAkB;IAC9D,OAAO,CAAC,KAAK,CACZ,IAAI,wBAAwB,sCAAsC,SAAS,OAAO;QACjF,sEAAsE;QACtE,gBAAgB;QAChB,gEAAgE;QAChE,yEAAyE;QACzE,oDAAoD,SAAS,aAAa;QAC1E,iEAAiE;QACjE,wDAAwD;QACxD,qBAAqB,UAAU,EAAE,CAClC,CAAA;AACF,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB,EAAE,UAAkB;IACnE,OAAO,CAAC,KAAK,CACZ,IAAI,wBAAwB,sCAAsC,SAAS,OAAO;QACjF,qCAAqC;QACrC,gBAAgB;QAChB,wEAAwE;QACxE,oDAAoD,SAAS,aAAa;QAC1E,kEAAkE;QAClE,qBAAqB,UAAU,EAAE,CAClC,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB;IACpC,OAAO,QAAQ,CAAC,IAAI,CACnB;QACC,KAAK,EAAE,kCAAkC;QACzC,IAAI,EAAE,wBAAwB;KAC9B,EACD,EAAE,MAAM,EAAE,GAAG,EAAE,CACf,CAAA;AACF,CAAC"}
|
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":"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;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,UAAU,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACD;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACxB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;AAErC,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;CACxB;AAID,MAAM,WAAW,SAAS;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAA;IAC7C,MAAM,EAAE,eAAe,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,eAAe,GACxB,QAAQ,GACR,MAAM,GACN,WAAW,GACX,QAAQ,GACR,SAAS,GACT,SAAS,GACT,YAAY,CAAA;AAIf,MAAM,WAAW,aAAa;IAC7B,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;CAClB;AAID,MAAM,MAAM,YAAY,GACrB,cAAc,GACd,SAAS,GACT,UAAU,GACV,QAAQ,GACR,eAAe,GACf,qBAAqB,CAAA;AAExB,MAAM,WAAW,YAAY;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IACD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAID,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -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
|