@growth-labs/mailer 0.4.10 → 0.5.0
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 +19 -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/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/utils/broadcast.d.ts.map +1 -1
- package/dist/utils/broadcast.js +10 -2
- package/dist/utils/broadcast.js.map +1 -1
- package/dist/utils/providers.d.ts +4 -5
- package/dist/utils/providers.d.ts.map +1 -1
- package/dist/utils/providers.js +10 -3
- package/dist/utils/providers.js.map +1 -1
- 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 +18 -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/package.json +4 -4
- package/src/options.ts +4 -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/utils/broadcast.ts +9 -2
- package/src/utils/providers.ts +15 -5
- package/src/utils/runtime-secrets.ts +144 -0
- package/src/utils/send.ts +21 -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
|
@@ -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":"AAEA,MAAM,CAAC,MAAM,gCAAgC,GAAG,kCAAkC,CAAA;AAClF,MAAM,CAAC,MAAM,8BAA8B,GAAG,gCAAgC,CAAA;AAE9E,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACtC,qCAAqC;IACrC,qCAAqC;IACrC,qCAAqC;CACrC,CAAC,CAAA;AAYF,MAAM,gBAAgB,GAAG,IAAI,OAAO,EAAuC,CAAA;AAE3E,SAAS,oBAAoB,CAAC,KAAc;IAC3C,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,IAAI,KAAK;QACd,OAAQ,KAA4B,CAAC,GAAG,KAAK,UAAU,CACvD,CAAA;AACF,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,IAAI,SAAS,CAAA;IAC/D,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAElD,IAAI,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACzC,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE;aACzB,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;aACvB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YAChB,MAAM,QAAQ,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;YACpF,IAAI,CAAC,QAAQ;gBAAE,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC7C,OAAO,QAAQ,CAAA;QAChB,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACX,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC9B,OAAO,SAAS,CAAA;QACjB,CAAC,CAAC,CAAA;QACH,gBAAgB,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,OAAO,CAAA;AACf,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC/B,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,OAAO;YACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;YAC/B,OAAO,EAAE,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW;SAC3E,CAAA;IACF,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAC3C,CAAC;AACF,CAAC;AAED,SAAS,WAAW,CAAC,IAAgB;IACpC,OAAO,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,8BAA8B,CAAA;AAChG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,EAChD,WAAW,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,IAAI,GAOJ;IACA,MAAM,WAAW,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IAC3C,IAAI,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAA;IAExD,uEAAuE;IACvE,yEAAyE;IACzE,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,OAAO;QAAE,MAAM,GAAG,WAAW,EAAE,IAAI,EAAE,IAAI,SAAS,CAAA;IAE7E,IACC,IAAI,KAAK,WAAW;QACpB,MAAM;QACN,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC;QAClC,CAAC,WAAW,CAAC,OAAO,EACnB,CAAC;QACF,MAAM,GAAG,SAAS,CAAA;IACnB,CAAC;IAED,IAAI,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IAEvC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC9B,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,+BAA+B,WAAW,CAAC,IAAI,YAAY,WAAW,EAAE,CAAC,CAAA;IAC/F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,WAAoC,EACpC,OAA8B,EAC9B,UAAkB;IAElB,OAAO,0BAA0B,CAAC;QACjC,WAAW;QACX,WAAW,EAAE,OAAO,CAAC,sBAAsB;QAC3C,WAAW,EAAE,OAAO,CAAC,kBAAkB;QACvC,UAAU;QACV,IAAI,EAAE,WAAW;KACjB,CAAC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,WAAoC,EACpC,OAA8B,EAC9B,UAAkB;IAElB,OAAO,0BAA0B,CAAC;QACjC,WAAW;QACX,WAAW,EAAE,OAAO,CAAC,oBAAoB;QACzC,WAAW,EAAE,OAAO,CAAC,aAAa;QAClC,UAAU;QACV,IAAI,EAAE,SAAS;KACf,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAmC;IAC7E,OAAO,QAAQ,CAAC,IAAI,CACnB,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,EACvB,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,EAAE,CACzD,CAAA;AACF,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACvC,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA;AAC3C,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;
|
|
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,CA2DjC;AAoFD,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,14 @@ 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();
|
|
33
41
|
const subscriberId = params.subscriberId ?? ulid();
|
|
34
42
|
// Render HTML
|
|
35
43
|
let html;
|
|
36
44
|
if (params.template) {
|
|
37
|
-
const manageUrls = await buildSubscriberManageUrls(options, params.subscriberId);
|
|
45
|
+
const manageUrls = await buildSubscriberManageUrls(options, params.subscriberId, signingSecret);
|
|
38
46
|
html = renderEmail(params.template, {
|
|
39
47
|
...params.data,
|
|
40
48
|
senderName: options.senderName,
|
|
@@ -84,7 +92,7 @@ export async function sendTransactional(env, options, params) {
|
|
|
84
92
|
return { trackingId };
|
|
85
93
|
}
|
|
86
94
|
// ─── Fan-out helper (shared by campaign + digest) ───
|
|
87
|
-
async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
95
|
+
async function fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, params) {
|
|
88
96
|
let cursor = null;
|
|
89
97
|
let recipientCount = 0;
|
|
90
98
|
const filter = params.filter ?? { status: ['active'] };
|
|
@@ -97,13 +105,15 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
|
97
105
|
const recipients = [];
|
|
98
106
|
for (const sub of batch) {
|
|
99
107
|
const trackingId = ulid();
|
|
100
|
-
const unsubscribeToken = await generateToken(
|
|
108
|
+
const unsubscribeToken = await generateToken(signingSecret, {
|
|
101
109
|
subscriberId: sub.id,
|
|
102
110
|
action: 'unsubscribe',
|
|
111
|
+
host: siteHost(options.siteUrl),
|
|
103
112
|
});
|
|
104
|
-
const preferencesToken = await generateToken(
|
|
113
|
+
const preferencesToken = await generateToken(signingSecret, {
|
|
105
114
|
subscriberId: sub.id,
|
|
106
115
|
action: 'preferences',
|
|
116
|
+
host: siteHost(options.siteUrl),
|
|
107
117
|
});
|
|
108
118
|
await sendsDb.insert(emailSends).values({
|
|
109
119
|
id: ulid(),
|
|
@@ -144,6 +154,7 @@ async function fanOutToSubscribers(queue, db, sendsDb, options, params) {
|
|
|
144
154
|
// ─── sendCampaign ───
|
|
145
155
|
export async function sendCampaign(env, options, params) {
|
|
146
156
|
const campaignId = params.campaignId ?? ulid();
|
|
157
|
+
const signingSecret = await requireSigningSecret(env, options);
|
|
147
158
|
const { db, sendsDb, queue } = resolveProducerBindings(env, options);
|
|
148
159
|
// Render HTML
|
|
149
160
|
let html;
|
|
@@ -168,7 +179,7 @@ export async function sendCampaign(env, options, params) {
|
|
|
168
179
|
const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
|
|
169
180
|
html = injectTrackingPixel(html, trackOpenUrl);
|
|
170
181
|
html = rewriteLinksForTracking(html, trackClickUrl);
|
|
171
|
-
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, {
|
|
182
|
+
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, {
|
|
172
183
|
subject: params.subject,
|
|
173
184
|
html,
|
|
174
185
|
campaignId,
|
|
@@ -180,6 +191,7 @@ export async function sendCampaign(env, options, params) {
|
|
|
180
191
|
// ─── sendDigest ───
|
|
181
192
|
export async function sendDigest(env, options, params) {
|
|
182
193
|
const campaignId = params.campaignId ?? ulid();
|
|
194
|
+
const signingSecret = await requireSigningSecret(env, options);
|
|
183
195
|
const { db, sendsDb, queue } = resolveProducerBindings(env, options);
|
|
184
196
|
// Render digest items then full template
|
|
185
197
|
const itemsHtml = renderDigestItems(params.items);
|
|
@@ -197,7 +209,7 @@ export async function sendDigest(env, options, params) {
|
|
|
197
209
|
const trackClickUrl = `${options.siteUrl}${options.trackClickPath}`;
|
|
198
210
|
html = injectTrackingPixel(html, trackOpenUrl);
|
|
199
211
|
html = rewriteLinksForTracking(html, trackClickUrl);
|
|
200
|
-
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, {
|
|
212
|
+
const recipientCount = await fanOutToSubscribers(queue, db, sendsDb, options, signingSecret, {
|
|
201
213
|
subject: params.subject,
|
|
202
214
|
html,
|
|
203
215
|
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;
|
package/dist/utils/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;AAEjC,SAAS,WAAW,CAAC,MAAgC;IACpD,MAAM,KAAK,GAAG,MAAM,YAAY,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAA;IAC5E,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC;SACxC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AACrB,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACjG,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,KAAK,CAAA;AACb,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAC7B,KAAK,EACL,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EACtB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,EAAE,QAAQ,CAAC,CAClB,CAAA;AACF,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,MAAc,EAAE,IAAY;IACnD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IAC7E,OAAO,WAAW,CAAC,SAAS,CAAC,CAAA;AAC9B,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,IAAY,EAAE,SAAiB;IACxE,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,CAAA;IACzC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAqB,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/F,CAAC;
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/utils/tokens.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/urls.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResolvedMailerOptions } from '../options.js';
|
|
2
2
|
export declare function buildSiteUrl(siteUrl: string, path: string, searchParams?: Record<string, string | boolean | undefined>): string;
|
|
3
|
-
export declare function buildSubscriberManageUrls(options: Pick<ResolvedMailerOptions, 'preferencesPath' | 'siteUrl' | '
|
|
3
|
+
export declare function buildSubscriberManageUrls(options: Pick<ResolvedMailerOptions, 'preferencesPath' | 'siteUrl' | 'unsubscribePath'>, subscriberId?: string, signingSecret?: string): Promise<{
|
|
4
4
|
preferencesUrl: string;
|
|
5
5
|
unsubscribeUrl: string;
|
|
6
6
|
}>;
|
package/dist/utils/urls.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/utils/urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/utils/urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAI1D,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GACzD,MAAM,CASR;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,iBAAiB,GAAG,SAAS,GAAG,iBAAiB,CAAC,EACvF,YAAY,CAAC,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CA4B7D"}
|
package/dist/utils/urls.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { siteHost } from './runtime-secrets.js';
|
|
1
2
|
import { generateToken } from './tokens.js';
|
|
2
3
|
export function buildSiteUrl(siteUrl, path, searchParams) {
|
|
3
4
|
const url = new URL(path, siteUrl);
|
|
@@ -8,18 +9,23 @@ export function buildSiteUrl(siteUrl, path, searchParams) {
|
|
|
8
9
|
}
|
|
9
10
|
return url.toString();
|
|
10
11
|
}
|
|
11
|
-
export async function buildSubscriberManageUrls(options, subscriberId) {
|
|
12
|
+
export async function buildSubscriberManageUrls(options, subscriberId, signingSecret) {
|
|
12
13
|
if (!subscriberId) {
|
|
13
14
|
return { preferencesUrl: '', unsubscribeUrl: '' };
|
|
14
15
|
}
|
|
16
|
+
if (!signingSecret)
|
|
17
|
+
throw new Error('GL_MAILER_SIGNING_UNCONFIGURED');
|
|
18
|
+
const host = siteHost(options.siteUrl);
|
|
15
19
|
const [unsubscribeToken, preferencesToken] = await Promise.all([
|
|
16
|
-
generateToken(
|
|
20
|
+
generateToken(signingSecret, {
|
|
17
21
|
subscriberId,
|
|
18
22
|
action: 'unsubscribe',
|
|
23
|
+
host,
|
|
19
24
|
}),
|
|
20
|
-
generateToken(
|
|
25
|
+
generateToken(signingSecret, {
|
|
21
26
|
subscriberId,
|
|
22
27
|
action: 'preferences',
|
|
28
|
+
host,
|
|
23
29
|
}),
|
|
24
30
|
]);
|
|
25
31
|
return {
|
package/dist/utils/urls.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urls.js","sourceRoot":"","sources":["../../src/utils/urls.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,UAAU,YAAY,CAC3B,OAAe,EACf,IAAY,EACZ,YAA2D;IAE3D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAElC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/D,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAC9E,CAAC;IAED,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAA;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,
|
|
1
|
+
{"version":3,"file":"urls.js","sourceRoot":"","sources":["../../src/utils/urls.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,UAAU,YAAY,CAC3B,OAAe,EACf,IAAY,EACZ,YAA2D;IAE3D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAElC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/D,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAC9E,CAAC;IAED,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAA;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,OAAuF,EACvF,YAAqB,EACrB,aAAsB;IAEtB,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,CAAA;IAClD,CAAC;IAED,IAAI,CAAC,aAAa;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC9D,aAAa,CAAC,aAAa,EAAE;YAC5B,YAAY;YACZ,MAAM,EAAE,aAAa;YACrB,IAAI;SACJ,CAAC;QACF,aAAa,CAAC,aAAa,EAAE;YAC5B,YAAY;YACZ,MAAM,EAAE,aAAa;YACrB,IAAI;SACJ,CAAC;KACF,CAAC,CAAA;IAEF,OAAO;QACN,cAAc,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE;YACtE,KAAK,EAAE,gBAAgB;SACvB,CAAC;QACF,cAAc,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE;YACtE,KAAK,EAAE,gBAAgB;SACvB,CAAC;KACF,CAAA;AACF,CAAC"}
|
package/dist/vite-plugin.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
import type { ResolvedMailerOptions } from './options.js';
|
|
3
|
+
export declare function renderMailerConfigModule(config: ResolvedMailerOptions, command: 'build' | 'serve'): string;
|
|
3
4
|
export declare function growthLabsMailerPlugin(config: ResolvedMailerOptions): Plugin;
|
|
4
5
|
//# sourceMappingURL=vite-plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAKzD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAKzD,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,OAAO,GAAG,OAAO,GACxB,MAAM,CAOR;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAmB5E"}
|
package/dist/vite-plugin.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
const VIRTUAL_MODULE_ID = 'virtual:growth-labs/mailer/config';
|
|
2
2
|
const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
3
|
+
export function renderMailerConfigModule(config, command) {
|
|
4
|
+
const serializableConfig = { ...config };
|
|
5
|
+
if (command !== 'serve') {
|
|
6
|
+
delete serializableConfig.turnstileSecretKey;
|
|
7
|
+
delete serializableConfig.signingSecret;
|
|
8
|
+
}
|
|
9
|
+
return `export const config = ${JSON.stringify(serializableConfig)};`;
|
|
10
|
+
}
|
|
3
11
|
export function growthLabsMailerPlugin(config) {
|
|
12
|
+
let command = 'build';
|
|
4
13
|
return {
|
|
5
14
|
name: 'growth-labs-mailer-config',
|
|
15
|
+
configResolved(resolvedConfig) {
|
|
16
|
+
command = resolvedConfig.command;
|
|
17
|
+
},
|
|
6
18
|
resolveId(id) {
|
|
7
19
|
if (id === VIRTUAL_MODULE_ID) {
|
|
8
20
|
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
@@ -10,7 +22,7 @@ export function growthLabsMailerPlugin(config) {
|
|
|
10
22
|
},
|
|
11
23
|
load(id) {
|
|
12
24
|
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
13
|
-
return
|
|
25
|
+
return renderMailerConfigModule(config, command);
|
|
14
26
|
}
|
|
15
27
|
},
|
|
16
28
|
};
|
package/dist/vite-plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAGA,MAAM,iBAAiB,GAAG,mCAAmC,CAAA;AAC7D,MAAM,0BAA0B,GAAG,KAAK,iBAAiB,EAAE,CAAA;AAE3D,MAAM,UAAU,sBAAsB,CAAC,MAA6B;IACnE,OAAO;QACN,IAAI,EAAE,2BAA2B;QACjC,SAAS,CAAC,EAAE;YACX,IAAI,EAAE,KAAK,iBAAiB,EAAE,CAAC;gBAC9B,OAAO,0BAA0B,CAAA;YAClC,CAAC;QACF,CAAC;QACD,IAAI,CAAC,EAAE;YACN,IAAI,EAAE,KAAK,0BAA0B,EAAE,CAAC;gBACvC,OAAO,
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAGA,MAAM,iBAAiB,GAAG,mCAAmC,CAAA;AAC7D,MAAM,0BAA0B,GAAG,KAAK,iBAAiB,EAAE,CAAA;AAE3D,MAAM,UAAU,wBAAwB,CACvC,MAA6B,EAC7B,OAA0B;IAE1B,MAAM,kBAAkB,GAAG,EAAE,GAAG,MAAM,EAAE,CAAA;IACxC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,kBAAkB,CAAC,kBAAkB,CAAA;QAC5C,OAAO,kBAAkB,CAAC,aAAa,CAAA;IACxC,CAAC;IACD,OAAO,yBAAyB,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG,CAAA;AACtE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAA6B;IACnE,IAAI,OAAO,GAAsB,OAAO,CAAA;IAExC,OAAO;QACN,IAAI,EAAE,2BAA2B;QACjC,cAAc,CAAC,cAAc;YAC5B,OAAO,GAAG,cAAc,CAAC,OAAO,CAAA;QACjC,CAAC;QACD,SAAS,CAAC,EAAE;YACX,IAAI,EAAE,KAAK,iBAAiB,EAAE,CAAC;gBAC9B,OAAO,0BAA0B,CAAA;YAClC,CAAC;QACF,CAAC;QACD,IAAI,CAAC,EAAE;YACN,IAAI,EAAE,KAAK,0BAA0B,EAAE,CAAC;gBACvC,OAAO,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YACjD,CAAC;QACF,CAAC;KACD,CAAA;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growth-labs/mailer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"registry": "https://registry.npmjs.org/"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"astro": "^6.1.10"
|
|
74
|
+
"astro": "^6.1.10 || ^7.0.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependenciesMeta": {
|
|
77
77
|
"@growth-labs/analytics": {
|
|
@@ -85,10 +85,10 @@
|
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@cloudflare/workers-types": "^4.20260402.1",
|
|
88
|
-
"astro": "^6.
|
|
88
|
+
"astro": "^6.4.6",
|
|
89
89
|
"drizzle-kit": "^0.30.0",
|
|
90
90
|
"typescript": "^5.7.0",
|
|
91
|
-
"vite": "^7.3.
|
|
91
|
+
"vite": "^7.3.5",
|
|
92
92
|
"vitest": "^4.1.8"
|
|
93
93
|
},
|
|
94
94
|
"scripts": {
|
package/src/options.ts
CHANGED
|
@@ -25,14 +25,16 @@ export const mailerOptionsSchema = z.object({
|
|
|
25
25
|
|
|
26
26
|
// ─── Turnstile (subscribe form) ───
|
|
27
27
|
turnstileSiteKey: z.string(),
|
|
28
|
-
|
|
28
|
+
turnstileSecretBinding: z.string().min(1).default('TURNSTILE_SECRET_KEY'),
|
|
29
|
+
turnstileSecretKey: z.string().optional(),
|
|
29
30
|
|
|
30
31
|
// ─── Subscriber lifecycle ───
|
|
31
32
|
doubleOptIn: z.boolean().default(true),
|
|
32
33
|
topics: z.array(z.string()).optional(),
|
|
33
34
|
|
|
34
35
|
// ─── Token signing ───
|
|
35
|
-
|
|
36
|
+
signingSecretBinding: z.string().min(1).default('MAILER_SIGNING_SECRET'),
|
|
37
|
+
signingSecret: z.string().optional(),
|
|
36
38
|
|
|
37
39
|
// ─── Routes ───
|
|
38
40
|
subscribePath: z.string().default('/api/newsletter/subscribe'),
|
package/src/routes/confirm.ts
CHANGED
|
@@ -4,6 +4,11 @@ import type { APIRoute } from 'astro'
|
|
|
4
4
|
import { drizzle } from 'drizzle-orm/d1'
|
|
5
5
|
import { probeMailerSchema, schemaMissingResponse } from '../_internal/schema-probe.js'
|
|
6
6
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js'
|
|
7
|
+
import {
|
|
8
|
+
resolveSigningSecret,
|
|
9
|
+
runtimeSecretErrorResponse,
|
|
10
|
+
siteHost,
|
|
11
|
+
} from '../utils/runtime-secrets.js'
|
|
7
12
|
import { sendTransactional } from '../utils/send.js'
|
|
8
13
|
import { confirmSubscriber, getSubscriberById } from '../utils/subscribers.js'
|
|
9
14
|
import { verifyToken } from '../utils/tokens.js'
|
|
@@ -14,15 +19,17 @@ export const GET: APIRoute = async (context) => {
|
|
|
14
19
|
if (!token) {
|
|
15
20
|
return Response.json({ error: 'Missing token' }, { status: 400 })
|
|
16
21
|
}
|
|
22
|
+
const bindingsEnv = cloudflareEnv as Record<string, unknown>
|
|
23
|
+
const signingSecret = await resolveSigningSecret(bindingsEnv, config, url.toString())
|
|
24
|
+
if (!signingSecret.ok) return runtimeSecretErrorResponse(signingSecret)
|
|
17
25
|
|
|
18
26
|
// Verify token
|
|
19
|
-
const payload = await verifyToken(
|
|
27
|
+
const payload = await verifyToken(signingSecret.secret, token, siteHost(config.siteUrl))
|
|
20
28
|
if (!payload || payload.action !== 'confirm') {
|
|
21
29
|
return Response.json({ error: 'Invalid or expired token' }, { status: 400 })
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
// Resolve bindings
|
|
25
|
-
const bindingsEnv = cloudflareEnv as Record<string, unknown>
|
|
26
33
|
const d1 = bindingsEnv[config.d1Binding] as D1Database
|
|
27
34
|
|
|
28
35
|
// Schema probe — return 503 with GL_MAILER_SCHEMA_MISSING on miss.
|
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
unsubscribeSubscriber,
|
|
9
9
|
updatePreferences,
|
|
10
10
|
} from "../utils/subscribers.js";
|
|
11
|
+
import {
|
|
12
|
+
resolveSigningSecret,
|
|
13
|
+
runtimeSecretErrorResponse,
|
|
14
|
+
siteHost,
|
|
15
|
+
} from "../utils/runtime-secrets.js";
|
|
11
16
|
import { generateToken, verifyToken } from "../utils/tokens.js";
|
|
12
17
|
import { buildSiteUrl } from "../utils/urls.js";
|
|
13
18
|
|
|
@@ -27,15 +32,20 @@ if (!token) {
|
|
|
27
32
|
return new Response("Missing token", { status: 400 });
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
const
|
|
35
|
+
const bindingsEnv = cloudflareEnv as Record<string, unknown>;
|
|
36
|
+
const signingSecret = await resolveSigningSecret(bindingsEnv, config, Astro.request.url);
|
|
37
|
+
if (!signingSecret.ok) return runtimeSecretErrorResponse(signingSecret);
|
|
38
|
+
|
|
39
|
+
const payload = await verifyToken(signingSecret.secret, token, siteHost(config.siteUrl));
|
|
31
40
|
if (!payload || !["preferences", "unsubscribe"].includes(payload.action)) {
|
|
32
41
|
return new Response("Invalid or expired token", { status: 400 });
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
if (Astro.request.method === "GET" && payload.action === "unsubscribe") {
|
|
36
|
-
const preferencesToken = await generateToken(
|
|
45
|
+
const preferencesToken = await generateToken(signingSecret.secret, {
|
|
37
46
|
subscriberId: payload.subscriberId,
|
|
38
47
|
action: "preferences",
|
|
48
|
+
host: siteHost(config.siteUrl),
|
|
39
49
|
});
|
|
40
50
|
|
|
41
51
|
return Astro.redirect(
|
|
@@ -51,7 +61,6 @@ if (Astro.request.method === "POST" && payload.action !== "preferences") {
|
|
|
51
61
|
return new Response("Invalid or expired token", { status: 400 });
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
const bindingsEnv = cloudflareEnv as Record<string, unknown>;
|
|
55
64
|
const d1 = bindingsEnv[config.d1Binding] as D1Database;
|
|
56
65
|
if (!d1) {
|
|
57
66
|
return new Response("D1 binding not available", { status: 500 });
|
package/src/routes/subscribe.ts
CHANGED
|
@@ -4,6 +4,12 @@ import type { APIRoute } from 'astro'
|
|
|
4
4
|
import { drizzle } from 'drizzle-orm/d1'
|
|
5
5
|
import { probeMailerSchema, schemaMissingResponse } from '../_internal/schema-probe.js'
|
|
6
6
|
import { emitMailerAnalyticsEvent } from '../utils/analytics.js'
|
|
7
|
+
import {
|
|
8
|
+
resolveSigningSecret,
|
|
9
|
+
resolveTurnstileSecret,
|
|
10
|
+
runtimeSecretErrorResponse,
|
|
11
|
+
siteHost,
|
|
12
|
+
} from '../utils/runtime-secrets.js'
|
|
7
13
|
import { sendTransactional } from '../utils/send.js'
|
|
8
14
|
import { createSubscriber } from '../utils/subscribers.js'
|
|
9
15
|
import { generateToken } from '../utils/tokens.js'
|
|
@@ -29,6 +35,12 @@ export const POST: APIRoute = async (context) => {
|
|
|
29
35
|
return Response.json({ error: 'Invalid email format' }, { status: 400 })
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
const bindingsEnv = cloudflareEnv as Record<string, unknown>
|
|
39
|
+
const turnstileSecret = await resolveTurnstileSecret(bindingsEnv, config, request.url)
|
|
40
|
+
if (!turnstileSecret.ok) return runtimeSecretErrorResponse(turnstileSecret)
|
|
41
|
+
const signingSecret = await resolveSigningSecret(bindingsEnv, config, request.url)
|
|
42
|
+
if (!signingSecret.ok) return runtimeSecretErrorResponse(signingSecret)
|
|
43
|
+
|
|
32
44
|
// 4. Validate Turnstile token
|
|
33
45
|
const turnstileResponse = await fetch(
|
|
34
46
|
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
|
|
@@ -36,7 +48,7 @@ export const POST: APIRoute = async (context) => {
|
|
|
36
48
|
method: 'POST',
|
|
37
49
|
headers: { 'Content-Type': 'application/json' },
|
|
38
50
|
body: JSON.stringify({
|
|
39
|
-
secret:
|
|
51
|
+
secret: turnstileSecret.secret,
|
|
40
52
|
response: body.turnstileToken,
|
|
41
53
|
remoteip: request.headers.get('cf-connecting-ip') ?? undefined,
|
|
42
54
|
}),
|
|
@@ -50,7 +62,6 @@ export const POST: APIRoute = async (context) => {
|
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
// 5. Resolve bindings
|
|
53
|
-
const bindingsEnv = cloudflareEnv as Record<string, unknown>
|
|
54
65
|
const d1 = bindingsEnv[config.d1Binding] as D1Database
|
|
55
66
|
|
|
56
67
|
// 5a. Schema probe — runs once per Worker instance. On miss, return 503
|
|
@@ -72,9 +83,10 @@ export const POST: APIRoute = async (context) => {
|
|
|
72
83
|
|
|
73
84
|
// 7. Send confirmation or welcome email
|
|
74
85
|
if (config.doubleOptIn && (isNew || subscriber.status === 'pending')) {
|
|
75
|
-
const confirmToken = await generateToken(
|
|
86
|
+
const confirmToken = await generateToken(signingSecret.secret, {
|
|
76
87
|
subscriberId: subscriber.id,
|
|
77
88
|
action: 'confirm',
|
|
89
|
+
host: siteHost(config.siteUrl),
|
|
78
90
|
})
|
|
79
91
|
const confirmUrl = `${config.siteUrl}${config.confirmPath}?token=${confirmToken}`
|
|
80
92
|
|