@growth-labs/mailer 0.5.0 → 0.5.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/queue/consumer.d.ts +12 -2
- package/dist/queue/consumer.d.ts.map +1 -1
- package/dist/queue/consumer.js +66 -0
- package/dist/queue/consumer.js.map +1 -1
- package/dist/schema/sends.d.ts +38 -0
- package/dist/schema/sends.d.ts.map +1 -1
- package/dist/schema/sends.js +2 -0
- package/dist/schema/sends.js.map +1 -1
- package/dist/types.d.ts +22 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/broadcast.d.ts +12 -0
- package/dist/utils/broadcast.d.ts.map +1 -1
- package/dist/utils/broadcast.js +7 -1
- package/dist/utils/broadcast.js.map +1 -1
- package/dist/utils/delivery-claim.d.ts +37 -0
- package/dist/utils/delivery-claim.d.ts.map +1 -0
- package/dist/utils/delivery-claim.js +46 -0
- package/dist/utils/delivery-claim.js.map +1 -0
- package/dist/utils/send.d.ts.map +1 -1
- package/dist/utils/send.js +6 -0
- package/dist/utils/send.js.map +1 -1
- package/migrations/0003_add_delivery_claim_to_email_sends.sql +8 -0
- package/package.json +3 -3
- package/src/queue/consumer.ts +116 -2
- package/src/schema/sends.ts +2 -0
- package/src/types.ts +23 -0
- package/src/utils/broadcast.ts +7 -1
- package/src/utils/delivery-claim.ts +66 -0
- package/src/utils/send.ts +6 -0
package/dist/queue/consumer.d.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type { EmailQueueMessage, SiteConfigLookup } from '../types.js';
|
|
2
|
-
export type { SiteConfigLookup, SiteMailerConfig } from '../types.js';
|
|
1
|
+
import type { EmailQueueMessage, SiteConfigLookup, SubscriberStatusLookup } from '../types.js';
|
|
2
|
+
export type { SiteConfigLookup, SiteMailerConfig, SubscriberStatusLookup } from '../types.js';
|
|
3
3
|
export interface MailerConsumerOptions {
|
|
4
4
|
d1Binding: string;
|
|
5
5
|
senderBinding: string;
|
|
6
6
|
analyticsBinding?: string;
|
|
7
7
|
siteConfigLookup: SiteConfigLookup;
|
|
8
|
+
/**
|
|
9
|
+
* Recheck a recipient's current suppression status at send admission
|
|
10
|
+
* (WM-01). Defaults to querying `gl_subscribers` on `env[d1Binding]` —
|
|
11
|
+
* correct for single-tenant sites. A realm consumer whose `d1Binding` D1
|
|
12
|
+
* holds `gl_email_sends` only (subscribers live in each site's own D1)
|
|
13
|
+
* must supply this for the recheck to be effective; unset in that
|
|
14
|
+
* topology, the consumer logs GL_MAILER_SUPPRESSION_CHECK_UNAVAILABLE
|
|
15
|
+
* once per instance and falls back to enqueue-time suppression only.
|
|
16
|
+
*/
|
|
17
|
+
subscriberStatusLookup?: SubscriberStatusLookup;
|
|
8
18
|
}
|
|
9
19
|
export declare function handleEmailQueue(batch: MessageBatch<EmailQueueMessage>, env: Record<string, unknown>, options: MailerConsumerOptions): Promise<void>;
|
|
10
20
|
//# 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,
|
|
1
|
+
{"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../../src/queue/consumer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,iBAAiB,EAEjB,gBAAgB,EAEhB,sBAAsB,EACtB,MAAM,aAAa,CAAA;AASpB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAI7F,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;CAC/C;AAkED,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,CAgNf"}
|
package/dist/queue/consumer.js
CHANGED
|
@@ -2,7 +2,41 @@ import { drizzle } from 'drizzle-orm/d1';
|
|
|
2
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
|
+
import { SUPPRESSED_STATUSES } from '../utils/broadcast.js';
|
|
6
|
+
import { claimDelivery } from '../utils/delivery-claim.js';
|
|
5
7
|
import { CloudflareEmailProvider, sleep } from '../utils/providers.js';
|
|
8
|
+
import { getSubscriberById } from '../utils/subscribers.js';
|
|
9
|
+
let subscriberLookupUnavailableLogged = false;
|
|
10
|
+
/**
|
|
11
|
+
* True when the recipient's subscriber row currently carries a suppressed
|
|
12
|
+
* status — rechecked at send admission, not trusted from enqueue time
|
|
13
|
+
* (WM-01). Falls back to a direct `gl_subscribers` query when no
|
|
14
|
+
* `subscriberStatusLookup` is configured; a query failure (table absent —
|
|
15
|
+
* the realm-consumer topology) logs once and fails open rather than
|
|
16
|
+
* silently blocking every send in a deployment this check can't reach.
|
|
17
|
+
*/
|
|
18
|
+
async function isCurrentlySuppressed(db, recipient, siteId, env, lookup) {
|
|
19
|
+
if (lookup) {
|
|
20
|
+
const status = await lookup({ subscriberId: recipient.subscriberId, siteId, email: recipient.email }, env);
|
|
21
|
+
return status !== null && SUPPRESSED_STATUSES.has(status);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const subscriber = await getSubscriberById(db, recipient.subscriberId);
|
|
25
|
+
return subscriber !== null && SUPPRESSED_STATUSES.has(subscriber.status);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (!subscriberLookupUnavailableLogged) {
|
|
29
|
+
subscriberLookupUnavailableLogged = true;
|
|
30
|
+
console.error('[mailer] GL_MAILER_SUPPRESSION_CHECK_UNAVAILABLE: cannot recheck suppression at send ' +
|
|
31
|
+
"time — gl_subscribers is not queryable on env.<d1Binding> (a realm consumer's " +
|
|
32
|
+
'd1Binding D1 holds gl_email_sends only; subscribers live in each site D1). Wire ' +
|
|
33
|
+
'MailerConsumerOptions.subscriberStatusLookup to close this gap. Sending without a ' +
|
|
34
|
+
'send-time recheck for this Worker instance. ' +
|
|
35
|
+
(err instanceof Error ? err.message : String(err)));
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
6
40
|
function applySiteConfig(config) {
|
|
7
41
|
return {
|
|
8
42
|
siteUrl: config.siteUrl,
|
|
@@ -62,6 +96,38 @@ export async function handleEmailQueue(batch, env, options) {
|
|
|
62
96
|
: from;
|
|
63
97
|
const effectiveReplyTo = effective.replyTo ?? replyTo;
|
|
64
98
|
for (const recipient of recipients) {
|
|
99
|
+
const claimedAt = new Date().toISOString();
|
|
100
|
+
const claim = await claimDelivery(db, recipient.trackingId, recipient.deliveryToken, claimedAt);
|
|
101
|
+
if (claim === 'missing' || claim === 'invalid-authorization') {
|
|
102
|
+
console.error(`[mailer] Refusing to send trackingId=${recipient.trackingId}: ${claim} — stale, ` +
|
|
103
|
+
'superseded, or a restore-era replay. Not sending, not touching the record.');
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (claim === 'already-terminal') {
|
|
107
|
+
// Duplicate enqueue or redelivery of an already fully-processed
|
|
108
|
+
// send — a no-op, not a resend.
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (claim === 'reconcile-in-flight') {
|
|
112
|
+
// A prior attempt claimed this row and never reached ack (crash or
|
|
113
|
+
// timeout before message.ack()). We cannot know whether the
|
|
114
|
+
// provider call actually completed, but calling it again risks a
|
|
115
|
+
// duplicate real send — reconcile as sent instead of resending.
|
|
116
|
+
await updateSendStatus(db, recipient.trackingId, 'sent', {
|
|
117
|
+
sentAt: new Date().toISOString(),
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// claim === 'claimed' — this invocation owns the send. Recheck
|
|
122
|
+
// suppression now: the subscriber's status may have changed since
|
|
123
|
+
// this message was enqueued.
|
|
124
|
+
if (type !== 'transactional') {
|
|
125
|
+
const suppressed = await isCurrentlySuppressed(db, recipient, message.body.siteId, env, options.subscriberStatusLookup);
|
|
126
|
+
if (suppressed) {
|
|
127
|
+
await updateSendStatus(db, recipient.trackingId, 'suppressed', {});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
65
131
|
let html = htmlTemplate;
|
|
66
132
|
if (type !== 'transactional') {
|
|
67
133
|
const siteUrl = effective.siteUrl ?? '';
|
|
@@ -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,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"consumer.js","sourceRoot":"","sources":["../../src/queue/consumer.ts"],"names":[],"mappings":"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"}
|
package/dist/schema/sends.d.ts
CHANGED
|
@@ -306,6 +306,44 @@ export declare const emailSends: import("drizzle-orm/sqlite-core").SQLiteTableWi
|
|
|
306
306
|
}, {}, {
|
|
307
307
|
length: number | undefined;
|
|
308
308
|
}>;
|
|
309
|
+
deliveryToken: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
310
|
+
name: "delivery_token";
|
|
311
|
+
tableName: "gl_email_sends";
|
|
312
|
+
dataType: "string";
|
|
313
|
+
columnType: "SQLiteText";
|
|
314
|
+
data: string;
|
|
315
|
+
driverParam: string;
|
|
316
|
+
notNull: false;
|
|
317
|
+
hasDefault: false;
|
|
318
|
+
isPrimaryKey: false;
|
|
319
|
+
isAutoincrement: false;
|
|
320
|
+
hasRuntimeDefault: false;
|
|
321
|
+
enumValues: [string, ...string[]];
|
|
322
|
+
baseColumn: never;
|
|
323
|
+
identity: undefined;
|
|
324
|
+
generated: undefined;
|
|
325
|
+
}, {}, {
|
|
326
|
+
length: number | undefined;
|
|
327
|
+
}>;
|
|
328
|
+
claimedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
329
|
+
name: "claimed_at";
|
|
330
|
+
tableName: "gl_email_sends";
|
|
331
|
+
dataType: "string";
|
|
332
|
+
columnType: "SQLiteText";
|
|
333
|
+
data: string;
|
|
334
|
+
driverParam: string;
|
|
335
|
+
notNull: false;
|
|
336
|
+
hasDefault: false;
|
|
337
|
+
isPrimaryKey: false;
|
|
338
|
+
isAutoincrement: false;
|
|
339
|
+
hasRuntimeDefault: false;
|
|
340
|
+
enumValues: [string, ...string[]];
|
|
341
|
+
baseColumn: never;
|
|
342
|
+
identity: undefined;
|
|
343
|
+
generated: undefined;
|
|
344
|
+
}, {}, {
|
|
345
|
+
length: number | undefined;
|
|
346
|
+
}>;
|
|
309
347
|
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
310
348
|
name: "created_at";
|
|
311
349
|
tableName: "gl_email_sends";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sends.d.ts","sourceRoot":"","sources":["../../src/schema/sends.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"sends.d.ts","sourceRoot":"","sources":["../../src/schema/sends.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BtB,CAAA"}
|
package/dist/schema/sends.js
CHANGED
|
@@ -16,6 +16,8 @@ export const emailSends = sqliteTable('gl_email_sends', {
|
|
|
16
16
|
bounceType: text('bounce_type'),
|
|
17
17
|
complainedAt: text('complained_at'),
|
|
18
18
|
trackingId: text('tracking_id').notNull().unique(),
|
|
19
|
+
deliveryToken: text('delivery_token'),
|
|
20
|
+
claimedAt: text('claimed_at'),
|
|
19
21
|
createdAt: text('created_at').notNull(),
|
|
20
22
|
}, (table) => [
|
|
21
23
|
index('idx_sends_subscriber').on(table.subscriberId),
|
package/dist/schema/sends.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sends.js","sourceRoot":"","sources":["../../src/schema/sends.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAElE,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CACpC,gBAAgB,EAChB;IACC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE;IAC7C,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;IACvB,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IAClC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAClD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC;IACjC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC;IAC3B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7B,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC;IACnC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE;IAClD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CACvC,EACD,CAAC,KAAK,EAAE,EAAE,CAAC;IACV,KAAK,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC;IACpD,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC;IAChD,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC;IAChD,KAAK,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;IAC1C,KAAK,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/D,KAAK,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC;CACnE,CACD,CAAA"}
|
|
1
|
+
{"version":3,"file":"sends.js","sourceRoot":"","sources":["../../src/schema/sends.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAElE,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CACpC,gBAAgB,EAChB;IACC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE;IAC7C,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;IACvB,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IAClC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAClD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC;IACjC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC;IAC3B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7B,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC;IACnC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE;IAClD,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC;IACrC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CACvC,EACD,CAAC,KAAK,EAAE,EAAE,CAAC;IACV,KAAK,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC;IACpD,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC;IAChD,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC;IAChD,KAAK,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;IAC1C,KAAK,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/D,KAAK,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC;CACnE,CACD,CAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -64,12 +64,33 @@ export interface SiteMailerConfig {
|
|
|
64
64
|
* should ack the message without sending because the registry entry is absent.
|
|
65
65
|
*/
|
|
66
66
|
export type SiteConfigLookup = (siteId: string, env: Record<string, unknown>) => Promise<SiteMailerConfig | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Recheck a recipient's current suppression status at send admission
|
|
69
|
+
* (WM-01). The default consumer implementation queries `gl_subscribers` on
|
|
70
|
+
* `env[d1Binding]`, which is correct for single-tenant sites. A realm
|
|
71
|
+
* consumer whose `d1Binding` D1 holds `gl_email_sends` only — subscribers
|
|
72
|
+
* live in each site's own D1 — must supply this to make the recheck
|
|
73
|
+
* effective for real; see MailerConsumerOptions.subscriberStatusLookup.
|
|
74
|
+
*/
|
|
75
|
+
export type SubscriberStatusLookup = (params: {
|
|
76
|
+
subscriberId: string;
|
|
77
|
+
siteId: string;
|
|
78
|
+
email: string;
|
|
79
|
+
}, env: Record<string, unknown>) => Promise<SubscriberStatus | null>;
|
|
67
80
|
export interface QueueRecipient {
|
|
68
81
|
email: string;
|
|
69
82
|
subscriberId: string;
|
|
70
83
|
trackingId: string;
|
|
71
84
|
unsubscribeToken: string;
|
|
72
85
|
preferencesToken: string;
|
|
86
|
+
/**
|
|
87
|
+
* Minted once at enqueue time and carried through to the consumer (WM-01).
|
|
88
|
+
* The consumer's delivery claim matches this against the current
|
|
89
|
+
* `gl_email_sends` row before sending, so a message superseded by a later
|
|
90
|
+
* resend cycle — or replayed from before a restore — can't act on a row
|
|
91
|
+
* it no longer authorizes.
|
|
92
|
+
*/
|
|
93
|
+
deliveryToken: string;
|
|
73
94
|
}
|
|
74
95
|
export interface EmailSend {
|
|
75
96
|
id: string;
|
|
@@ -89,7 +110,7 @@ export interface EmailSend {
|
|
|
89
110
|
trackingId: string;
|
|
90
111
|
createdAt: string;
|
|
91
112
|
}
|
|
92
|
-
export type EmailSendStatus = 'queued' | 'sent' | 'delivered' | 'opened' | 'clicked' | 'bounced' | 'complained';
|
|
113
|
+
export type EmailSendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'opened' | 'clicked' | 'bounced' | 'complained' | 'suppressed';
|
|
93
114
|
export interface EmailProvider {
|
|
94
115
|
send(email: OutboundEmail): Promise<SendResult>;
|
|
95
116
|
readonly name: string;
|
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,GAAG,WAAW,CAAA;IAC3D,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;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { ResolvedMailerOptions } from '../options.js';
|
|
2
|
+
import type { SubscriberStatus } from '../types.js';
|
|
2
3
|
import type { MailerEnv } from './send.js';
|
|
4
|
+
/**
|
|
5
|
+
* Statuses that mean "do not send" — the suppression list. A broadcast skips
|
|
6
|
+
* every recipient whose `gl_subscribers` row carries one of these. `active` is
|
|
7
|
+
* the ONLY sendable status; everything else (including `pending`, i.e. an
|
|
8
|
+
* unconfirmed double-opt-in subscriber) is suppressed so a one-off blast can
|
|
9
|
+
* never re-permission or re-mail a suppressed address.
|
|
10
|
+
*
|
|
11
|
+
* Exported so the queue consumer can recheck against the same set at send
|
|
12
|
+
* admission (WM-01) — the same statuses that block enqueue must block send.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SUPPRESSED_STATUSES: ReadonlySet<SubscriberStatus>;
|
|
3
15
|
/**
|
|
4
16
|
* One recipient of a broadcast. `subscriberId` is optional: when omitted the
|
|
5
17
|
* subscriber is resolved by `email` against `gl_subscribers` (needed both for
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../src/utils/broadcast.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../src/utils/broadcast.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAG1D,OAAO,KAAK,EAAqC,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAM1C;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,gBAAgB,CAK5D,CAAA;AAIF;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IACnC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAAA;IAC/C;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAAC,CAAA;IAExF,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IAEZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,gFAAgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAEhC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAA;IACvB,uFAAuF;IACvF,gBAAgB,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,OAAO,CAAA;CACf;AA8JD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAClC,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,mBAAmB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAwI9B"}
|
package/dist/utils/broadcast.js
CHANGED
|
@@ -13,8 +13,11 @@ import { injectTrackingPixel, rewriteLinksForTracking } from './tracking.js';
|
|
|
13
13
|
* the ONLY sendable status; everything else (including `pending`, i.e. an
|
|
14
14
|
* unconfirmed double-opt-in subscriber) is suppressed so a one-off blast can
|
|
15
15
|
* never re-permission or re-mail a suppressed address.
|
|
16
|
+
*
|
|
17
|
+
* Exported so the queue consumer can recheck against the same set at send
|
|
18
|
+
* admission (WM-01) — the same statuses that block enqueue must block send.
|
|
16
19
|
*/
|
|
17
|
-
const SUPPRESSED_STATUSES = new Set([
|
|
20
|
+
export const SUPPRESSED_STATUSES = new Set([
|
|
18
21
|
'unsubscribed',
|
|
19
22
|
'bounced',
|
|
20
23
|
'complained',
|
|
@@ -206,6 +209,7 @@ export async function sendBroadcast(env, options, params) {
|
|
|
206
209
|
const queueRecipients = [];
|
|
207
210
|
for (const recipient of fresh) {
|
|
208
211
|
const trackingId = ulid();
|
|
212
|
+
const deliveryToken = ulid();
|
|
209
213
|
const [unsubscribeToken, preferencesToken] = await Promise.all([
|
|
210
214
|
generateToken(signingSecret, {
|
|
211
215
|
subscriberId: recipient.subscriberId,
|
|
@@ -228,6 +232,7 @@ export async function sendBroadcast(env, options, params) {
|
|
|
228
232
|
type: 'broadcast',
|
|
229
233
|
status: 'queued',
|
|
230
234
|
trackingId,
|
|
235
|
+
deliveryToken,
|
|
231
236
|
createdAt: now,
|
|
232
237
|
});
|
|
233
238
|
queueRecipients.push({
|
|
@@ -236,6 +241,7 @@ export async function sendBroadcast(env, options, params) {
|
|
|
236
241
|
trackingId,
|
|
237
242
|
unsubscribeToken,
|
|
238
243
|
preferencesToken,
|
|
244
|
+
deliveryToken,
|
|
239
245
|
});
|
|
240
246
|
}
|
|
241
247
|
// 5. Batched fan-out, paced to the configured send rate.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../../src/utils/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAEtD,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAErE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAI5E
|
|
1
|
+
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../../src/utils/broadcast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { drizzle } from 'drizzle-orm/d1';
|
|
2
|
+
type DrizzleDB = ReturnType<typeof drizzle>;
|
|
3
|
+
export type ClaimOutcome =
|
|
4
|
+
/** This invocation owns the send — no prior claim exists. */
|
|
5
|
+
'claimed'
|
|
6
|
+
/** A prior claim never reached ack (crash/timeout). Reconcile, don't resend. */
|
|
7
|
+
| 'reconcile-in-flight'
|
|
8
|
+
/** The row is already in a terminal state — a duplicate enqueue, no-op. */
|
|
9
|
+
| 'already-terminal'
|
|
10
|
+
/** The row exists but under a different delivery_token — a superseded or replayed message. */
|
|
11
|
+
| 'invalid-authorization'
|
|
12
|
+
/** No row for this trackingId at all — lost or not-yet-visible write. */
|
|
13
|
+
| 'missing';
|
|
14
|
+
/**
|
|
15
|
+
* Atomically claim a queued send for this delivery attempt (WM-01) — the
|
|
16
|
+
* stable per-(campaign, recipient) delivery claim the mailer consumer needs.
|
|
17
|
+
* `trackingId` is unique per recipient/attempt; the update is a
|
|
18
|
+
* compare-and-swap gated on the row still being `queued`, so of two
|
|
19
|
+
* concurrent or sequential claim attempts for the same row only one can
|
|
20
|
+
* succeed — D1 serializes writes, so the loser always observes the winner's
|
|
21
|
+
* new status.
|
|
22
|
+
*
|
|
23
|
+
* `deliveryToken` is minted once at enqueue time and carried in the queue
|
|
24
|
+
* message. Matching it against the row's stored token means a message from
|
|
25
|
+
* a superseded generation of the same trackingId (a fresh resend cycle
|
|
26
|
+
* re-keyed the row, or the message is a stale replay from before a D1
|
|
27
|
+
* restore) can never claim or act on the current row — it reads back as
|
|
28
|
+
* `invalid-authorization`, never `claimed` or `reconcile-in-flight`.
|
|
29
|
+
*
|
|
30
|
+
* An empty `deliveryToken` skips the token match: queue messages already
|
|
31
|
+
* in flight when this column shipped carry no token, and requiring one
|
|
32
|
+
* would strand that one-time backlog instead of letting it drain under the
|
|
33
|
+
* trackingId + status guard alone.
|
|
34
|
+
*/
|
|
35
|
+
export declare function claimDelivery(db: DrizzleDB, trackingId: string, deliveryToken: string, now: string): Promise<ClaimOutcome>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=delivery-claim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-claim.d.ts","sourceRoot":"","sources":["../../src/utils/delivery-claim.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAG7C,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAE3C,MAAM,MAAM,YAAY;AACvB,6DAA6D;AAC3D,SAAS;AACX,gFAAgF;GAC9E,qBAAqB;AACvB,2EAA2E;GACzE,kBAAkB;AACpB,8FAA8F;GAC5F,uBAAuB;AACzB,yEAAyE;GACvE,SAAS,CAAA;AAEZ;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CAClC,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,CAAC,CAqBvB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { emailSends } from '../schema/sends.js';
|
|
3
|
+
/**
|
|
4
|
+
* Atomically claim a queued send for this delivery attempt (WM-01) — the
|
|
5
|
+
* stable per-(campaign, recipient) delivery claim the mailer consumer needs.
|
|
6
|
+
* `trackingId` is unique per recipient/attempt; the update is a
|
|
7
|
+
* compare-and-swap gated on the row still being `queued`, so of two
|
|
8
|
+
* concurrent or sequential claim attempts for the same row only one can
|
|
9
|
+
* succeed — D1 serializes writes, so the loser always observes the winner's
|
|
10
|
+
* new status.
|
|
11
|
+
*
|
|
12
|
+
* `deliveryToken` is minted once at enqueue time and carried in the queue
|
|
13
|
+
* message. Matching it against the row's stored token means a message from
|
|
14
|
+
* a superseded generation of the same trackingId (a fresh resend cycle
|
|
15
|
+
* re-keyed the row, or the message is a stale replay from before a D1
|
|
16
|
+
* restore) can never claim or act on the current row — it reads back as
|
|
17
|
+
* `invalid-authorization`, never `claimed` or `reconcile-in-flight`.
|
|
18
|
+
*
|
|
19
|
+
* An empty `deliveryToken` skips the token match: queue messages already
|
|
20
|
+
* in flight when this column shipped carry no token, and requiring one
|
|
21
|
+
* would strand that one-time backlog instead of letting it drain under the
|
|
22
|
+
* trackingId + status guard alone.
|
|
23
|
+
*/
|
|
24
|
+
export async function claimDelivery(db, trackingId, deliveryToken, now) {
|
|
25
|
+
const claimConditions = [eq(emailSends.trackingId, trackingId), eq(emailSends.status, 'queued')];
|
|
26
|
+
if (deliveryToken)
|
|
27
|
+
claimConditions.push(eq(emailSends.deliveryToken, deliveryToken));
|
|
28
|
+
const claimed = await db
|
|
29
|
+
.update(emailSends)
|
|
30
|
+
.set({ status: 'sending', claimedAt: now })
|
|
31
|
+
.where(and(...claimConditions))
|
|
32
|
+
.returning({ id: emailSends.id });
|
|
33
|
+
if (claimed.length > 0)
|
|
34
|
+
return 'claimed';
|
|
35
|
+
const rows = await db.select().from(emailSends).where(eq(emailSends.trackingId, trackingId));
|
|
36
|
+
if (rows.length === 0)
|
|
37
|
+
return 'missing';
|
|
38
|
+
const row = rows[0];
|
|
39
|
+
if (deliveryToken && row.deliveryToken && row.deliveryToken !== deliveryToken) {
|
|
40
|
+
return 'invalid-authorization';
|
|
41
|
+
}
|
|
42
|
+
if (row.status === 'sending')
|
|
43
|
+
return 'reconcile-in-flight';
|
|
44
|
+
return 'already-terminal';
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=delivery-claim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-claim.js","sourceRoot":"","sources":["../../src/utils/delivery-claim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAgB/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,EAAa,EACb,UAAkB,EAClB,aAAqB,EACrB,GAAW;IAEX,MAAM,eAAe,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IAChG,IAAI,aAAa;QAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CAAA;IAEpF,MAAM,OAAO,GAAG,MAAM,EAAE;SACtB,MAAM,CAAC,UAAU,CAAC;SAClB,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;SAC1C,KAAK,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC;SAC9B,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAA;IAElC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IAExC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAA;IAC5F,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAqD,CAAA;IACvE,IAAI,aAAa,IAAI,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,aAAa,KAAK,aAAa,EAAE,CAAC;QAC/E,OAAO,uBAAuB,CAAA;IAC/B,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,qBAAqB,CAAA;IAC1D,OAAO,kBAAkB,CAAA;AAC1B,CAAC"}
|
package/dist/utils/send.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,KAAK,EACX,UAAU,EAGV,gBAAgB,EAEhB,YAAY,EACZ,MAAM,aAAa,CAAA;AAQpB,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAI3C;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAkD/C,wBAAsB,iBAAiB,CACtC,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE;IACP,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B,GACC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
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;AAQpB,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAI3C;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAkD/C,wBAAsB,iBAAiB,CACtC,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE;IACP,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B,GACC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CA8DjC;AAuFD,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,CAqCzD;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,CAgCzD;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"}
|
package/dist/utils/send.js
CHANGED
|
@@ -38,6 +38,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
38
38
|
const signingSecret = params.subscriberId ? await requireSigningSecret(env, options) : undefined;
|
|
39
39
|
const { sendsDb, queue } = resolveProducerBindings(env, options);
|
|
40
40
|
const trackingId = ulid();
|
|
41
|
+
const deliveryToken = ulid();
|
|
41
42
|
const subscriberId = params.subscriberId ?? ulid();
|
|
42
43
|
// Render HTML
|
|
43
44
|
let html;
|
|
@@ -68,6 +69,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
68
69
|
type: 'transactional',
|
|
69
70
|
status: 'queued',
|
|
70
71
|
trackingId,
|
|
72
|
+
deliveryToken,
|
|
71
73
|
createdAt: new Date().toISOString(),
|
|
72
74
|
});
|
|
73
75
|
// Build and enqueue the message
|
|
@@ -81,6 +83,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
81
83
|
trackingId,
|
|
82
84
|
unsubscribeToken: '',
|
|
83
85
|
preferencesToken: '',
|
|
86
|
+
deliveryToken,
|
|
84
87
|
},
|
|
85
88
|
],
|
|
86
89
|
subject: params.subject,
|
|
@@ -105,6 +108,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, p
|
|
|
105
108
|
const recipients = [];
|
|
106
109
|
for (const sub of batch) {
|
|
107
110
|
const trackingId = ulid();
|
|
111
|
+
const deliveryToken = ulid();
|
|
108
112
|
const unsubscribeToken = await generateToken(signingSecret, {
|
|
109
113
|
subscriberId: sub.id,
|
|
110
114
|
action: 'unsubscribe',
|
|
@@ -125,6 +129,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, p
|
|
|
125
129
|
type: params.type,
|
|
126
130
|
status: 'queued',
|
|
127
131
|
trackingId,
|
|
132
|
+
deliveryToken,
|
|
128
133
|
createdAt: new Date().toISOString(),
|
|
129
134
|
});
|
|
130
135
|
recipients.push({
|
|
@@ -133,6 +138,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, p
|
|
|
133
138
|
trackingId,
|
|
134
139
|
unsubscribeToken,
|
|
135
140
|
preferencesToken,
|
|
141
|
+
deliveryToken,
|
|
136
142
|
});
|
|
137
143
|
}
|
|
138
144
|
const message = {
|
package/dist/utils/send.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- WM-01: send-time suppression recheck + a stable per-recipient delivery
|
|
2
|
+
-- claim. delivery_token is minted once at enqueue time and carried in the
|
|
3
|
+
-- queue message; the consumer's claim update matches on it so a message
|
|
4
|
+
-- superseded by a fresh resend cycle (or replayed from before a restore)
|
|
5
|
+
-- can never be actioned against the current row. claimed_at marks the
|
|
6
|
+
-- in-flight window between claiming the row and reconciling its outcome.
|
|
7
|
+
ALTER TABLE gl_email_sends ADD COLUMN delivery_token TEXT;
|
|
8
|
+
ALTER TABLE gl_email_sends ADD COLUMN claimed_at TEXT;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growth-labs/mailer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -85,10 +85,10 @@
|
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@cloudflare/workers-types": "^4.20260402.1",
|
|
88
|
-
"astro": "
|
|
88
|
+
"astro": "7.3.1",
|
|
89
89
|
"drizzle-kit": "^0.30.0",
|
|
90
90
|
"typescript": "^5.7.0",
|
|
91
|
-
"vite": "
|
|
91
|
+
"vite": "8.0.16",
|
|
92
92
|
"vitest": "^4.1.8"
|
|
93
93
|
},
|
|
94
94
|
"scripts": {
|
package/src/queue/consumer.ts
CHANGED
|
@@ -1,18 +1,82 @@
|
|
|
1
1
|
import { drizzle } from 'drizzle-orm/d1'
|
|
2
2
|
import { probeMailerSendsSchema } from '../_internal/schema-probe.js'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
EmailQueueMessage,
|
|
5
|
+
QueueRecipient,
|
|
6
|
+
SiteConfigLookup,
|
|
7
|
+
SiteMailerConfig,
|
|
8
|
+
SubscriberStatusLookup,
|
|
9
|
+
} from '../types.js'
|
|
4
10
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js'
|
|
5
11
|
import { updateSendStatus } from '../utils/bounce.js'
|
|
12
|
+
import { SUPPRESSED_STATUSES } from '../utils/broadcast.js'
|
|
13
|
+
import { claimDelivery } from '../utils/delivery-claim.js'
|
|
6
14
|
import type { CloudflareEmailSender } from '../utils/providers.js'
|
|
7
15
|
import { CloudflareEmailProvider, sleep } from '../utils/providers.js'
|
|
16
|
+
import { getSubscriberById } from '../utils/subscribers.js'
|
|
8
17
|
|
|
9
|
-
export type { SiteConfigLookup, SiteMailerConfig } from '../types.js'
|
|
18
|
+
export type { SiteConfigLookup, SiteMailerConfig, SubscriberStatusLookup } from '../types.js'
|
|
19
|
+
|
|
20
|
+
type DrizzleDB = ReturnType<typeof drizzle>
|
|
10
21
|
|
|
11
22
|
export interface MailerConsumerOptions {
|
|
12
23
|
d1Binding: string
|
|
13
24
|
senderBinding: string
|
|
14
25
|
analyticsBinding?: string
|
|
15
26
|
siteConfigLookup: SiteConfigLookup
|
|
27
|
+
/**
|
|
28
|
+
* Recheck a recipient's current suppression status at send admission
|
|
29
|
+
* (WM-01). Defaults to querying `gl_subscribers` on `env[d1Binding]` —
|
|
30
|
+
* correct for single-tenant sites. A realm consumer whose `d1Binding` D1
|
|
31
|
+
* holds `gl_email_sends` only (subscribers live in each site's own D1)
|
|
32
|
+
* must supply this for the recheck to be effective; unset in that
|
|
33
|
+
* topology, the consumer logs GL_MAILER_SUPPRESSION_CHECK_UNAVAILABLE
|
|
34
|
+
* once per instance and falls back to enqueue-time suppression only.
|
|
35
|
+
*/
|
|
36
|
+
subscriberStatusLookup?: SubscriberStatusLookup
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let subscriberLookupUnavailableLogged = false
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* True when the recipient's subscriber row currently carries a suppressed
|
|
43
|
+
* status — rechecked at send admission, not trusted from enqueue time
|
|
44
|
+
* (WM-01). Falls back to a direct `gl_subscribers` query when no
|
|
45
|
+
* `subscriberStatusLookup` is configured; a query failure (table absent —
|
|
46
|
+
* the realm-consumer topology) logs once and fails open rather than
|
|
47
|
+
* silently blocking every send in a deployment this check can't reach.
|
|
48
|
+
*/
|
|
49
|
+
async function isCurrentlySuppressed(
|
|
50
|
+
db: DrizzleDB,
|
|
51
|
+
recipient: QueueRecipient,
|
|
52
|
+
siteId: string,
|
|
53
|
+
env: Record<string, unknown>,
|
|
54
|
+
lookup: SubscriberStatusLookup | undefined,
|
|
55
|
+
): Promise<boolean> {
|
|
56
|
+
if (lookup) {
|
|
57
|
+
const status = await lookup(
|
|
58
|
+
{ subscriberId: recipient.subscriberId, siteId, email: recipient.email },
|
|
59
|
+
env,
|
|
60
|
+
)
|
|
61
|
+
return status !== null && SUPPRESSED_STATUSES.has(status)
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const subscriber = await getSubscriberById(db, recipient.subscriberId)
|
|
65
|
+
return subscriber !== null && SUPPRESSED_STATUSES.has(subscriber.status)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (!subscriberLookupUnavailableLogged) {
|
|
68
|
+
subscriberLookupUnavailableLogged = true
|
|
69
|
+
console.error(
|
|
70
|
+
'[mailer] GL_MAILER_SUPPRESSION_CHECK_UNAVAILABLE: cannot recheck suppression at send ' +
|
|
71
|
+
"time — gl_subscribers is not queryable on env.<d1Binding> (a realm consumer's " +
|
|
72
|
+
'd1Binding D1 holds gl_email_sends only; subscribers live in each site D1). Wire ' +
|
|
73
|
+
'MailerConsumerOptions.subscriberStatusLookup to close this gap. Sending without a ' +
|
|
74
|
+
'send-time recheck for this Worker instance. ' +
|
|
75
|
+
(err instanceof Error ? err.message : String(err)),
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
16
80
|
}
|
|
17
81
|
|
|
18
82
|
/** Effective options for a single message, resolved from `siteConfigLookup`. */
|
|
@@ -106,6 +170,56 @@ export async function handleEmailQueue(
|
|
|
106
170
|
const effectiveReplyTo = effective.replyTo ?? replyTo
|
|
107
171
|
|
|
108
172
|
for (const recipient of recipients) {
|
|
173
|
+
const claimedAt = new Date().toISOString()
|
|
174
|
+
const claim = await claimDelivery(
|
|
175
|
+
db,
|
|
176
|
+
recipient.trackingId,
|
|
177
|
+
recipient.deliveryToken,
|
|
178
|
+
claimedAt,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if (claim === 'missing' || claim === 'invalid-authorization') {
|
|
182
|
+
console.error(
|
|
183
|
+
`[mailer] Refusing to send trackingId=${recipient.trackingId}: ${claim} — stale, ` +
|
|
184
|
+
'superseded, or a restore-era replay. Not sending, not touching the record.',
|
|
185
|
+
)
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (claim === 'already-terminal') {
|
|
190
|
+
// Duplicate enqueue or redelivery of an already fully-processed
|
|
191
|
+
// send — a no-op, not a resend.
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (claim === 'reconcile-in-flight') {
|
|
196
|
+
// A prior attempt claimed this row and never reached ack (crash or
|
|
197
|
+
// timeout before message.ack()). We cannot know whether the
|
|
198
|
+
// provider call actually completed, but calling it again risks a
|
|
199
|
+
// duplicate real send — reconcile as sent instead of resending.
|
|
200
|
+
await updateSendStatus(db, recipient.trackingId, 'sent', {
|
|
201
|
+
sentAt: new Date().toISOString(),
|
|
202
|
+
})
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// claim === 'claimed' — this invocation owns the send. Recheck
|
|
207
|
+
// suppression now: the subscriber's status may have changed since
|
|
208
|
+
// this message was enqueued.
|
|
209
|
+
if (type !== 'transactional') {
|
|
210
|
+
const suppressed = await isCurrentlySuppressed(
|
|
211
|
+
db,
|
|
212
|
+
recipient,
|
|
213
|
+
message.body.siteId,
|
|
214
|
+
env,
|
|
215
|
+
options.subscriberStatusLookup,
|
|
216
|
+
)
|
|
217
|
+
if (suppressed) {
|
|
218
|
+
await updateSendStatus(db, recipient.trackingId, 'suppressed', {})
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
109
223
|
let html = htmlTemplate
|
|
110
224
|
if (type !== 'transactional') {
|
|
111
225
|
const siteUrl = effective.siteUrl ?? ''
|
package/src/schema/sends.ts
CHANGED
|
@@ -19,6 +19,8 @@ export const emailSends = sqliteTable(
|
|
|
19
19
|
bounceType: text('bounce_type'),
|
|
20
20
|
complainedAt: text('complained_at'),
|
|
21
21
|
trackingId: text('tracking_id').notNull().unique(),
|
|
22
|
+
deliveryToken: text('delivery_token'),
|
|
23
|
+
claimedAt: text('claimed_at'),
|
|
22
24
|
createdAt: text('created_at').notNull(),
|
|
23
25
|
},
|
|
24
26
|
(table) => [
|
package/src/types.ts
CHANGED
|
@@ -82,12 +82,33 @@ export type SiteConfigLookup = (
|
|
|
82
82
|
env: Record<string, unknown>,
|
|
83
83
|
) => Promise<SiteMailerConfig | null>
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Recheck a recipient's current suppression status at send admission
|
|
87
|
+
* (WM-01). The default consumer implementation queries `gl_subscribers` on
|
|
88
|
+
* `env[d1Binding]`, which is correct for single-tenant sites. A realm
|
|
89
|
+
* consumer whose `d1Binding` D1 holds `gl_email_sends` only — subscribers
|
|
90
|
+
* live in each site's own D1 — must supply this to make the recheck
|
|
91
|
+
* effective for real; see MailerConsumerOptions.subscriberStatusLookup.
|
|
92
|
+
*/
|
|
93
|
+
export type SubscriberStatusLookup = (
|
|
94
|
+
params: { subscriberId: string; siteId: string; email: string },
|
|
95
|
+
env: Record<string, unknown>,
|
|
96
|
+
) => Promise<SubscriberStatus | null>
|
|
97
|
+
|
|
85
98
|
export interface QueueRecipient {
|
|
86
99
|
email: string
|
|
87
100
|
subscriberId: string
|
|
88
101
|
trackingId: string
|
|
89
102
|
unsubscribeToken: string
|
|
90
103
|
preferencesToken: string
|
|
104
|
+
/**
|
|
105
|
+
* Minted once at enqueue time and carried through to the consumer (WM-01).
|
|
106
|
+
* The consumer's delivery claim matches this against the current
|
|
107
|
+
* `gl_email_sends` row before sending, so a message superseded by a later
|
|
108
|
+
* resend cycle — or replayed from before a restore — can't act on a row
|
|
109
|
+
* it no longer authorizes.
|
|
110
|
+
*/
|
|
111
|
+
deliveryToken: string
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
// ─── Email send record ───
|
|
@@ -113,12 +134,14 @@ export interface EmailSend {
|
|
|
113
134
|
|
|
114
135
|
export type EmailSendStatus =
|
|
115
136
|
| 'queued'
|
|
137
|
+
| 'sending'
|
|
116
138
|
| 'sent'
|
|
117
139
|
| 'delivered'
|
|
118
140
|
| 'opened'
|
|
119
141
|
| 'clicked'
|
|
120
142
|
| 'bounced'
|
|
121
143
|
| 'complained'
|
|
144
|
+
| 'suppressed'
|
|
122
145
|
|
|
123
146
|
// ─── Email provider interface ───
|
|
124
147
|
|
package/src/utils/broadcast.ts
CHANGED
|
@@ -19,8 +19,11 @@ type DrizzleDB = ReturnType<typeof drizzle>
|
|
|
19
19
|
* the ONLY sendable status; everything else (including `pending`, i.e. an
|
|
20
20
|
* unconfirmed double-opt-in subscriber) is suppressed so a one-off blast can
|
|
21
21
|
* never re-permission or re-mail a suppressed address.
|
|
22
|
+
*
|
|
23
|
+
* Exported so the queue consumer can recheck against the same set at send
|
|
24
|
+
* admission (WM-01) — the same statuses that block enqueue must block send.
|
|
22
25
|
*/
|
|
23
|
-
const SUPPRESSED_STATUSES: ReadonlySet<SubscriberStatus> = new Set<SubscriberStatus>([
|
|
26
|
+
export const SUPPRESSED_STATUSES: ReadonlySet<SubscriberStatus> = new Set<SubscriberStatus>([
|
|
24
27
|
'unsubscribed',
|
|
25
28
|
'bounced',
|
|
26
29
|
'complained',
|
|
@@ -343,6 +346,7 @@ export async function sendBroadcast(
|
|
|
343
346
|
const queueRecipients: QueueRecipient[] = []
|
|
344
347
|
for (const recipient of fresh) {
|
|
345
348
|
const trackingId = ulid()
|
|
349
|
+
const deliveryToken = ulid()
|
|
346
350
|
const [unsubscribeToken, preferencesToken] = await Promise.all([
|
|
347
351
|
generateToken(signingSecret, {
|
|
348
352
|
subscriberId: recipient.subscriberId,
|
|
@@ -366,6 +370,7 @@ export async function sendBroadcast(
|
|
|
366
370
|
type: 'broadcast',
|
|
367
371
|
status: 'queued',
|
|
368
372
|
trackingId,
|
|
373
|
+
deliveryToken,
|
|
369
374
|
createdAt: now,
|
|
370
375
|
})
|
|
371
376
|
|
|
@@ -375,6 +380,7 @@ export async function sendBroadcast(
|
|
|
375
380
|
trackingId,
|
|
376
381
|
unsubscribeToken,
|
|
377
382
|
preferencesToken,
|
|
383
|
+
deliveryToken,
|
|
378
384
|
})
|
|
379
385
|
}
|
|
380
386
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm'
|
|
2
|
+
import type { drizzle } from 'drizzle-orm/d1'
|
|
3
|
+
import { emailSends } from '../schema/sends.js'
|
|
4
|
+
|
|
5
|
+
type DrizzleDB = ReturnType<typeof drizzle>
|
|
6
|
+
|
|
7
|
+
export type ClaimOutcome =
|
|
8
|
+
/** This invocation owns the send — no prior claim exists. */
|
|
9
|
+
| 'claimed'
|
|
10
|
+
/** A prior claim never reached ack (crash/timeout). Reconcile, don't resend. */
|
|
11
|
+
| 'reconcile-in-flight'
|
|
12
|
+
/** The row is already in a terminal state — a duplicate enqueue, no-op. */
|
|
13
|
+
| 'already-terminal'
|
|
14
|
+
/** The row exists but under a different delivery_token — a superseded or replayed message. */
|
|
15
|
+
| 'invalid-authorization'
|
|
16
|
+
/** No row for this trackingId at all — lost or not-yet-visible write. */
|
|
17
|
+
| 'missing'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Atomically claim a queued send for this delivery attempt (WM-01) — the
|
|
21
|
+
* stable per-(campaign, recipient) delivery claim the mailer consumer needs.
|
|
22
|
+
* `trackingId` is unique per recipient/attempt; the update is a
|
|
23
|
+
* compare-and-swap gated on the row still being `queued`, so of two
|
|
24
|
+
* concurrent or sequential claim attempts for the same row only one can
|
|
25
|
+
* succeed — D1 serializes writes, so the loser always observes the winner's
|
|
26
|
+
* new status.
|
|
27
|
+
*
|
|
28
|
+
* `deliveryToken` is minted once at enqueue time and carried in the queue
|
|
29
|
+
* message. Matching it against the row's stored token means a message from
|
|
30
|
+
* a superseded generation of the same trackingId (a fresh resend cycle
|
|
31
|
+
* re-keyed the row, or the message is a stale replay from before a D1
|
|
32
|
+
* restore) can never claim or act on the current row — it reads back as
|
|
33
|
+
* `invalid-authorization`, never `claimed` or `reconcile-in-flight`.
|
|
34
|
+
*
|
|
35
|
+
* An empty `deliveryToken` skips the token match: queue messages already
|
|
36
|
+
* in flight when this column shipped carry no token, and requiring one
|
|
37
|
+
* would strand that one-time backlog instead of letting it drain under the
|
|
38
|
+
* trackingId + status guard alone.
|
|
39
|
+
*/
|
|
40
|
+
export async function claimDelivery(
|
|
41
|
+
db: DrizzleDB,
|
|
42
|
+
trackingId: string,
|
|
43
|
+
deliveryToken: string,
|
|
44
|
+
now: string,
|
|
45
|
+
): Promise<ClaimOutcome> {
|
|
46
|
+
const claimConditions = [eq(emailSends.trackingId, trackingId), eq(emailSends.status, 'queued')]
|
|
47
|
+
if (deliveryToken) claimConditions.push(eq(emailSends.deliveryToken, deliveryToken))
|
|
48
|
+
|
|
49
|
+
const claimed = await db
|
|
50
|
+
.update(emailSends)
|
|
51
|
+
.set({ status: 'sending', claimedAt: now })
|
|
52
|
+
.where(and(...claimConditions))
|
|
53
|
+
.returning({ id: emailSends.id })
|
|
54
|
+
|
|
55
|
+
if (claimed.length > 0) return 'claimed'
|
|
56
|
+
|
|
57
|
+
const rows = await db.select().from(emailSends).where(eq(emailSends.trackingId, trackingId))
|
|
58
|
+
if (rows.length === 0) return 'missing'
|
|
59
|
+
|
|
60
|
+
const row = rows[0] as { status: string; deliveryToken: string | null }
|
|
61
|
+
if (deliveryToken && row.deliveryToken && row.deliveryToken !== deliveryToken) {
|
|
62
|
+
return 'invalid-authorization'
|
|
63
|
+
}
|
|
64
|
+
if (row.status === 'sending') return 'reconcile-in-flight'
|
|
65
|
+
return 'already-terminal'
|
|
66
|
+
}
|
package/src/utils/send.ts
CHANGED
|
@@ -93,6 +93,7 @@ export async function sendTransactional(
|
|
|
93
93
|
const signingSecret = params.subscriberId ? await requireSigningSecret(env, options) : undefined
|
|
94
94
|
const { sendsDb, queue } = resolveProducerBindings(env, options)
|
|
95
95
|
const trackingId = ulid()
|
|
96
|
+
const deliveryToken = ulid()
|
|
96
97
|
const subscriberId = params.subscriberId ?? ulid()
|
|
97
98
|
|
|
98
99
|
// Render HTML
|
|
@@ -123,6 +124,7 @@ export async function sendTransactional(
|
|
|
123
124
|
type: 'transactional',
|
|
124
125
|
status: 'queued',
|
|
125
126
|
trackingId,
|
|
127
|
+
deliveryToken,
|
|
126
128
|
createdAt: new Date().toISOString(),
|
|
127
129
|
})
|
|
128
130
|
|
|
@@ -137,6 +139,7 @@ export async function sendTransactional(
|
|
|
137
139
|
trackingId,
|
|
138
140
|
unsubscribeToken: '',
|
|
139
141
|
preferencesToken: '',
|
|
142
|
+
deliveryToken,
|
|
140
143
|
},
|
|
141
144
|
],
|
|
142
145
|
subject: params.subject,
|
|
@@ -178,6 +181,7 @@ async function fanOutToSubscribers(
|
|
|
178
181
|
const recipients: QueueRecipient[] = []
|
|
179
182
|
for (const sub of batch) {
|
|
180
183
|
const trackingId = ulid()
|
|
184
|
+
const deliveryToken = ulid()
|
|
181
185
|
const unsubscribeToken = await generateToken(signingSecret, {
|
|
182
186
|
subscriberId: sub.id,
|
|
183
187
|
action: 'unsubscribe',
|
|
@@ -199,6 +203,7 @@ async function fanOutToSubscribers(
|
|
|
199
203
|
type: params.type,
|
|
200
204
|
status: 'queued',
|
|
201
205
|
trackingId,
|
|
206
|
+
deliveryToken,
|
|
202
207
|
createdAt: new Date().toISOString(),
|
|
203
208
|
})
|
|
204
209
|
|
|
@@ -208,6 +213,7 @@ async function fanOutToSubscribers(
|
|
|
208
213
|
trackingId,
|
|
209
214
|
unsubscribeToken,
|
|
210
215
|
preferencesToken,
|
|
216
|
+
deliveryToken,
|
|
211
217
|
})
|
|
212
218
|
}
|
|
213
219
|
|