@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/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
|
@@ -4,6 +4,7 @@ import { ulid } from 'ulidx';
|
|
|
4
4
|
import { emailSends } from '../schema/sends.js';
|
|
5
5
|
import { subscribers } from '../schema/subscribers.js';
|
|
6
6
|
import { sleep } from './providers.js';
|
|
7
|
+
import { resolveSigningSecret, siteHost } from './runtime-secrets.js';
|
|
7
8
|
import { generateToken } from './tokens.js';
|
|
8
9
|
import { injectTrackingPixel, rewriteLinksForTracking } from './tracking.js';
|
|
9
10
|
/**
|
|
@@ -12,8 +13,11 @@ import { injectTrackingPixel, rewriteLinksForTracking } from './tracking.js';
|
|
|
12
13
|
* the ONLY sendable status; everything else (including `pending`, i.e. an
|
|
13
14
|
* unconfirmed double-opt-in subscriber) is suppressed so a one-off blast can
|
|
14
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.
|
|
15
19
|
*/
|
|
16
|
-
const SUPPRESSED_STATUSES = new Set([
|
|
20
|
+
export const SUPPRESSED_STATUSES = new Set([
|
|
17
21
|
'unsubscribed',
|
|
18
22
|
'bounced',
|
|
19
23
|
'complained',
|
|
@@ -194,20 +198,28 @@ export async function sendBroadcast(env, options, params) {
|
|
|
194
198
|
dryRun: false,
|
|
195
199
|
};
|
|
196
200
|
}
|
|
201
|
+
const signingSecretResult = await resolveSigningSecret(env, options, options.siteUrl);
|
|
202
|
+
if (!signingSecretResult.ok)
|
|
203
|
+
throw new Error(signingSecretResult.code);
|
|
204
|
+
const signingSecret = signingSecretResult.secret;
|
|
205
|
+
const host = siteHost(options.siteUrl);
|
|
197
206
|
const html = prepareBroadcastHtml(params.html, options);
|
|
198
207
|
const now = new Date().toISOString();
|
|
199
208
|
// 4. Per-recipient row write + queue-recipient build.
|
|
200
209
|
const queueRecipients = [];
|
|
201
210
|
for (const recipient of fresh) {
|
|
202
211
|
const trackingId = ulid();
|
|
212
|
+
const deliveryToken = ulid();
|
|
203
213
|
const [unsubscribeToken, preferencesToken] = await Promise.all([
|
|
204
|
-
generateToken(
|
|
214
|
+
generateToken(signingSecret, {
|
|
205
215
|
subscriberId: recipient.subscriberId,
|
|
206
216
|
action: 'unsubscribe',
|
|
217
|
+
host,
|
|
207
218
|
}),
|
|
208
|
-
generateToken(
|
|
219
|
+
generateToken(signingSecret, {
|
|
209
220
|
subscriberId: recipient.subscriberId,
|
|
210
221
|
action: 'preferences',
|
|
222
|
+
host,
|
|
211
223
|
}),
|
|
212
224
|
]);
|
|
213
225
|
await sendsDb.insert(emailSends).values({
|
|
@@ -220,6 +232,7 @@ export async function sendBroadcast(env, options, params) {
|
|
|
220
232
|
type: 'broadcast',
|
|
221
233
|
status: 'queued',
|
|
222
234
|
trackingId,
|
|
235
|
+
deliveryToken,
|
|
223
236
|
createdAt: now,
|
|
224
237
|
});
|
|
225
238
|
queueRecipients.push({
|
|
@@ -228,6 +241,7 @@ export async function sendBroadcast(env, options, params) {
|
|
|
228
241
|
trackingId,
|
|
229
242
|
unsubscribeToken,
|
|
230
243
|
preferencesToken,
|
|
244
|
+
deliveryToken,
|
|
231
245
|
});
|
|
232
246
|
}
|
|
233
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;
|
|
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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ResolvedMailerOptions } from '../options.js';
|
|
2
|
+
export declare const GL_MAILER_TURNSTILE_UNCONFIGURED = "GL_MAILER_TURNSTILE_UNCONFIGURED";
|
|
3
|
+
export declare const GL_MAILER_SIGNING_UNCONFIGURED = "GL_MAILER_SIGNING_UNCONFIGURED";
|
|
4
|
+
type SecretKind = 'turnstile' | 'signing';
|
|
5
|
+
export type MailerRuntimeSecretFailure = {
|
|
6
|
+
ok: false;
|
|
7
|
+
code: typeof GL_MAILER_TURNSTILE_UNCONFIGURED | typeof GL_MAILER_SIGNING_UNCONFIGURED;
|
|
8
|
+
};
|
|
9
|
+
export type MailerRuntimeSecretResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
secret: string;
|
|
12
|
+
} | MailerRuntimeSecretFailure;
|
|
13
|
+
export declare function resolveMailerRuntimeSecret({ bindingsEnv, bindingName, legacyValue, requestUrl, kind, }: {
|
|
14
|
+
bindingsEnv: Record<string, unknown>;
|
|
15
|
+
bindingName: string;
|
|
16
|
+
legacyValue?: string;
|
|
17
|
+
requestUrl: string;
|
|
18
|
+
kind: SecretKind;
|
|
19
|
+
}): Promise<MailerRuntimeSecretResult>;
|
|
20
|
+
export declare function resolveTurnstileSecret(bindingsEnv: Record<string, unknown>, options: ResolvedMailerOptions, requestUrl: string): Promise<MailerRuntimeSecretResult>;
|
|
21
|
+
export declare function resolveSigningSecret(bindingsEnv: Record<string, unknown>, options: ResolvedMailerOptions, requestUrl: string): Promise<MailerRuntimeSecretResult>;
|
|
22
|
+
export declare function runtimeSecretErrorResponse(failure: MailerRuntimeSecretFailure): Response;
|
|
23
|
+
export declare function siteHost(siteUrl: string): string;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=runtime-secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-secrets.d.ts","sourceRoot":"","sources":["../../src/utils/runtime-secrets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,eAAO,MAAM,gCAAgC,qCAAqC,CAAA;AAClF,eAAO,MAAM,8BAA8B,mCAAmC,CAAA;AAQ9E,KAAK,UAAU,GAAG,WAAW,GAAG,SAAS,CAAA;AAGzC,MAAM,MAAM,0BAA0B,GAAG;IACxC,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,OAAO,gCAAgC,GAAG,OAAO,8BAA8B,CAAA;CACrF,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,0BAA0B,CAAA;AAmDjG,wBAAsB,0BAA0B,CAAC,EAChD,WAAW,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,IAAI,GACJ,EAAE;IACF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,UAAU,CAAA;CAChB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAsBrC;AAED,wBAAsB,sBAAsB,CAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,yBAAyB,CAAC,CAQpC;AAED,wBAAsB,oBAAoB,CACzC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,yBAAyB,CAAC,CAQpC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,CAKxF;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export const GL_MAILER_TURNSTILE_UNCONFIGURED = 'GL_MAILER_TURNSTILE_UNCONFIGURED';
|
|
2
|
+
export const GL_MAILER_SIGNING_UNCONFIGURED = 'GL_MAILER_SIGNING_UNCONFIGURED';
|
|
3
|
+
const TURNSTILE_TEST_SECRETS = new Set([
|
|
4
|
+
'1x0000000000000000000000000000000AA',
|
|
5
|
+
'2x0000000000000000000000000000000AA',
|
|
6
|
+
'3x0000000000000000000000000000000AA',
|
|
7
|
+
]);
|
|
8
|
+
const secretStoreCache = new WeakMap();
|
|
9
|
+
function isSecretStoreBinding(value) {
|
|
10
|
+
return (typeof value === 'object' &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
'get' in value &&
|
|
13
|
+
typeof value.get === 'function');
|
|
14
|
+
}
|
|
15
|
+
async function readBinding(value) {
|
|
16
|
+
if (typeof value === 'string')
|
|
17
|
+
return value.trim() || undefined;
|
|
18
|
+
if (!isSecretStoreBinding(value))
|
|
19
|
+
return undefined;
|
|
20
|
+
let pending = secretStoreCache.get(value);
|
|
21
|
+
if (!pending) {
|
|
22
|
+
pending = Promise.resolve()
|
|
23
|
+
.then(() => value.get())
|
|
24
|
+
.then((secret) => {
|
|
25
|
+
const resolved = typeof secret === 'string' ? secret.trim() || undefined : undefined;
|
|
26
|
+
if (!resolved)
|
|
27
|
+
secretStoreCache.delete(value);
|
|
28
|
+
return resolved;
|
|
29
|
+
})
|
|
30
|
+
.catch(() => {
|
|
31
|
+
secretStoreCache.delete(value);
|
|
32
|
+
return undefined;
|
|
33
|
+
});
|
|
34
|
+
secretStoreCache.set(value, pending);
|
|
35
|
+
}
|
|
36
|
+
return pending;
|
|
37
|
+
}
|
|
38
|
+
function hostFromUrl(url) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = new URL(url);
|
|
41
|
+
return {
|
|
42
|
+
host: parsed.host.toLowerCase(),
|
|
43
|
+
isLocal: parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { host: 'unknown', isLocal: false };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function failureCode(kind) {
|
|
51
|
+
return kind === 'turnstile' ? GL_MAILER_TURNSTILE_UNCONFIGURED : GL_MAILER_SIGNING_UNCONFIGURED;
|
|
52
|
+
}
|
|
53
|
+
export async function resolveMailerRuntimeSecret({ bindingsEnv, bindingName, legacyValue, requestUrl, kind, }) {
|
|
54
|
+
const requestHost = hostFromUrl(requestUrl);
|
|
55
|
+
let secret = await readBinding(bindingsEnv[bindingName]);
|
|
56
|
+
// Build-time literals are a localhost-only development escape hatch. A
|
|
57
|
+
// production request must always resolve its secret from the Worker env.
|
|
58
|
+
if (!secret && requestHost.isLocal)
|
|
59
|
+
secret = legacyValue?.trim() || undefined;
|
|
60
|
+
if (kind === 'turnstile' &&
|
|
61
|
+
secret &&
|
|
62
|
+
TURNSTILE_TEST_SECRETS.has(secret) &&
|
|
63
|
+
!requestHost.isLocal) {
|
|
64
|
+
secret = undefined;
|
|
65
|
+
}
|
|
66
|
+
if (secret)
|
|
67
|
+
return { ok: true, secret };
|
|
68
|
+
const code = failureCode(kind);
|
|
69
|
+
console.error(`[${code}] @growth-labs/mailer: host=${requestHost.host} binding=${bindingName}`);
|
|
70
|
+
return { ok: false, code };
|
|
71
|
+
}
|
|
72
|
+
export async function resolveTurnstileSecret(bindingsEnv, options, requestUrl) {
|
|
73
|
+
return resolveMailerRuntimeSecret({
|
|
74
|
+
bindingsEnv,
|
|
75
|
+
bindingName: options.turnstileSecretBinding,
|
|
76
|
+
legacyValue: options.turnstileSecretKey,
|
|
77
|
+
requestUrl,
|
|
78
|
+
kind: 'turnstile',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function resolveSigningSecret(bindingsEnv, options, requestUrl) {
|
|
82
|
+
return resolveMailerRuntimeSecret({
|
|
83
|
+
bindingsEnv,
|
|
84
|
+
bindingName: options.signingSecretBinding,
|
|
85
|
+
legacyValue: options.signingSecret,
|
|
86
|
+
requestUrl,
|
|
87
|
+
kind: 'signing',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function runtimeSecretErrorResponse(failure) {
|
|
91
|
+
return Response.json({ error: failure.code }, { status: 503, headers: { 'Cache-Control': 'no-store' } });
|
|
92
|
+
}
|
|
93
|
+
export function siteHost(siteUrl) {
|
|
94
|
+
return new URL(siteUrl).host.toLowerCase();
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=runtime-secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-secrets.js","sourceRoot":"","sources":["../../src/utils/runtime-secrets.ts"],"names":[],"mappings":"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"}
|
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;
|
|
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
|
@@ -2,11 +2,18 @@ import { eq } from 'drizzle-orm';
|
|
|
2
2
|
import { drizzle } from 'drizzle-orm/d1';
|
|
3
3
|
import { ulid } from 'ulidx';
|
|
4
4
|
import { emailSends } from '../schema/sends.js';
|
|
5
|
+
import { resolveSigningSecret, siteHost } from './runtime-secrets.js';
|
|
5
6
|
import { getSubscriberBatch } from './subscribers.js';
|
|
6
7
|
import { renderDigestItems, renderEmail } from './templates.js';
|
|
7
8
|
import { generateToken } from './tokens.js';
|
|
8
9
|
import { injectTrackingPixel, rewriteLinksForTracking } from './tracking.js';
|
|
9
10
|
import { buildSubscriberManageUrls } from './urls.js';
|
|
11
|
+
async function requireSigningSecret(env, options) {
|
|
12
|
+
const result = await resolveSigningSecret(env, options, options.siteUrl);
|
|
13
|
+
if (!result.ok)
|
|
14
|
+
throw new Error(result.code);
|
|
15
|
+
return result.secret;
|
|
16
|
+
}
|
|
10
17
|
function resolveProducerBindings(env, options) {
|
|
11
18
|
const d1 = env[options.d1Binding];
|
|
12
19
|
if (!d1) {
|
|
@@ -28,13 +35,15 @@ function resolveProducerBindings(env, options) {
|
|
|
28
35
|
}
|
|
29
36
|
// ─── sendTransactional ───
|
|
30
37
|
export async function sendTransactional(env, options, params) {
|
|
38
|
+
const signingSecret = params.subscriberId ? await requireSigningSecret(env, options) : undefined;
|
|
31
39
|
const { sendsDb, queue } = resolveProducerBindings(env, options);
|
|
32
40
|
const trackingId = ulid();
|
|
41
|
+
const deliveryToken = ulid();
|
|
33
42
|
const subscriberId = params.subscriberId ?? ulid();
|
|
34
43
|
// Render HTML
|
|
35
44
|
let html;
|
|
36
45
|
if (params.template) {
|
|
37
|
-
const manageUrls = await buildSubscriberManageUrls(options, params.subscriberId);
|
|
46
|
+
const manageUrls = await buildSubscriberManageUrls(options, params.subscriberId, signingSecret);
|
|
38
47
|
html = renderEmail(params.template, {
|
|
39
48
|
...params.data,
|
|
40
49
|
senderName: options.senderName,
|
|
@@ -60,6 +69,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
60
69
|
type: 'transactional',
|
|
61
70
|
status: 'queued',
|
|
62
71
|
trackingId,
|
|
72
|
+
deliveryToken,
|
|
63
73
|
createdAt: new Date().toISOString(),
|
|
64
74
|
});
|
|
65
75
|
// Build and enqueue the message
|
|
@@ -73,6 +83,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
73
83
|
trackingId,
|
|
74
84
|
unsubscribeToken: '',
|
|
75
85
|
preferencesToken: '',
|
|
86
|
+
deliveryToken,
|
|
76
87
|
},
|
|
77
88
|
],
|
|
78
89
|
subject: params.subject,
|
|
@@ -84,7 +95,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
84
95
|
return { trackingId };
|
|
85
96
|
}
|
|
86
97
|
// ─── Fan-out helper (shared by campaign + digest) ───
|
|
87
|
-
async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
98
|
+
async function fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, params) {
|
|
88
99
|
let cursor = null;
|
|
89
100
|
let recipientCount = 0;
|
|
90
101
|
const filter = params.filter ?? { status: ['active'] };
|
|
@@ -97,13 +108,16 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
|
97
108
|
const recipients = [];
|
|
98
109
|
for (const sub of batch) {
|
|
99
110
|
const trackingId = ulid();
|
|
100
|
-
const
|
|
111
|
+
const deliveryToken = ulid();
|
|
112
|
+
const unsubscribeToken = await generateToken(signingSecret, {
|
|
101
113
|
subscriberId: sub.id,
|
|
102
114
|
action: 'unsubscribe',
|
|
115
|
+
host: siteHost(options.siteUrl),
|
|
103
116
|
});
|
|
104
|
-
const preferencesToken = await generateToken(
|
|
117
|
+
const preferencesToken = await generateToken(signingSecret, {
|
|
105
118
|
subscriberId: sub.id,
|
|
106
119
|
action: 'preferences',
|
|
120
|
+
host: siteHost(options.siteUrl),
|
|
107
121
|
});
|
|
108
122
|
await sendsDb.insert(emailSends).values({
|
|
109
123
|
id: ulid(),
|
|
@@ -115,6 +129,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
|
115
129
|
type: params.type,
|
|
116
130
|
status: 'queued',
|
|
117
131
|
trackingId,
|
|
132
|
+
deliveryToken,
|
|
118
133
|
createdAt: new Date().toISOString(),
|
|
119
134
|
});
|
|
120
135
|
recipients.push({
|
|
@@ -123,6 +138,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
|
123
138
|
trackingId,
|
|
124
139
|
unsubscribeToken,
|
|
125
140
|
preferencesToken,
|
|
141
|
+
deliveryToken,
|
|
126
142
|
});
|
|
127
143
|
}
|
|
128
144
|
const message = {
|
|
@@ -144,6 +160,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
|
144
160
|
// ─── sendCampaign ───
|
|
145
161
|
export async function sendCampaign(env, options, params) {
|
|
146
162
|
const campaignId = params.campaignId ?? ulid();
|
|
163
|
+
const signingSecret = await requireSigningSecret(env, options);
|
|
147
164
|
const { db, sendsDb, queue } = resolveProducerBindings(env, options);
|
|
148
165
|
// Render HTML
|
|
149
166
|
let html;
|
|
@@ -168,7 +185,7 @@ export async function sendCampaign(env, options, params) {
|
|
|
168
185
|
const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
|
|
169
186
|
html = injectTrackingPixel(html, trackOpenUrl);
|
|
170
187
|
html = rewriteLinksForTracking(html, trackClickUrl);
|
|
171
|
-
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, {
|
|
188
|
+
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, {
|
|
172
189
|
subject: params.subject,
|
|
173
190
|
html,
|
|
174
191
|
campaignId,
|
|
@@ -180,6 +197,7 @@ export async function sendCampaign(env, options, params) {
|
|
|
180
197
|
// ─── sendDigest ───
|
|
181
198
|
export async function sendDigest(env, options, params) {
|
|
182
199
|
const campaignId = params.campaignId ?? ulid();
|
|
200
|
+
const signingSecret = await requireSigningSecret(env, options);
|
|
183
201
|
const { db, sendsDb, queue } = resolveProducerBindings(env, options);
|
|
184
202
|
// Render digest items then full template
|
|
185
203
|
const itemsHtml = renderDigestItems(params.items);
|
|
@@ -197,7 +215,7 @@ export async function sendDigest(env, options, params) {
|
|
|
197
215
|
const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
|
|
198
216
|
html = injectTrackingPixel(html, trackOpenUrl);
|
|
199
217
|
html = rewriteLinksForTracking(html, trackClickUrl);
|
|
200
|
-
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, {
|
|
218
|
+
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, {
|
|
201
219
|
subject: params.subject,
|
|
202
220
|
html,
|
|
203
221
|
campaignId,
|
package/dist/utils/send.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAS/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAC5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AAsBrD,SAAS,uBAAuB,CAC/B,GAAc,EACd,OAA8B;IAE9B,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,CAA2B,CAAA;IAC3D,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACd,gBAAgB,OAAO,CAAC,SAAS,iBAAiB;YACjD,qFAAqF,CACtF,CAAA;IACF,CAAC;IACD,MAAM,gBAAgB,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,SAAS,CAAA;IACpE,MAAM,OAAO,GAAG,GAAG,CAAC,gBAAgB,CAA2B,CAAA;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACd,gBAAgB,gBAAgB,iBAAiB;YAChD,0FAA0F,CAC3F,CAAA;IACF,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,CAAsB,CAAA;IAC5D,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACd,gBAAgB,OAAO,CAAC,YAAY,iBAAiB;YACpD,wFAAwF,CACzF,CAAA;IACF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAA;AAC7D,CAAC;AAED,4BAA4B;AAE5B,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,GAAc,EACd,OAA8B,EAC9B,MAOC;IAED,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,uBAAuB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChE,MAAM,UAAU,GAAG,IAAI,EAAE,CAAA;IACzB,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,IAAI,EAAE,CAAA;IAElD,cAAc;IACd,IAAI,IAAY,CAAA;IAChB,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,UAAU,GAAG,MAAM,yBAAyB,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/tokens.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
interface TokenPayload {
|
|
2
2
|
subscriberId: string;
|
|
3
3
|
action: 'confirm' | 'unsubscribe' | 'preferences';
|
|
4
|
+
host: string;
|
|
4
5
|
exp?: number;
|
|
5
6
|
}
|
|
6
7
|
export declare function generateToken(secret: string, payload: TokenPayload): Promise<string>;
|
|
7
|
-
export declare function verifyToken(secret: string, token: string): Promise<{
|
|
8
|
+
export declare function verifyToken(secret: string, token: string, expectedHost: string): Promise<{
|
|
8
9
|
subscriberId: string;
|
|
9
10
|
action: string;
|
|
11
|
+
host: string;
|
|
10
12
|
exp?: number;
|
|
11
13
|
} | null>;
|
|
12
14
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"AA0CA,UAAU,YAAY;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,aAAa,CAAA;IACjD,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ;AAID,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"AA0CA,UAAU,YAAY;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,aAAa,CAAA;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ;AAID,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAc1F;AAED,wBAAsB,WAAW,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA8BtF"}
|
package/dist/utils/tokens.js
CHANGED
|
@@ -30,6 +30,9 @@ async function hmacVerify(secret, data, signature) {
|
|
|
30
30
|
}
|
|
31
31
|
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
32
32
|
export async function generateToken(secret, payload) {
|
|
33
|
+
if (typeof payload.host !== 'string' || !payload.host.trim()) {
|
|
34
|
+
throw new Error('GL_MAILER_TOKEN_HOST_REQUIRED');
|
|
35
|
+
}
|
|
33
36
|
const finalPayload = { ...payload };
|
|
34
37
|
if (finalPayload.action === 'confirm' && finalPayload.exp === undefined) {
|
|
35
38
|
finalPayload.exp = Date.now() + SEVEN_DAYS_MS;
|
|
@@ -38,7 +41,9 @@ export async function generateToken(secret, payload) {
|
|
|
38
41
|
const signature = await hmacSign(secret, payloadB64);
|
|
39
42
|
return `${payloadB64}.${signature}`;
|
|
40
43
|
}
|
|
41
|
-
export async function verifyToken(secret, token) {
|
|
44
|
+
export async function verifyToken(secret, token, expectedHost) {
|
|
45
|
+
if (typeof expectedHost !== 'string' || !expectedHost.trim())
|
|
46
|
+
return null;
|
|
42
47
|
const dotIndex = token.indexOf('.');
|
|
43
48
|
if (dotIndex === -1)
|
|
44
49
|
return null;
|
|
@@ -46,14 +51,18 @@ export async function verifyToken(secret, token) {
|
|
|
46
51
|
const signature = token.slice(dotIndex + 1);
|
|
47
52
|
if (!payloadB64 || !signature)
|
|
48
53
|
return null;
|
|
49
|
-
const valid = await hmacVerify(secret, payloadB64, signature);
|
|
50
|
-
if (!valid)
|
|
51
|
-
return null;
|
|
52
54
|
try {
|
|
55
|
+
const valid = await hmacVerify(secret, payloadB64, signature);
|
|
56
|
+
if (!valid)
|
|
57
|
+
return null;
|
|
53
58
|
const decoded = JSON.parse(new TextDecoder().decode(fromBase64Url(payloadB64)));
|
|
54
59
|
if (decoded.exp !== undefined && decoded.exp < Date.now())
|
|
55
60
|
return null;
|
|
56
|
-
|
|
61
|
+
if (typeof decoded.host !== 'string' || !decoded.host.trim())
|
|
62
|
+
return null;
|
|
63
|
+
if (decoded.host !== expectedHost)
|
|
64
|
+
return null;
|
|
65
|
+
return { ...decoded, host: decoded.host };
|
|
57
66
|
}
|
|
58
67
|
catch {
|
|
59
68
|
return null;
|