@voltro/plugin-mail 0.86.0 → 0.88.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/CHANGELOG.md +5484 -0
- package/THIRD-PARTY-NOTICES.md +1512 -277
- package/dist/egress.js +1 -2
- package/dist/events.js +1 -2
- package/dist/idempotency.js +17 -5
- package/dist/index.d.ts +38 -0
- package/dist/index.js +42 -43
- package/dist/outbox.js +1 -2
- package/dist/providers.js +1 -2
- package/dist/registry.js +1 -2
- package/dist/render.js +1 -2
- package/dist/scheduler.js +1 -2
- package/dist/service.js +42 -37
- package/dist/skeleton.js +1 -2
- package/dist/storeTables.js +1 -2
- package/dist/suppression.js +1 -2
- package/dist/types.js +1 -2
- package/package.json +6 -6
package/dist/egress.js
CHANGED
package/dist/events.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect as e } from "effect";
|
|
2
|
-
|
|
2
|
+
// src/events.ts
|
|
3
3
|
var t = (e, t, n, r, i, a = {}) => {
|
|
4
4
|
if (!t) return null;
|
|
5
5
|
let o = a.metadata !== null && typeof a.metadata == "object" ? Object.fromEntries(Object.entries(a.metadata).filter(([, e]) => typeof e == "string")) : void 0;
|
|
@@ -42,5 +42,4 @@ var t = (e, t, n, r, i, a = {}) => {
|
|
|
42
42
|
for (let t of e) t.type === "bounced" || t.type === "complained" ? yield* r.suppress(i, t.email, t.type) : t.type === "unsuppressed" && (yield* r.unsuppress(i, t.email));
|
|
43
43
|
return e;
|
|
44
44
|
});
|
|
45
|
-
//#endregion
|
|
46
45
|
export { c as handleMailEvents, s as parseMailEvent };
|
package/dist/idempotency.js
CHANGED
|
@@ -2,7 +2,7 @@ import { MAIL_IDEMPOTENCY_TABLE as e, attempt as t, describeError as n } from ".
|
|
|
2
2
|
import { Effect as r } from "effect";
|
|
3
3
|
import { createLogger as i } from "@voltro/logger";
|
|
4
4
|
import { and as a, eq as o } from "@voltro/database";
|
|
5
|
-
|
|
5
|
+
// src/idempotency.ts
|
|
6
6
|
var s = i({ scope: "@voltro/plugin-mail" }), c = (e) => e ?? "*", l = () => {
|
|
7
7
|
let e = /* @__PURE__ */ new Map(), t = (e, t) => `${c(e)} ${t}`;
|
|
8
8
|
return {
|
|
@@ -41,7 +41,7 @@ var s = i({ scope: "@voltro/plugin-mail" }), c = (e) => e ?? "*", l = () => {
|
|
|
41
41
|
}), r.catch((e) => r.sync(() => (s.warn("idempotency reserve failed — skipping send to avoid a double", {
|
|
42
42
|
key: o,
|
|
43
43
|
message: n(e)
|
|
44
|
-
}), { won: !1 })))) : r.succeed({ won: !
|
|
44
|
+
}), { won: !1 })))) : r.succeed({ won: !1 });
|
|
45
45
|
},
|
|
46
46
|
record: (l, u, d) => {
|
|
47
47
|
let f = i;
|
|
@@ -57,6 +57,18 @@ var s = i({ scope: "@voltro/plugin-mail" }), c = (e) => e ?? "*", l = () => {
|
|
|
57
57
|
i = e;
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
}, d = (n, i, s) => t(() => n.query({
|
|
61
|
+
table: e,
|
|
62
|
+
predicate: a(o("tenant", c(i)), o("key", s)),
|
|
63
|
+
projection: ["resultId", "resultProvider"],
|
|
64
|
+
order: [],
|
|
65
|
+
take: 1,
|
|
66
|
+
skip: void 0
|
|
67
|
+
})).pipe(r.map((e) => {
|
|
68
|
+
let t = e[0], n = t?.resultId, r = t?.resultProvider;
|
|
69
|
+
return typeof n == "string" && n !== "" && typeof r == "string" && r !== "" ? {
|
|
70
|
+
id: n,
|
|
71
|
+
provider: r
|
|
72
|
+
} : null;
|
|
73
|
+
}), r.catch(() => r.succeed(null)));
|
|
74
|
+
export { l as memoryIdempotencyStore, u as postgresIdempotencyStore, d as readMailReceipt };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { ColumnBuilder } from '@voltro/database';
|
|
1
2
|
import { Context } from 'effect';
|
|
2
3
|
import { DataStore } from '@voltro/database';
|
|
3
4
|
import { Effect } from 'effect';
|
|
5
|
+
import { FieldDefinitions } from '@voltro/database';
|
|
4
6
|
import { Layer } from 'effect';
|
|
5
7
|
import { PluginEgressDeclaration } from '@voltro/protocol';
|
|
6
8
|
import { PluginHttpRouteRequest } from '@voltro/protocol';
|
|
7
9
|
import { Schema } from 'effect';
|
|
10
|
+
import { Table } from '@voltro/database';
|
|
8
11
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
9
12
|
import { YieldableError } from 'effect/Cause';
|
|
10
13
|
|
|
@@ -215,6 +218,17 @@ export declare interface MailgunOptions {
|
|
|
215
218
|
|
|
216
219
|
export declare const mailgunProvider: (options: MailgunOptions) => MailProvider;
|
|
217
220
|
|
|
221
|
+
/** One row per idempotency key: the reservation, then the winning send's result. */
|
|
222
|
+
export declare const mailIdempotencyTable: Table<"_voltro_mail_idempotency", FieldDefinitions<{
|
|
223
|
+
readonly id: ColumnBuilder<number, "id", boolean>;
|
|
224
|
+
readonly tenant: ColumnBuilder<string, "text", boolean>;
|
|
225
|
+
readonly key: ColumnBuilder<string, "text", boolean>;
|
|
226
|
+
readonly resultId: ColumnBuilder<string | null, "text", boolean>;
|
|
227
|
+
readonly resultProvider: ColumnBuilder<string | null, "text", boolean>;
|
|
228
|
+
readonly createdAt: ColumnBuilder<Date, "timestamp", true>;
|
|
229
|
+
readonly created_at: ColumnBuilder<unknown, "text", boolean>;
|
|
230
|
+
}>, false, never>;
|
|
231
|
+
|
|
218
232
|
/** A message to send. `from` is optional here — the service fills it
|
|
219
233
|
* from the plugin's configured default before handing it to a provider. */
|
|
220
234
|
export declare interface MailMessage {
|
|
@@ -245,6 +259,10 @@ export declare interface MailMessage {
|
|
|
245
259
|
* step. The first send with a given key delivers; a replay with the same
|
|
246
260
|
* key is a no-op returning the recorded result. Scoped per tenant. */
|
|
247
261
|
readonly idempotencyKey?: string;
|
|
262
|
+
/** Override the service's transient retry count for this message. Zero means
|
|
263
|
+
* one provider dispatch, for callers that durably reconcile unknown outcomes.
|
|
264
|
+
* This is not a provider-side delivery/idempotency guarantee. */
|
|
265
|
+
readonly retryAttempts?: number;
|
|
248
266
|
/** `transactional` (default) or `broadcast`. See `MailStream`. */
|
|
249
267
|
readonly stream?: MailStream;
|
|
250
268
|
/** How a recipient leaves a broadcast. See `MailUnsubscribe`. */
|
|
@@ -447,6 +465,17 @@ export declare interface MailServiceShape {
|
|
|
447
465
|
*/
|
|
448
466
|
export declare type MailStream = 'transactional' | 'broadcast';
|
|
449
467
|
|
|
468
|
+
/** One row per suppressed (tenant, address). Kept until lifted — a bounce or a
|
|
469
|
+
* complaint does not expire, so this table has no retention policy. */
|
|
470
|
+
export declare const mailSuppressionTable: Table<"_voltro_mail_suppression", FieldDefinitions<{
|
|
471
|
+
readonly id: ColumnBuilder<number, "id", boolean>;
|
|
472
|
+
readonly tenant: ColumnBuilder<string, "text", boolean>;
|
|
473
|
+
readonly email: ColumnBuilder<string, "text", boolean>;
|
|
474
|
+
readonly reason: ColumnBuilder<string | null, "text", boolean>;
|
|
475
|
+
readonly createdAt: ColumnBuilder<Date, "timestamp", true>;
|
|
476
|
+
readonly created_at: ColumnBuilder<unknown, "text", boolean>;
|
|
477
|
+
}>, false, never>;
|
|
478
|
+
|
|
450
479
|
/**
|
|
451
480
|
* How a recipient leaves a broadcast. `oneClickUrl` is the HTTPS endpoint a
|
|
452
481
|
* mail client POSTs `List-Unsubscribe=One-Click` to (RFC 8058); the service
|
|
@@ -543,6 +572,11 @@ export declare const postmarkProvider: (options: {
|
|
|
543
572
|
/** Stable snapshot of everything the memory provider has "sent". */
|
|
544
573
|
export declare const readMailBuffer: () => ReadonlyArray<ResolvedMessage>;
|
|
545
574
|
|
|
575
|
+
/** Read a recorded persistent receipt without reserving a key or dispatching.
|
|
576
|
+
* Missing, pending, unbound or unavailable storage is not delivery evidence.
|
|
577
|
+
* The server caller owns authorization for the requested tenant/key. */
|
|
578
|
+
export declare const readMailReceipt: (store: DataStore, tenantId: string | null, key: string) => Effect.Effect<SendResult | null>;
|
|
579
|
+
|
|
546
580
|
/** Register CLI-discovered (or hand-collected) templates. Idempotent per key. */
|
|
547
581
|
export declare const registerEmailTemplates: (templates: ReadonlyArray<AnyEmailTemplate>) => void;
|
|
548
582
|
|
|
@@ -697,6 +731,10 @@ export declare interface TemplateMessage {
|
|
|
697
731
|
readonly scheduledAt?: Date;
|
|
698
732
|
/** See `MailMessage.idempotencyKey`. */
|
|
699
733
|
readonly idempotencyKey?: string;
|
|
734
|
+
/** Override the service's transient retry count for this message. Zero means
|
|
735
|
+
* one provider dispatch, for callers that durably reconcile unknown outcomes.
|
|
736
|
+
* This is not a provider-side delivery/idempotency guarantee. */
|
|
737
|
+
readonly retryAttempts?: number;
|
|
700
738
|
/** See `MailMessage.stream`. */
|
|
701
739
|
readonly stream?: MailStream;
|
|
702
740
|
/** See `MailMessage.unsubscribe`. */
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { MailError as e, MailService as t, MailWebhookError as n, defineEmail as r } from "./types.js";
|
|
2
2
|
import { renderTemplate as i } from "./render.js";
|
|
3
3
|
import { clearGlobalTemplates as a, globalTemplateCount as o, isEmailTemplate as s, listGlobalTemplates as c, lookupGlobalTemplate as l, registerEmailTemplates as u } from "./registry.js";
|
|
4
|
-
import { MAIL_IDEMPOTENCY_TABLE as
|
|
5
|
-
import { memorySuppressionStore as
|
|
6
|
-
import { memoryIdempotencyStore as
|
|
7
|
-
import { heldId as
|
|
8
|
-
import { clearMailBuffer as
|
|
9
|
-
import { buildMailService as
|
|
10
|
-
import { handleMailEvents as
|
|
11
|
-
import { createOutbox as
|
|
12
|
-
import { mailEgressOf as
|
|
4
|
+
import { MAIL_IDEMPOTENCY_TABLE as d, mailIdempotencyTable as f, mailSuppressionTable as p } from "./storeTables.js";
|
|
5
|
+
import { memorySuppressionStore as m, postgresSuppressionStore as h } from "./suppression.js";
|
|
6
|
+
import { memoryIdempotencyStore as g, postgresIdempotencyStore as _, readMailReceipt as v } from "./idempotency.js";
|
|
7
|
+
import { heldId as y, makeScheduler as b, memoryScheduleStore as x } from "./scheduler.js";
|
|
8
|
+
import { clearMailBuffer as S, consoleProvider as C, lettermintProvider as w, mailgunProvider as T, memoryProvider as E, postmarkProvider as D, readMailBuffer as O, resendProvider as k, sendgridProvider as A, sesProvider as j, smtpProvider as M, verifyLettermintSignature as N } from "./providers.js";
|
|
9
|
+
import { buildMailService as P, buildMailServiceWithScheduler as F, mailServiceLayer as I, resolveProvider as L } from "./service.js";
|
|
10
|
+
import { handleMailEvents as ee, parseMailEvent as R } from "./events.js";
|
|
11
|
+
import { createOutbox as z } from "./outbox.js";
|
|
12
|
+
import { mailEgressOf as B } from "./egress.js";
|
|
13
13
|
import { schemaSkeleton as te } from "./skeleton.js";
|
|
14
|
-
import { Duration as
|
|
14
|
+
import { Duration as ne, Effect as V, Fiber as H, Layer as U, Result as W, Schedule as G } from "effect";
|
|
15
15
|
import { definePlugin as K, pluginInstanceName as q } from "@voltro/protocol";
|
|
16
16
|
import { pluginEnv as J } from "@voltro/env";
|
|
17
17
|
import { registerRetention as Y, retentionTtlMsFromEnv as X } from "@voltro/database";
|
|
18
|
-
|
|
18
|
+
// src/index.ts
|
|
19
19
|
var Z = J([
|
|
20
20
|
{
|
|
21
21
|
name: "MAIL_PROVIDER",
|
|
@@ -141,7 +141,7 @@ var Z = J([
|
|
|
141
141
|
status: e,
|
|
142
142
|
contentType: "application/json",
|
|
143
143
|
body: JSON.stringify(t)
|
|
144
|
-
}),
|
|
144
|
+
}), re = (e, t, n, r) => {
|
|
145
145
|
let i = e.webhook;
|
|
146
146
|
if (i === void 0) throw Error(`@voltro/plugin-mail: provider '${e.name}' has no webhook verifier — mailPlugin({ webhook }) needs one (lettermint)`);
|
|
147
147
|
let a = n.secret ?? Z.read("MAIL_WEBHOOK_SECRET");
|
|
@@ -153,7 +153,7 @@ var Z = J([
|
|
|
153
153
|
if (!a) return $(503, { error: "mail webhook secret not configured" });
|
|
154
154
|
let o = await V.runPromise(V.result(i.verify(r.headers, new TextDecoder().decode(r.rawBody), a)));
|
|
155
155
|
if (W.isFailure(o)) return $(401, { error: o.failure.message });
|
|
156
|
-
let s =
|
|
156
|
+
let s = R(e.name, o.success);
|
|
157
157
|
try {
|
|
158
158
|
for (let e of s) {
|
|
159
159
|
e.type === "bounced" || e.type === "complained" ? await V.runPromise(t.suppress(null, e.email, e.type)) : e.type === "unsuppressed" && await V.runPromise(t.unsuppress(null, e.email));
|
|
@@ -169,46 +169,46 @@ var Z = J([
|
|
|
169
169
|
return $(200, { received: s.length });
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
-
},
|
|
173
|
-
let n = process.env, r =
|
|
172
|
+
}, ie = (e = {}) => {
|
|
173
|
+
let n = process.env, r = L({
|
|
174
174
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
175
175
|
...e.apiKey === void 0 ? {} : { apiKey: e.apiKey },
|
|
176
176
|
env: n
|
|
177
|
-
}), a = Z.read("MAIL_ALLOWLIST"), o = e.allowlist ?? (a ? a.split(",").map((e) => e.trim()).filter(Boolean) : void 0), s = n.NODE_ENV === "production", u = e.suppression === void 0 || e.suppression === "memory" ?
|
|
177
|
+
}), a = Z.read("MAIL_ALLOWLIST"), o = e.allowlist ?? (a ? a.split(",").map((e) => e.trim()).filter(Boolean) : void 0), s = n.NODE_ENV === "production", u = e.suppression === void 0 || e.suppression === "memory" ? m() : e.suppression === "postgres" ? h() : e.suppression, v = e.idempotency === void 0 || e.idempotency === "memory" ? g() : e.idempotency === "postgres" ? _() : e.idempotency, y = e.schedule ?? x(), b = [...e.suppression === "postgres" ? [p] : [], ...e.idempotency === "postgres" ? [f] : []];
|
|
178
178
|
e.idempotency === "postgres" && Y({
|
|
179
179
|
source: "plugin",
|
|
180
|
-
table:
|
|
180
|
+
table: d,
|
|
181
181
|
timeColumn: "createdAt",
|
|
182
182
|
ttlMs: X(process.env.VOLTRO_MAIL_IDEMPOTENCY_TTL_HOURS, 720),
|
|
183
183
|
label: "mail idempotency keys"
|
|
184
184
|
});
|
|
185
|
-
let
|
|
185
|
+
let S = z(), C = F({
|
|
186
186
|
provider: r,
|
|
187
187
|
suppression: u,
|
|
188
|
-
idempotency:
|
|
189
|
-
schedule:
|
|
190
|
-
onSent:
|
|
188
|
+
idempotency: v,
|
|
189
|
+
schedule: y,
|
|
190
|
+
onSent: S.record,
|
|
191
191
|
...e.from === void 0 ? {} : { from: e.from },
|
|
192
192
|
...e.replyTo === void 0 ? {} : { replyTo: e.replyTo },
|
|
193
193
|
...o === void 0 ? {} : { allowlist: o },
|
|
194
194
|
...e.attempts === void 0 ? {} : { attempts: e.attempts },
|
|
195
195
|
...e.templates === void 0 ? {} : { templates: e.templates },
|
|
196
196
|
production: s
|
|
197
|
-
}),
|
|
198
|
-
for (let t of e.templates ?? [])
|
|
199
|
-
let
|
|
197
|
+
}), w = U.succeed(t, C.service), T = r.supportsScheduling !== !0, E = e.scheduleTickMs ?? 1e3, D, O = /* @__PURE__ */ new Map();
|
|
198
|
+
for (let t of e.templates ?? []) O.set(t.locale ? `${t.name}:${t.locale}` : t.name, t);
|
|
199
|
+
let k = (e, t) => (t ? O.get(`${e}:${t}`) : void 0) ?? O.get(e) ?? l(e, t), A = (e) => ({
|
|
200
200
|
name: e.name,
|
|
201
201
|
...e.locale ? { locale: e.locale } : {},
|
|
202
202
|
props: e.preview ?? te(e.props)
|
|
203
|
-
}),
|
|
203
|
+
}), j = () => {
|
|
204
204
|
let t = /* @__PURE__ */ new Map();
|
|
205
|
-
for (let n of e.templates ?? []) t.set(n.locale ? `${n.name}:${n.locale}` : n.name,
|
|
205
|
+
for (let n of e.templates ?? []) t.set(n.locale ? `${n.name}:${n.locale}` : n.name, A(n));
|
|
206
206
|
for (let e of c()) {
|
|
207
207
|
let n = l(e.name, e.locale);
|
|
208
|
-
n && t.set(e.locale ? `${e.name}:${e.locale}` : e.name,
|
|
208
|
+
n && t.set(e.locale ? `${e.name}:${e.locale}` : e.name, A(n));
|
|
209
209
|
}
|
|
210
210
|
return [...t.values()];
|
|
211
|
-
},
|
|
211
|
+
}, M = e.webhook === void 0 ? [] : [re(r, u, e.webhook, s)], N = [
|
|
212
212
|
{
|
|
213
213
|
method: "GET",
|
|
214
214
|
path: "/outbox",
|
|
@@ -216,7 +216,7 @@ var Z = J([
|
|
|
216
216
|
description: "Recent sent mail (dev capture buffer), newest first",
|
|
217
217
|
handler: () => V.succeed({
|
|
218
218
|
kind: "json",
|
|
219
|
-
data: { sends:
|
|
219
|
+
data: { sends: S.list() }
|
|
220
220
|
})
|
|
221
221
|
},
|
|
222
222
|
{
|
|
@@ -226,7 +226,7 @@ var Z = J([
|
|
|
226
226
|
description: "Discovered + registered email templates",
|
|
227
227
|
handler: () => V.succeed({
|
|
228
228
|
kind: "json",
|
|
229
|
-
data: { templates:
|
|
229
|
+
data: { templates: j() }
|
|
230
230
|
})
|
|
231
231
|
},
|
|
232
232
|
{
|
|
@@ -249,7 +249,7 @@ var Z = J([
|
|
|
249
249
|
status: 400,
|
|
250
250
|
data: { error: "missing \"template\"" }
|
|
251
251
|
};
|
|
252
|
-
let n =
|
|
252
|
+
let n = k(t.template, t.locale);
|
|
253
253
|
return n ? yield* i(n, t.props).pipe(V.map((e) => ({
|
|
254
254
|
kind: "json",
|
|
255
255
|
data: e
|
|
@@ -281,21 +281,21 @@ var Z = J([
|
|
|
281
281
|
description: "Transactional and broadcast email — MailService over Lettermint / Resend / Postmark / SendGrid / SES / Mailgun / SMTP (+ console/memory), with default-from, transient retry, suppression and a dev allowlist.",
|
|
282
282
|
permissions: P,
|
|
283
283
|
declaredEnv: Z.declared,
|
|
284
|
-
egress:
|
|
284
|
+
egress: B({
|
|
285
285
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
286
286
|
env: n
|
|
287
287
|
}),
|
|
288
|
-
services:
|
|
289
|
-
inspectEndpoints:
|
|
290
|
-
...
|
|
288
|
+
services: w,
|
|
289
|
+
inspectEndpoints: N,
|
|
290
|
+
...M.length > 0 ? { httpRoutes: M } : {},
|
|
291
291
|
...b.length > 0 ? { extendSchema: { tables: b } } : {},
|
|
292
292
|
bindDataStore: async (e) => {
|
|
293
|
-
await u.bind?.(e), await
|
|
293
|
+
await u.bind?.(e), await v.bind?.(e);
|
|
294
294
|
},
|
|
295
295
|
onActivate: (t) => V.gen(function* () {
|
|
296
|
-
if (u.activate && (yield* V.promise(() => u.activate(t.env))),
|
|
297
|
-
let e =
|
|
298
|
-
|
|
296
|
+
if (u.activate && (yield* V.promise(() => u.activate(t.env))), v.activate && (yield* V.promise(() => v.activate(t.env))), T) {
|
|
297
|
+
let e = C.scheduler.tick().pipe(V.repeat(G.spaced(ne.millis(Math.max(50, E)))));
|
|
298
|
+
D = V.runFork(e);
|
|
299
299
|
}
|
|
300
300
|
t.logger.info("mail active", {
|
|
301
301
|
provider: r.name,
|
|
@@ -308,11 +308,10 @@ var Z = J([
|
|
|
308
308
|
});
|
|
309
309
|
}),
|
|
310
310
|
onDeactivate: () => V.gen(function* () {
|
|
311
|
-
|
|
311
|
+
D !== void 0 && (yield* H.interrupt(D), D = void 0), u.deactivate && (yield* V.promise(() => u.deactivate())), v.deactivate && (yield* V.promise(() => v.deactivate()));
|
|
312
312
|
})
|
|
313
313
|
}),
|
|
314
|
-
service:
|
|
314
|
+
service: C.service
|
|
315
315
|
};
|
|
316
316
|
};
|
|
317
|
-
|
|
318
|
-
export { e as MailError, t as MailService, n as MailWebhookError, M as buildMailService, N as buildMailServiceWithScheduler, a as clearGlobalTemplates, b as clearMailBuffer, x as consoleProvider, R as createOutbox, r as defineEmail, o as globalTemplateCount, I as handleMailEvents, _ as heldId, s as isEmailTemplate, S as lettermintProvider, c as listGlobalTemplates, l as lookupGlobalTemplate, z as mailEgressOf, re as mailPlugin, P as mailServiceLayer, C as mailgunProvider, v as makeScheduler, h as memoryIdempotencyStore, w as memoryProvider, y as memoryScheduleStore, p as memorySuppressionStore, L as parseMailEvent, g as postgresIdempotencyStore, m as postgresSuppressionStore, T as postmarkProvider, E as readMailBuffer, u as registerEmailTemplates, i as renderTemplate, D as resendProvider, F as resolveProvider, O as sendgridProvider, k as sesProvider, A as smtpProvider, j as verifyLettermintSignature };
|
|
317
|
+
export { e as MailError, t as MailService, n as MailWebhookError, P as buildMailService, F as buildMailServiceWithScheduler, a as clearGlobalTemplates, S as clearMailBuffer, C as consoleProvider, z as createOutbox, r as defineEmail, o as globalTemplateCount, ee as handleMailEvents, y as heldId, s as isEmailTemplate, w as lettermintProvider, c as listGlobalTemplates, l as lookupGlobalTemplate, B as mailEgressOf, f as mailIdempotencyTable, ie as mailPlugin, I as mailServiceLayer, p as mailSuppressionTable, T as mailgunProvider, b as makeScheduler, g as memoryIdempotencyStore, E as memoryProvider, x as memoryScheduleStore, m as memorySuppressionStore, R as parseMailEvent, _ as postgresIdempotencyStore, h as postgresSuppressionStore, D as postmarkProvider, O as readMailBuffer, v as readMailReceipt, u as registerEmailTemplates, i as renderTemplate, k as resendProvider, L as resolveProvider, A as sendgridProvider, j as sesProvider, M as smtpProvider, N as verifyLettermintSignature };
|
package/dist/outbox.js
CHANGED
package/dist/providers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MailError as e, MailWebhookError as t, toList as n } from "./types.js";
|
|
2
2
|
import { Effect as r } from "effect";
|
|
3
3
|
import { FetchHttpClient as i, HttpClient as a, HttpClientRequest as o } from "effect/http";
|
|
4
|
-
|
|
4
|
+
// src/providers.ts
|
|
5
5
|
var s = 0, c = (e) => `${e}-${s += 1}`, l = (e) => e === void 0 || e === 408 || e === 429 || e >= 500, u = (t, n) => {
|
|
6
6
|
let r = n.status;
|
|
7
7
|
return new e({
|
|
@@ -373,5 +373,4 @@ var s = 0, c = (e) => `${e}-${s += 1}`, l = (e) => e === void 0 || e === 408 ||
|
|
|
373
373
|
provider: "memory"
|
|
374
374
|
}))
|
|
375
375
|
});
|
|
376
|
-
//#endregion
|
|
377
376
|
export { P as clearMailBuffer, j as consoleProvider, h as lettermintProvider, k as mailgunProvider, F as memoryProvider, v as postmarkProvider, N as readMailBuffer, _ as resendProvider, S as sendgridProvider, D as sesProvider, A as smtpProvider, m as verifyLettermintSignature };
|
package/dist/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/registry.ts
|
|
2
2
|
var e = /* @__PURE__ */ new Map(), t = (e) => e.locale ? `${e.name}:${e.locale}` : e.name, n = (n) => {
|
|
3
3
|
for (let r of n) e.set(t(r), r);
|
|
4
4
|
}, r = (t, n) => (n ? e.get(`${t}:${n}`) : void 0) ?? e.get(t), i = () => e.size, a = () => [...e.values()].map((e) => e.locale ? {
|
|
@@ -9,5 +9,4 @@ var e = /* @__PURE__ */ new Map(), t = (e) => e.locale ? `${e.name}:${e.locale}`
|
|
|
9
9
|
let t = e;
|
|
10
10
|
return typeof t.name == "string" && typeof t.subject == "function" && typeof t.render == "function" && t.props !== void 0;
|
|
11
11
|
};
|
|
12
|
-
//#endregion
|
|
13
12
|
export { o as clearGlobalTemplates, i as globalTemplateCount, s as isEmailTemplate, a as listGlobalTemplates, r as lookupGlobalTemplate, n as registerEmailTemplates };
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MailError as e } from "./types.js";
|
|
2
2
|
import { Effect as t, Schema as n, SchemaIssue as r } from "effect";
|
|
3
|
-
|
|
3
|
+
// src/render.ts
|
|
4
4
|
var i = (e) => {
|
|
5
5
|
try {
|
|
6
6
|
let t = r.makeFormatterStandardSchemaV1()(e.issue).issues;
|
|
@@ -40,5 +40,4 @@ var i = (e) => {
|
|
|
40
40
|
})
|
|
41
41
|
};
|
|
42
42
|
});
|
|
43
|
-
//#endregion
|
|
44
43
|
export { s as renderTemplate };
|
package/dist/scheduler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect as e } from "effect";
|
|
2
2
|
import { createLogger as t } from "@voltro/logger";
|
|
3
|
-
|
|
3
|
+
// src/scheduler.ts
|
|
4
4
|
var n = t({ scope: "@voltro/plugin-mail" }), r = () => {
|
|
5
5
|
let t = /* @__PURE__ */ new Map();
|
|
6
6
|
return {
|
|
@@ -32,5 +32,4 @@ var n = t({ scope: "@voltro/plugin-mail" }), r = () => {
|
|
|
32
32
|
}),
|
|
33
33
|
pending: () => t.store.size()
|
|
34
34
|
});
|
|
35
|
-
//#endregion
|
|
36
35
|
export { a as heldId, o as makeScheduler, r as memoryScheduleStore };
|
package/dist/service.js
CHANGED
|
@@ -7,7 +7,7 @@ import { makeScheduler as s, memoryScheduleStore as c } from "./scheduler.js";
|
|
|
7
7
|
import { consoleProvider as l, lettermintProvider as u, mailgunProvider as d, memoryProvider as f, postmarkProvider as p, resendProvider as m, sendgridProvider as h, sesProvider as g, smtpProvider as _ } from "./providers.js";
|
|
8
8
|
import { Effect as v, Layer as y, Schedule as b } from "effect";
|
|
9
9
|
import { createLogger as x } from "@voltro/logger";
|
|
10
|
-
|
|
10
|
+
// src/service.ts
|
|
11
11
|
var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
12
12
|
if (e.provider !== void 0 && typeof e.provider != "string") return e.provider;
|
|
13
13
|
let t = e.env ?? {}, n = e.provider ?? t.MAIL_PROVIDER ?? "console", r = (e, t) => {
|
|
@@ -47,18 +47,18 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
47
47
|
default: return l((e, t) => S.info(e, t));
|
|
48
48
|
}
|
|
49
49
|
}, w = (t) => {
|
|
50
|
-
let l = t.attempts ?? 3, u = t.suppression ?? a(), d = t.allowlist, f = t.production !== !0 && d !== void 0 && d.length > 0, p = (e) => d.some((t) => t.toLowerCase() === e.toLowerCase()), m = b.recurs(
|
|
51
|
-
for (let e of t.templates ?? [])
|
|
52
|
-
let
|
|
53
|
-
if (
|
|
54
|
-
let e =
|
|
55
|
-
e !== void 0 &&
|
|
50
|
+
let l = t.attempts ?? 3, u = t.suppression ?? a(), d = t.allowlist, f = t.production !== !0 && d !== void 0 && d.length > 0, p = (e) => d.some((t) => t.toLowerCase() === e.toLowerCase()), m = (e) => b.recurs(e).pipe(b.while(({ input: e }) => e.transient)), h = m(l), g = /* @__PURE__ */ new Map();
|
|
51
|
+
for (let e of t.templates ?? []) g.set(e.locale ? `${e.name}:${e.locale}` : e.name, e);
|
|
52
|
+
let _ = (e, t) => (t ? g.get(`${e}:${t}`) : void 0) ?? g.get(e) ?? i(e, t), y = /* @__PURE__ */ new Map(), x = (e, t) => `${e.name}|${e.locale ?? ""}|${JSON.stringify(t ?? null)}`, C = (e, t) => {
|
|
53
|
+
if (y.set(e, t), y.size > 500) {
|
|
54
|
+
let e = y.keys().next().value;
|
|
55
|
+
e !== void 0 && y.delete(e);
|
|
56
56
|
}
|
|
57
|
-
},
|
|
57
|
+
}, w = t.idempotency ?? o(), T = s({
|
|
58
58
|
store: t.schedule ?? c(),
|
|
59
59
|
providerSchedules: t.provider.supportsScheduling === !0,
|
|
60
|
-
flush: (e) =>
|
|
61
|
-
}),
|
|
60
|
+
flush: (e) => A(e)
|
|
61
|
+
}), E = (n) => {
|
|
62
62
|
if (n.stream !== "broadcast" || t.provider.managesUnsubscribe === !0) return v.succeed(void 0);
|
|
63
63
|
let r = n.unsubscribe;
|
|
64
64
|
if (r === void 0 || !r.oneClickUrl.startsWith("https://")) return v.fail(new e({
|
|
@@ -71,7 +71,7 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
71
71
|
"List-Unsubscribe": i.join(", "),
|
|
72
72
|
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
|
|
73
73
|
});
|
|
74
|
-
},
|
|
74
|
+
}, D = (r) => v.gen(function* () {
|
|
75
75
|
let i = r.from ?? t.from;
|
|
76
76
|
if (!i) return yield* v.fail(new e({
|
|
77
77
|
provider: t.provider.name,
|
|
@@ -122,7 +122,7 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
122
122
|
}
|
|
123
123
|
};
|
|
124
124
|
a = s;
|
|
125
|
-
let c = r.replyTo ?? t.replyTo, l = yield*
|
|
125
|
+
let c = r.replyTo ?? t.replyTo, l = yield* E(r), d = l === void 0 ? r.headers : {
|
|
126
126
|
...r.headers,
|
|
127
127
|
...l
|
|
128
128
|
};
|
|
@@ -136,9 +136,13 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
136
136
|
...d === void 0 ? {} : { headers: d }
|
|
137
137
|
}
|
|
138
138
|
};
|
|
139
|
-
}),
|
|
139
|
+
}), O = (n) => n.retryAttempts === void 0 || Number.isSafeInteger(n.retryAttempts) && n.retryAttempts >= 0 ? v.void : v.fail(new e({
|
|
140
|
+
provider: t.provider.name,
|
|
141
|
+
transient: !1,
|
|
142
|
+
message: "retryAttempts must be a non-negative safe integer."
|
|
143
|
+
})), k = (e) => {
|
|
140
144
|
let r = n(e.to);
|
|
141
|
-
return t.provider.send(e).pipe(v.retry(m), v.tap((n) => v.sync(() => {
|
|
145
|
+
return t.provider.send(e).pipe(v.retry(e.retryAttempts === void 0 ? h : m(e.retryAttempts)), v.tap((n) => v.sync(() => {
|
|
142
146
|
S.info("mail sent", {
|
|
143
147
|
provider: n.provider,
|
|
144
148
|
id: n.id,
|
|
@@ -156,7 +160,7 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
156
160
|
S.warn("mail failed", {
|
|
157
161
|
provider: n.provider,
|
|
158
162
|
status: n.status,
|
|
159
|
-
message:
|
|
163
|
+
message: "Mail provider request failed."
|
|
160
164
|
}), t.onSent?.({
|
|
161
165
|
id: `err-${Date.now()}`,
|
|
162
166
|
to: r,
|
|
@@ -164,13 +168,14 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
164
168
|
subject: e.subject,
|
|
165
169
|
provider: n.provider,
|
|
166
170
|
ok: !1,
|
|
167
|
-
error:
|
|
171
|
+
error: "Mail provider request failed.",
|
|
168
172
|
ts: Date.now()
|
|
169
173
|
});
|
|
170
174
|
})));
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
}, A = (e) => v.gen(function* () {
|
|
176
|
+
yield* O(e);
|
|
177
|
+
let r = yield* D(e);
|
|
178
|
+
return r.kind === "skip" ? r.result : T.shouldHold(e.scheduledAt) ? (yield* T.hold(r.message), S.info("mail scheduled (held for later flush)", {
|
|
174
179
|
subject: r.message.subject,
|
|
175
180
|
dueAt: e.scheduledAt?.toISOString()
|
|
176
181
|
}), t.onSent?.({
|
|
@@ -184,24 +189,24 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
184
189
|
}), {
|
|
185
190
|
id: "scheduled",
|
|
186
191
|
provider: "scheduled"
|
|
187
|
-
}) : yield*
|
|
188
|
-
}),
|
|
189
|
-
let r = yield*
|
|
192
|
+
}) : yield* k(r.message);
|
|
193
|
+
}), j = (e, t, n) => t === void 0 ? n : v.gen(function* () {
|
|
194
|
+
let r = yield* w.reserve(e, t);
|
|
190
195
|
if (!r.won) return S.info("mail idempotent replay — skipped", { key: t }), r.prior ?? {
|
|
191
196
|
id: "idempotent-replay",
|
|
192
197
|
provider: "idempotent"
|
|
193
198
|
};
|
|
194
199
|
let i = yield* n;
|
|
195
|
-
return yield*
|
|
196
|
-
}),
|
|
197
|
-
let n =
|
|
200
|
+
return yield* w.record(e, t, i), i;
|
|
201
|
+
}), M = (t) => v.suspend(() => {
|
|
202
|
+
let n = _(t.template, t.locale);
|
|
198
203
|
if (!n) return v.fail(new e({
|
|
199
204
|
provider: "template",
|
|
200
205
|
message: `unknown template '${t.template}'${t.locale ? ` (locale '${t.locale}')` : ""}`,
|
|
201
206
|
transient: !1
|
|
202
207
|
}));
|
|
203
|
-
let i =
|
|
204
|
-
return (a ? v.succeed(a) : r(n, t.props).pipe(v.tap((e) => v.sync(() =>
|
|
208
|
+
let i = x(n, t.props), a = y.get(i);
|
|
209
|
+
return (a ? v.succeed(a) : r(n, t.props).pipe(v.tap((e) => v.sync(() => C(i, e))))).pipe(v.map((e) => ({
|
|
205
210
|
to: t.to,
|
|
206
211
|
subject: e.subject,
|
|
207
212
|
html: e.html,
|
|
@@ -216,26 +221,27 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
216
221
|
...t.tenantId === void 0 ? {} : { tenantId: t.tenantId },
|
|
217
222
|
...t.scheduledAt === void 0 ? {} : { scheduledAt: t.scheduledAt },
|
|
218
223
|
...t.idempotencyKey === void 0 ? {} : { idempotencyKey: t.idempotencyKey },
|
|
224
|
+
...t.retryAttempts === void 0 ? {} : { retryAttempts: t.retryAttempts },
|
|
219
225
|
...t.stream === void 0 ? {} : { stream: t.stream },
|
|
220
226
|
...t.unsubscribe === void 0 ? {} : { unsubscribe: t.unsubscribe },
|
|
221
227
|
...t.metadata === void 0 ? {} : { metadata: t.metadata }
|
|
222
228
|
})));
|
|
223
|
-
}),
|
|
229
|
+
}), N = (e) => ("template" in e ? M(e) : v.succeed(e)).pipe(v.tap(O));
|
|
224
230
|
return {
|
|
225
231
|
service: {
|
|
226
232
|
send: (e) => v.gen(function* () {
|
|
227
|
-
let t = yield*
|
|
228
|
-
return yield*
|
|
233
|
+
let t = yield* N(e);
|
|
234
|
+
return yield* j(t.tenantId ?? null, t.idempotencyKey, A(t));
|
|
229
235
|
}),
|
|
230
236
|
sendBatch: (e) => v.gen(function* () {
|
|
231
|
-
let r = yield* v.forEach(e,
|
|
237
|
+
let r = yield* v.forEach(e, N), i = r.some((e) => e.idempotencyKey !== void 0 || e.retryAttempts !== void 0 || T.shouldHold(e.scheduledAt));
|
|
232
238
|
if (t.provider.sendBatch && !i) {
|
|
233
239
|
let e = [], i = [];
|
|
234
240
|
for (let t of r) {
|
|
235
|
-
let n = yield*
|
|
241
|
+
let n = yield* D(t);
|
|
236
242
|
n.kind === "skip" ? i.push(n.result) : (i.push(e.length), e.push(n.message));
|
|
237
243
|
}
|
|
238
|
-
let a = e.length > 0 ? yield* t.provider.sendBatch(e).pipe(v.retry(
|
|
244
|
+
let a = e.length > 0 ? yield* t.provider.sendBatch(e).pipe(v.retry(h), v.tap((r) => v.sync(() => {
|
|
239
245
|
e.forEach((e, i) => {
|
|
240
246
|
let a = r[i];
|
|
241
247
|
a && (S.info("mail sent (batch)", {
|
|
@@ -255,14 +261,14 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
255
261
|
})), v.tapError((e) => v.sync(() => S.warn("mail batch failed", {
|
|
256
262
|
provider: e.provider,
|
|
257
263
|
status: e.status,
|
|
258
|
-
message:
|
|
264
|
+
message: "Mail provider request failed."
|
|
259
265
|
})))) : [];
|
|
260
266
|
return i.map((e) => typeof e == "number" ? a[e] ?? {
|
|
261
267
|
id: "batch",
|
|
262
268
|
provider: t.provider.name
|
|
263
269
|
} : e);
|
|
264
270
|
}
|
|
265
|
-
return yield* v.forEach(r, (e) =>
|
|
271
|
+
return yield* v.forEach(r, (e) => j(e.tenantId ?? null, e.idempotencyKey, A(e)));
|
|
266
272
|
}),
|
|
267
273
|
suppress: u.suppress,
|
|
268
274
|
unsuppress: u.unsuppress,
|
|
@@ -272,8 +278,7 @@ var S = x({ scope: "@voltro/plugin-mail" }), C = (e) => {
|
|
|
272
278
|
managesUnsubscribe: t.provider.managesUnsubscribe === !0
|
|
273
279
|
}
|
|
274
280
|
},
|
|
275
|
-
scheduler:
|
|
281
|
+
scheduler: T
|
|
276
282
|
};
|
|
277
283
|
}, T = (e) => w(e).service, E = (e) => y.succeed(t, T(e));
|
|
278
|
-
//#endregion
|
|
279
284
|
export { T as buildMailService, w as buildMailServiceWithScheduler, E as mailServiceLayer, C as resolveProvider };
|
package/dist/skeleton.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/skeleton.ts
|
|
2
2
|
var e = (t, n) => {
|
|
3
3
|
if (n > 6) return null;
|
|
4
4
|
let r = t.encoding === void 0 ? t : t.encoding[t.encoding.length - 1].to;
|
|
@@ -20,5 +20,4 @@ var e = (t, n) => {
|
|
|
20
20
|
default: return null;
|
|
21
21
|
}
|
|
22
22
|
}, t = (t) => e(t.ast, 0);
|
|
23
|
-
//#endregion
|
|
24
23
|
export { t as schemaSkeleton };
|
package/dist/storeTables.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect as e } from "effect";
|
|
2
2
|
import { dropped as t, id as n, table as r, text as i, timestamp as a } from "@voltro/database";
|
|
3
|
-
|
|
3
|
+
// src/storeTables.ts
|
|
4
4
|
var o = "_voltro_mail_suppression", s = "_voltro_mail_idempotency", c = r(o, {
|
|
5
5
|
id: n({ scheme: "numeric" }),
|
|
6
6
|
tenant: i().maxLength(191),
|
|
@@ -24,5 +24,4 @@ var o = "_voltro_mail_suppression", s = "_voltro_mail_idempotency", c = r(o, {
|
|
|
24
24
|
for (let n = e, r = 0; n != null && r < 4; n = n.cause, r++) t.push(n instanceof Error ? n.message : String(n));
|
|
25
25
|
return t.join(" ← ");
|
|
26
26
|
};
|
|
27
|
-
//#endregion
|
|
28
27
|
export { s as MAIL_IDEMPOTENCY_TABLE, o as MAIL_SUPPRESSION_TABLE, u as attempt, d as describeError, l as mailIdempotencyTable, c as mailSuppressionTable };
|
package/dist/suppression.js
CHANGED
|
@@ -2,7 +2,7 @@ import { MAIL_SUPPRESSION_TABLE as e, attempt as t, describeError as n, mailSupp
|
|
|
2
2
|
import { Effect as i } from "effect";
|
|
3
3
|
import { createLogger as a } from "@voltro/logger";
|
|
4
4
|
import { and as o, eq as s, inSet as c, queryFor as l } from "@voltro/database";
|
|
5
|
-
|
|
5
|
+
// src/suppression.ts
|
|
6
6
|
var u = a({ scope: "@voltro/plugin-mail" }), d = (e) => e.trim().toLowerCase(), f = "*", p = (e) => e ?? f, m = () => {
|
|
7
7
|
let e = /* @__PURE__ */ new Set(), t = (e, t) => `${p(e)}\u0000${d(t)}`;
|
|
8
8
|
return {
|
|
@@ -42,5 +42,4 @@ var u = a({ scope: "@voltro/plugin-mail" }), d = (e) => e.trim().toLowerCase(),
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
};
|
|
45
|
-
//#endregion
|
|
46
45
|
export { m as memorySuppressionStore, h as postgresSuppressionStore };
|