@growth-labs/mailer 0.4.11 → 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/README.md +18 -2
- package/dist/options.d.ts +12 -6
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +4 -2
- package/dist/options.js.map +1 -1
- 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/routes/confirm.d.ts.map +1 -1
- package/dist/routes/confirm.js +6 -2
- package/dist/routes/confirm.js.map +1 -1
- package/dist/routes/subscribe.d.ts.map +1 -1
- package/dist/routes/subscribe.js +11 -3
- package/dist/routes/subscribe.js.map +1 -1
- package/dist/routes/unsubscribe.d.ts.map +1 -1
- package/dist/routes/unsubscribe.js +14 -3
- package/dist/routes/unsubscribe.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 +17 -3
- 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/runtime-secrets.d.ts +25 -0
- package/dist/utils/runtime-secrets.d.ts.map +1 -0
- package/dist/utils/runtime-secrets.js +96 -0
- package/dist/utils/runtime-secrets.js.map +1 -0
- package/dist/utils/send.d.ts.map +1 -1
- package/dist/utils/send.js +24 -6
- package/dist/utils/send.js.map +1 -1
- package/dist/utils/tokens.d.ts +3 -1
- package/dist/utils/tokens.d.ts.map +1 -1
- package/dist/utils/tokens.js +14 -5
- package/dist/utils/tokens.js.map +1 -1
- package/dist/utils/urls.d.ts +1 -1
- package/dist/utils/urls.d.ts.map +1 -1
- package/dist/utils/urls.js +9 -3
- package/dist/utils/urls.js.map +1 -1
- package/dist/vite-plugin.d.ts +1 -0
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +13 -1
- package/dist/vite-plugin.js.map +1 -1
- package/migrations/0003_add_delivery_claim_to_email_sends.sql +8 -0
- package/package.json +4 -4
- package/src/options.ts +4 -2
- package/src/queue/consumer.ts +116 -2
- package/src/routes/confirm.ts +9 -2
- package/src/routes/preferences.astro +12 -3
- package/src/routes/subscribe.ts +15 -3
- package/src/routes/unsubscribe.ts +16 -3
- package/src/schema/sends.ts +2 -0
- package/src/types.ts +23 -0
- package/src/utils/broadcast.ts +16 -3
- package/src/utils/delivery-claim.ts +66 -0
- package/src/utils/runtime-secrets.ts +144 -0
- package/src/utils/send.ts +27 -5
- package/src/utils/tokens.ts +16 -5
- package/src/utils/urls.ts +9 -6
- package/src/virtual.d.ts +4 -2
- package/src/vite-plugin.ts +18 -1
package/README.md
CHANGED
|
@@ -26,10 +26,10 @@ mailer({
|
|
|
26
26
|
d1Binding: 'SITE_DB',
|
|
27
27
|
queueBinding: 'EMAIL_QUEUE',
|
|
28
28
|
turnstileSiteKey: '...',
|
|
29
|
-
|
|
29
|
+
turnstileSecretBinding: 'TURNSTILE_SECRET_KEY', // Optional; this is the default
|
|
30
30
|
doubleOptIn: true,
|
|
31
31
|
topics: ['daily-digest', 'breaking-news'], // Optional topic-based subscriptions
|
|
32
|
-
|
|
32
|
+
signingSecretBinding: 'MAILER_SIGNING_SECRET', // Optional; this is the default
|
|
33
33
|
siteUrl: 'https://fedweek.com',
|
|
34
34
|
brand: {
|
|
35
35
|
logoUrl: 'https://media.fedweek.com/logos/email.png',
|
|
@@ -52,6 +52,12 @@ mailer({
|
|
|
52
52
|
|
|
53
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
54
|
|
|
55
|
+
**Secrets resolve at request time.** Bind `TURNSTILE_SECRET_KEY` and
|
|
56
|
+
`MAILER_SIGNING_SECRET` on the Worker. Plain Worker secrets and Secrets Store
|
|
57
|
+
bindings are both supported; Secrets Store bindings are read with async `.get()`
|
|
58
|
+
and cached per isolate. Missing or empty production bindings return a 503 before
|
|
59
|
+
subscriber writes. Build-time literal options are ignored outside localhost.
|
|
60
|
+
|
|
55
61
|
## What It Injects
|
|
56
62
|
|
|
57
63
|
**Routes:**
|
|
@@ -90,6 +96,16 @@ max_batch_size = 10
|
|
|
90
96
|
[[analytics_engine_datasets]]
|
|
91
97
|
binding = "ANALYTICS"
|
|
92
98
|
dataset = "your_analytics_dataset"
|
|
99
|
+
|
|
100
|
+
[[secrets_store_secrets]]
|
|
101
|
+
binding = "TURNSTILE_SECRET_KEY"
|
|
102
|
+
store_id = "..."
|
|
103
|
+
secret_name = "your-turnstile-secret"
|
|
104
|
+
|
|
105
|
+
[[secrets_store_secrets]]
|
|
106
|
+
binding = "MAILER_SIGNING_SECRET"
|
|
107
|
+
store_id = "..."
|
|
108
|
+
secret_name = "your-mailer-signing-secret"
|
|
93
109
|
```
|
|
94
110
|
|
|
95
111
|
## Email Sending Flow
|
package/dist/options.d.ts
CHANGED
|
@@ -11,10 +11,12 @@ export declare const mailerOptionsSchema: z.ZodObject<{
|
|
|
11
11
|
senderBinding: z.ZodDefault<z.ZodString>;
|
|
12
12
|
siteConfigLookup: z.ZodOptional<z.ZodType<SiteConfigLookup, z.ZodTypeDef, SiteConfigLookup>>;
|
|
13
13
|
turnstileSiteKey: z.ZodString;
|
|
14
|
-
|
|
14
|
+
turnstileSecretBinding: z.ZodDefault<z.ZodString>;
|
|
15
|
+
turnstileSecretKey: z.ZodOptional<z.ZodString>;
|
|
15
16
|
doubleOptIn: z.ZodDefault<z.ZodBoolean>;
|
|
16
17
|
topics: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
|
-
|
|
18
|
+
signingSecretBinding: z.ZodDefault<z.ZodString>;
|
|
19
|
+
signingSecret: z.ZodOptional<z.ZodString>;
|
|
18
20
|
subscribePath: z.ZodDefault<z.ZodString>;
|
|
19
21
|
confirmPath: z.ZodDefault<z.ZodString>;
|
|
20
22
|
unsubscribePath: z.ZodDefault<z.ZodString>;
|
|
@@ -91,9 +93,9 @@ export declare const mailerOptionsSchema: z.ZodObject<{
|
|
|
91
93
|
queueBinding: string;
|
|
92
94
|
senderBinding: string;
|
|
93
95
|
turnstileSiteKey: string;
|
|
94
|
-
|
|
96
|
+
turnstileSecretBinding: string;
|
|
95
97
|
doubleOptIn: boolean;
|
|
96
|
-
|
|
98
|
+
signingSecretBinding: string;
|
|
97
99
|
subscribePath: string;
|
|
98
100
|
confirmPath: string;
|
|
99
101
|
unsubscribePath: string;
|
|
@@ -119,15 +121,15 @@ export declare const mailerOptionsSchema: z.ZodObject<{
|
|
|
119
121
|
replyTo?: string | undefined;
|
|
120
122
|
sendsD1Binding?: string | undefined;
|
|
121
123
|
siteConfigLookup?: SiteConfigLookup | undefined;
|
|
124
|
+
turnstileSecretKey?: string | undefined;
|
|
122
125
|
topics?: string[] | undefined;
|
|
126
|
+
signingSecret?: string | undefined;
|
|
123
127
|
}, {
|
|
124
128
|
senderName: string;
|
|
125
129
|
siteUrl: string;
|
|
126
130
|
siteId: string;
|
|
127
131
|
fromAddress: string;
|
|
128
132
|
turnstileSiteKey: string;
|
|
129
|
-
turnstileSecretKey: string;
|
|
130
|
-
signingSecret: string;
|
|
131
133
|
brand?: {
|
|
132
134
|
logoUrl?: string | undefined;
|
|
133
135
|
primaryColor?: string | undefined;
|
|
@@ -140,8 +142,12 @@ export declare const mailerOptionsSchema: z.ZodObject<{
|
|
|
140
142
|
queueBinding?: string | undefined;
|
|
141
143
|
senderBinding?: string | undefined;
|
|
142
144
|
siteConfigLookup?: SiteConfigLookup | undefined;
|
|
145
|
+
turnstileSecretBinding?: string | undefined;
|
|
146
|
+
turnstileSecretKey?: string | undefined;
|
|
143
147
|
doubleOptIn?: boolean | undefined;
|
|
144
148
|
topics?: string[] | undefined;
|
|
149
|
+
signingSecretBinding?: string | undefined;
|
|
150
|
+
signingSecret?: string | undefined;
|
|
145
151
|
subscribePath?: string | undefined;
|
|
146
152
|
confirmPath?: string | undefined;
|
|
147
153
|
unsubscribePath?: string | undefined;
|
package/dist/options.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,eAAO,MAAM,mBAAmB
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqF9B,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC/D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
|
package/dist/options.js
CHANGED
|
@@ -19,12 +19,14 @@ export const mailerOptionsSchema = z.object({
|
|
|
19
19
|
siteConfigLookup: z.custom().optional(),
|
|
20
20
|
// ─── Turnstile (subscribe form) ───
|
|
21
21
|
turnstileSiteKey: z.string(),
|
|
22
|
-
|
|
22
|
+
turnstileSecretBinding: z.string().min(1).default('TURNSTILE_SECRET_KEY'),
|
|
23
|
+
turnstileSecretKey: z.string().optional(),
|
|
23
24
|
// ─── Subscriber lifecycle ───
|
|
24
25
|
doubleOptIn: z.boolean().default(true),
|
|
25
26
|
topics: z.array(z.string()).optional(),
|
|
26
27
|
// ─── Token signing ───
|
|
27
|
-
|
|
28
|
+
signingSecretBinding: z.string().min(1).default('MAILER_SIGNING_SECRET'),
|
|
29
|
+
signingSecret: z.string().optional(),
|
|
28
30
|
// ─── Routes ───
|
|
29
31
|
subscribePath: z.string().default('/api/newsletter/subscribe'),
|
|
30
32
|
confirmPath: z.string().default('/api/newsletter/confirm'),
|
package/dist/options.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,mBAAmB;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;IAE/B,mBAAmB;IACnB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAEtC,8BAA8B;IAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACxC,yEAAyE;IACzE,0EAA0E;IAC1E,0EAA0E;IAC1E,oEAAoE;IACpE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC;IAC/C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC;IAEjD,qDAAqD;IACrD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAoB,CAAC,QAAQ,EAAE;IAEzD,qCAAqC;IACrC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC5B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../src/routes/confirm.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../src/routes/confirm.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAarC,eAAO,MAAM,GAAG,EAAE,QAyEjB,CAAA"}
|
package/dist/routes/confirm.js
CHANGED
|
@@ -3,6 +3,7 @@ import { config } from 'virtual:growth-labs/mailer/config';
|
|
|
3
3
|
import { drizzle } from 'drizzle-orm/d1';
|
|
4
4
|
import { probeMailerSchema, schemaMissingResponse } from '../_internal/schema-probe.js';
|
|
5
5
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js';
|
|
6
|
+
import { resolveSigningSecret, runtimeSecretErrorResponse, siteHost, } from '../utils/runtime-secrets.js';
|
|
6
7
|
import { sendTransactional } from '../utils/send.js';
|
|
7
8
|
import { confirmSubscriber, getSubscriberById } from '../utils/subscribers.js';
|
|
8
9
|
import { verifyToken } from '../utils/tokens.js';
|
|
@@ -12,13 +13,16 @@ export const GET = async (context) => {
|
|
|
12
13
|
if (!token) {
|
|
13
14
|
return Response.json({ error: 'Missing token' }, { status: 400 });
|
|
14
15
|
}
|
|
16
|
+
const bindingsEnv = cloudflareEnv;
|
|
17
|
+
const signingSecret = await resolveSigningSecret(bindingsEnv, config, url.toString());
|
|
18
|
+
if (!signingSecret.ok)
|
|
19
|
+
return runtimeSecretErrorResponse(signingSecret);
|
|
15
20
|
// Verify token
|
|
16
|
-
const payload = await verifyToken(
|
|
21
|
+
const payload = await verifyToken(signingSecret.secret, token, siteHost(config.siteUrl));
|
|
17
22
|
if (!payload || payload.action !== 'confirm') {
|
|
18
23
|
return Response.json({ error: 'Invalid or expired token' }, { status: 400 });
|
|
19
24
|
}
|
|
20
25
|
// Resolve bindings
|
|
21
|
-
const bindingsEnv = cloudflareEnv;
|
|
22
26
|
const d1 = bindingsEnv[config.d1Binding];
|
|
23
27
|
// Schema probe — return 503 with GL_MAILER_SCHEMA_MISSING on miss.
|
|
24
28
|
const schema = await probeMailerSchema(d1, config.d1Binding);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/routes/confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,MAAM,CAAC,MAAM,GAAG,GAAa,KAAK,EAAE,OAAO,EAAE,EAAE;IAC9C,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAA;IACvB,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAClE,CAAC;
|
|
1
|
+
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/routes/confirm.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/routes/subscribe.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/routes/subscribe.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAcrC,eAAO,MAAM,IAAI,EAAE,QA0HlB,CAAA"}
|
package/dist/routes/subscribe.js
CHANGED
|
@@ -3,6 +3,7 @@ import { config } from 'virtual:growth-labs/mailer/config';
|
|
|
3
3
|
import { drizzle } from 'drizzle-orm/d1';
|
|
4
4
|
import { probeMailerSchema, schemaMissingResponse } from '../_internal/schema-probe.js';
|
|
5
5
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js';
|
|
6
|
+
import { resolveSigningSecret, resolveTurnstileSecret, runtimeSecretErrorResponse, siteHost, } from '../utils/runtime-secrets.js';
|
|
6
7
|
import { sendTransactional } from '../utils/send.js';
|
|
7
8
|
import { createSubscriber } from '../utils/subscribers.js';
|
|
8
9
|
import { generateToken } from '../utils/tokens.js';
|
|
@@ -18,12 +19,19 @@ export const POST = async (context) => {
|
|
|
18
19
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email)) {
|
|
19
20
|
return Response.json({ error: 'Invalid email format' }, { status: 400 });
|
|
20
21
|
}
|
|
22
|
+
const bindingsEnv = cloudflareEnv;
|
|
23
|
+
const turnstileSecret = await resolveTurnstileSecret(bindingsEnv, config, request.url);
|
|
24
|
+
if (!turnstileSecret.ok)
|
|
25
|
+
return runtimeSecretErrorResponse(turnstileSecret);
|
|
26
|
+
const signingSecret = await resolveSigningSecret(bindingsEnv, config, request.url);
|
|
27
|
+
if (!signingSecret.ok)
|
|
28
|
+
return runtimeSecretErrorResponse(signingSecret);
|
|
21
29
|
// 4. Validate Turnstile token
|
|
22
30
|
const turnstileResponse = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
|
23
31
|
method: 'POST',
|
|
24
32
|
headers: { 'Content-Type': 'application/json' },
|
|
25
33
|
body: JSON.stringify({
|
|
26
|
-
secret:
|
|
34
|
+
secret: turnstileSecret.secret,
|
|
27
35
|
response: body.turnstileToken,
|
|
28
36
|
remoteip: request.headers.get('cf-connecting-ip') ?? undefined,
|
|
29
37
|
}),
|
|
@@ -33,7 +41,6 @@ export const POST = async (context) => {
|
|
|
33
41
|
return Response.json({ error: 'Turnstile verification failed' }, { status: 400 });
|
|
34
42
|
}
|
|
35
43
|
// 5. Resolve bindings
|
|
36
|
-
const bindingsEnv = cloudflareEnv;
|
|
37
44
|
const d1 = bindingsEnv[config.d1Binding];
|
|
38
45
|
// 5a. Schema probe — runs once per Worker instance. On miss, return 503
|
|
39
46
|
// with GL_MAILER_SCHEMA_MISSING so the consumer's site doesn't 500-cascade.
|
|
@@ -52,9 +59,10 @@ export const POST = async (context) => {
|
|
|
52
59
|
});
|
|
53
60
|
// 7. Send confirmation or welcome email
|
|
54
61
|
if (config.doubleOptIn && (isNew || subscriber.status === 'pending')) {
|
|
55
|
-
const confirmToken = await generateToken(
|
|
62
|
+
const confirmToken = await generateToken(signingSecret.secret, {
|
|
56
63
|
subscriberId: subscriber.id,
|
|
57
64
|
action: 'confirm',
|
|
65
|
+
host: siteHost(config.siteUrl),
|
|
58
66
|
});
|
|
59
67
|
const confirmUrl = `${config.siteUrl}${config.confirmPath}?token=${confirmToken}`;
|
|
60
68
|
await sendTransactional(bindingsEnv, config, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../src/routes/subscribe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,MAAM,CAAC,MAAM,IAAI,GAAa,KAAK,EAAE,OAAO,EAAE,EAAE;IAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAC3B,wBAAwB;IACxB,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAMjC,CAAA;IAED,8BAA8B;IAC9B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QACzC,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAC5E,CAAC;IAED,mCAAmC;IACnC,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IACzE,CAAC;IAED,8BAA8B;IAC9B,MAAM,iBAAiB,GAAG,MAAM,KAAK,CACpC,2DAA2D,EAC3D;QACC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACpB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"subscribe.js","sourceRoot":"","sources":["../../src/routes/subscribe.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unsubscribe.d.ts","sourceRoot":"","sources":["../../src/routes/unsubscribe.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"unsubscribe.d.ts","sourceRoot":"","sources":["../../src/routes/unsubscribe.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAgFrC,eAAO,MAAM,GAAG,EAAE,QA6BjB,CAAA;AAGD,eAAO,MAAM,IAAI,EAAE,QAkBlB,CAAA"}
|
|
@@ -3,18 +3,23 @@ import { config } from 'virtual:growth-labs/mailer/config';
|
|
|
3
3
|
import { drizzle } from 'drizzle-orm/d1';
|
|
4
4
|
import { probeMailerSchema, schemaMissingResponse } from '../_internal/schema-probe.js';
|
|
5
5
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js';
|
|
6
|
+
import { resolveSigningSecret, runtimeSecretErrorResponse, siteHost, } from '../utils/runtime-secrets.js';
|
|
6
7
|
import { sendTransactional } from '../utils/send.js';
|
|
7
8
|
import { getSubscriberById, unsubscribeSubscriber } from '../utils/subscribers.js';
|
|
8
9
|
import { generateToken, verifyToken } from '../utils/tokens.js';
|
|
9
10
|
import { buildSiteUrl } from '../utils/urls.js';
|
|
10
11
|
async function processUnsubscribe(token, context, request) {
|
|
12
|
+
const bindingsEnv = cloudflareEnv;
|
|
13
|
+
const requestUrl = request?.url ?? context?.url.toString() ?? config.siteUrl;
|
|
14
|
+
const signingSecret = await resolveSigningSecret(bindingsEnv, config, requestUrl);
|
|
15
|
+
if (!signingSecret.ok)
|
|
16
|
+
return { secretFailure: signingSecret };
|
|
11
17
|
// Verify token
|
|
12
|
-
const payload = await verifyToken(
|
|
18
|
+
const payload = await verifyToken(signingSecret.secret, token, siteHost(config.siteUrl));
|
|
13
19
|
if (!payload || payload.action !== 'unsubscribe') {
|
|
14
20
|
return { error: 'Invalid or expired token', status: 400 };
|
|
15
21
|
}
|
|
16
22
|
// Resolve bindings
|
|
17
|
-
const bindingsEnv = cloudflareEnv;
|
|
18
23
|
const d1 = bindingsEnv[config.d1Binding];
|
|
19
24
|
// Schema probe — short-circuit with a Response on miss; both GET and POST
|
|
20
25
|
// handlers below forward it unchanged.
|
|
@@ -53,6 +58,7 @@ async function processUnsubscribe(token, context, request) {
|
|
|
53
58
|
return {
|
|
54
59
|
success: true,
|
|
55
60
|
subscriberId: payload.subscriberId,
|
|
61
|
+
signingSecret: signingSecret.secret,
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
64
|
// GET: Link from email footer
|
|
@@ -63,15 +69,18 @@ export const GET = async (context) => {
|
|
|
63
69
|
return Response.json({ error: 'Missing token' }, { status: 400 });
|
|
64
70
|
}
|
|
65
71
|
const result = await processUnsubscribe(token, context, request);
|
|
72
|
+
if (result.secretFailure)
|
|
73
|
+
return runtimeSecretErrorResponse(result.secretFailure);
|
|
66
74
|
if ('schemaMissing' in result)
|
|
67
75
|
return schemaMissingResponse();
|
|
68
76
|
if ('error' in result) {
|
|
69
77
|
return Response.json({ error: result.error }, { status: result.status });
|
|
70
78
|
}
|
|
71
79
|
// Generate preferences token for the redirect
|
|
72
|
-
const preferencesToken = await generateToken(
|
|
80
|
+
const preferencesToken = await generateToken(result.signingSecret, {
|
|
73
81
|
subscriberId: result.subscriberId,
|
|
74
82
|
action: 'preferences',
|
|
83
|
+
host: siteHost(config.siteUrl),
|
|
75
84
|
});
|
|
76
85
|
const preferencesUrl = buildSiteUrl(config.siteUrl, config.preferencesPath, {
|
|
77
86
|
token: preferencesToken,
|
|
@@ -93,6 +102,8 @@ export const POST = async (context) => {
|
|
|
93
102
|
return new Response('Missing token', { status: 400 });
|
|
94
103
|
}
|
|
95
104
|
const result = await processUnsubscribe(token, context, request);
|
|
105
|
+
if (result.secretFailure)
|
|
106
|
+
return runtimeSecretErrorResponse(result.secretFailure);
|
|
96
107
|
if ('schemaMissing' in result)
|
|
97
108
|
return schemaMissingResponse();
|
|
98
109
|
if ('error' in result) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unsubscribe.js","sourceRoot":"","sources":["../../src/routes/unsubscribe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AACvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAClF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,KAAK,UAAU,kBAAkB,CAChC,KAAa,EACb,OAAiC,EACjC,OAAiB;IAEjB,
|
|
1
|
+
{"version":3,"file":"unsubscribe.js","sourceRoot":"","sources":["../../src/routes/unsubscribe.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;
|