@voltro/plugin-mail 0.83.0 → 0.84.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 +717 -0
- package/dist/index.js +171 -168
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -2,84 +2,86 @@ import { Context as e, Duration as t, Effect as n, Fiber as r, Layer as i, Parse
|
|
|
2
2
|
import { definePlugin as c, pluginInstanceName as l } from "@voltro/protocol";
|
|
3
3
|
import { pluginEnv as u } from "@voltro/env";
|
|
4
4
|
import { createLogger as d } from "@voltro/logger";
|
|
5
|
-
import { and as f,
|
|
6
|
-
import { FetchHttpClient as
|
|
5
|
+
import { and as f, dropped as p, eq as m, id as h, inSet as g, queryFor as _, registerRetention as ee, retentionTtlMsFromEnv as v, table as y, text as b, timestamp as x } from "@voltro/database";
|
|
6
|
+
import { FetchHttpClient as te, HttpClient as S, HttpClientRequest as C } from "@effect/platform";
|
|
7
7
|
//#region src/types.ts
|
|
8
|
-
var
|
|
8
|
+
var w = class extends s.TaggedError()("MailError", {
|
|
9
9
|
provider: s.String,
|
|
10
10
|
message: s.String,
|
|
11
11
|
transient: s.Boolean,
|
|
12
12
|
status: s.optional(s.Number)
|
|
13
|
-
}) {},
|
|
13
|
+
}) {}, T = class extends s.TaggedError()("MailWebhookError", {
|
|
14
14
|
provider: s.String,
|
|
15
15
|
message: s.String
|
|
16
|
-
}) {},
|
|
16
|
+
}) {}, ne = (e) => ({
|
|
17
17
|
name: e.name,
|
|
18
18
|
props: e.props,
|
|
19
19
|
subject: e.subject,
|
|
20
20
|
render: e.render,
|
|
21
21
|
...e.locale === void 0 ? {} : { locale: e.locale },
|
|
22
22
|
...e.preview === void 0 ? {} : { preview: e.preview }
|
|
23
|
-
}),
|
|
23
|
+
}), E = class extends e.Tag("@voltro/plugin-mail/MailService")() {}, D = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, re = (e) => {
|
|
24
24
|
try {
|
|
25
25
|
let t = a.ArrayFormatter.formatErrorSync(e);
|
|
26
26
|
return t.length === 0 ? String(e) : t.map((e) => `${e.path.length > 0 ? e.path.join(".") : "(root)"}: ${e.message}`).join("; ");
|
|
27
27
|
} catch {
|
|
28
28
|
return String(e);
|
|
29
29
|
}
|
|
30
|
-
}, O = null,
|
|
30
|
+
}, O = null, ie = async () => {
|
|
31
31
|
if (!O) {
|
|
32
32
|
let e = await import("@react-email/render");
|
|
33
33
|
if (O = e.render ?? e.default?.render ?? null, !O) throw Error("@react-email/render: `render` export not found (install @react-email/render + react + react-dom)");
|
|
34
34
|
}
|
|
35
35
|
return O;
|
|
36
36
|
}, k = (e, t) => n.gen(function* () {
|
|
37
|
-
let r = e, i = yield* s.decodeUnknown(r.props)(t).pipe(n.mapError((t) => new
|
|
37
|
+
let r = e, i = yield* s.decodeUnknown(r.props)(t).pipe(n.mapError((t) => new w({
|
|
38
38
|
provider: "template",
|
|
39
|
-
message: `invalid props for '${e.name}': ${
|
|
39
|
+
message: `invalid props for '${e.name}': ${re(t)}`.slice(0, 500),
|
|
40
40
|
transient: !1
|
|
41
41
|
}))), a = r.subject(i), o = r.render(i);
|
|
42
42
|
return {
|
|
43
43
|
subject: a,
|
|
44
44
|
html: yield* n.tryPromise({
|
|
45
|
-
try: async () => (await
|
|
46
|
-
catch: (t) => new
|
|
45
|
+
try: async () => (await ie())(o, { plainText: !1 }),
|
|
46
|
+
catch: (t) => new w({
|
|
47
47
|
provider: "template",
|
|
48
48
|
message: `render failed for '${e.name}': ${String(t)}`.slice(0, 500),
|
|
49
49
|
transient: !1
|
|
50
50
|
})
|
|
51
51
|
}),
|
|
52
52
|
text: yield* n.tryPromise({
|
|
53
|
-
try: async () => (await
|
|
54
|
-
catch: (t) => new
|
|
53
|
+
try: async () => (await ie())(o, { plainText: !0 }),
|
|
54
|
+
catch: (t) => new w({
|
|
55
55
|
provider: "template",
|
|
56
56
|
message: `render(text) failed for '${e.name}': ${String(t)}`.slice(0, 500),
|
|
57
57
|
transient: !1
|
|
58
58
|
})
|
|
59
59
|
})
|
|
60
60
|
};
|
|
61
|
-
}), A = /* @__PURE__ */ new Map(),
|
|
62
|
-
for (let t of e) A.set(
|
|
63
|
-
}, j = (e, t) => (t ? A.get(`${e}:${t}`) : void 0) ?? A.get(e),
|
|
61
|
+
}), A = /* @__PURE__ */ new Map(), ae = (e) => e.locale ? `${e.name}:${e.locale}` : e.name, oe = (e) => {
|
|
62
|
+
for (let t of e) A.set(ae(t), t);
|
|
63
|
+
}, j = (e, t) => (t ? A.get(`${e}:${t}`) : void 0) ?? A.get(e), se = () => A.size, ce = () => [...A.values()].map((e) => e.locale ? {
|
|
64
64
|
name: e.name,
|
|
65
65
|
locale: e.locale
|
|
66
|
-
} : { name: e.name }),
|
|
66
|
+
} : { name: e.name }), le = () => A.clear(), ue = (e) => {
|
|
67
67
|
if (typeof e != "object" || !e) return !1;
|
|
68
68
|
let t = e;
|
|
69
69
|
return typeof t.name == "string" && typeof t.subject == "function" && typeof t.render == "function" && t.props !== void 0;
|
|
70
|
-
}, M = "_voltro_mail_suppression", N = "_voltro_mail_idempotency",
|
|
71
|
-
id:
|
|
72
|
-
tenant:
|
|
73
|
-
email:
|
|
74
|
-
reason:
|
|
75
|
-
createdAt:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
}, M = "_voltro_mail_suppression", N = "_voltro_mail_idempotency", de = y(M, {
|
|
71
|
+
id: h({ scheme: "numeric" }),
|
|
72
|
+
tenant: b().maxLength(191),
|
|
73
|
+
email: b().maxLength(320),
|
|
74
|
+
reason: b().nullable(),
|
|
75
|
+
createdAt: x().default("now"),
|
|
76
|
+
created_at: p()
|
|
77
|
+
}).unique("mailSuppressionAddress", ["tenant", "email"]).nonReactive(), fe = y(N, {
|
|
78
|
+
id: h({ scheme: "numeric" }),
|
|
79
|
+
tenant: b().maxLength(191),
|
|
80
|
+
key: b().maxLength(255),
|
|
81
|
+
resultId: b().nullable().renamedFrom("result_id"),
|
|
82
|
+
resultProvider: b().nullable().renamedFrom("result_provider"),
|
|
83
|
+
createdAt: x().default("now"),
|
|
84
|
+
created_at: p()
|
|
83
85
|
}).unique("mailIdempotencyKey", ["tenant", "key"]).nonReactive(), P = (e) => n.tryPromise({
|
|
84
86
|
try: e,
|
|
85
87
|
catch: (e) => e
|
|
@@ -87,14 +89,14 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
87
89
|
let t = [];
|
|
88
90
|
for (let n = e, r = 0; n != null && r < 4; n = n.cause, r++) t.push(n instanceof Error ? n.message : String(n));
|
|
89
91
|
return t.join(" ← ");
|
|
90
|
-
},
|
|
92
|
+
}, pe = d({ scope: "@voltro/plugin-mail" }), I = (e) => e.trim().toLowerCase(), me = "*", L = (e) => e ?? me, R = () => {
|
|
91
93
|
let e = /* @__PURE__ */ new Set(), t = (e, t) => `${L(e)}\u0000${I(t)}`;
|
|
92
94
|
return {
|
|
93
95
|
isSuppressed: (r, i) => n.sync(() => e.has(t(r, i)) || e.has(t(null, i))),
|
|
94
96
|
suppress: (r, i) => n.sync(() => void e.add(t(r, i))),
|
|
95
97
|
unsuppress: (r, i) => n.sync(() => void e.delete(t(r, i)))
|
|
96
98
|
};
|
|
97
|
-
},
|
|
99
|
+
}, he = () => {
|
|
98
100
|
let e = null, t = (e, t) => ({
|
|
99
101
|
tenant: L(e),
|
|
100
102
|
email: I(t)
|
|
@@ -102,7 +104,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
102
104
|
return {
|
|
103
105
|
isSuppressed: (t, r) => {
|
|
104
106
|
let i = e;
|
|
105
|
-
return i ? P(() => i.query(
|
|
107
|
+
return i ? P(() => i.query(_(de).where(f(m("email", I(r)), g("tenant", [L(t), me]))).limit(1).descriptor)).pipe(n.map((e) => e.length > 0), n.catchAll(() => n.succeed(!1))) : n.succeed(!1);
|
|
106
108
|
},
|
|
107
109
|
suppress: (r, i, a) => {
|
|
108
110
|
let o = e;
|
|
@@ -110,7 +112,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
110
112
|
...t(r, i),
|
|
111
113
|
reason: a,
|
|
112
114
|
createdAt: /* @__PURE__ */ new Date()
|
|
113
|
-
}, { conflictColumns: ["tenant", "email"] })).pipe(n.asVoid, n.catchAll((e) => n.sync(() =>
|
|
115
|
+
}, { conflictColumns: ["tenant", "email"] })).pipe(n.asVoid, n.catchAll((e) => n.sync(() => pe.warn("suppress write failed", {
|
|
114
116
|
email: I(i),
|
|
115
117
|
message: F(e)
|
|
116
118
|
})))) : n.void;
|
|
@@ -119,13 +121,13 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
119
121
|
let a = e;
|
|
120
122
|
if (!a) return n.void;
|
|
121
123
|
let { tenant: o, email: s } = t(r, i);
|
|
122
|
-
return P(() => a.deleteMany(M, { where: f(
|
|
124
|
+
return P(() => a.deleteMany(M, { where: f(m("tenant", o), m("email", s)) })).pipe(n.asVoid, n.catchAll(() => n.void));
|
|
123
125
|
},
|
|
124
126
|
bind: async (t) => {
|
|
125
127
|
e = t;
|
|
126
128
|
}
|
|
127
129
|
};
|
|
128
|
-
},
|
|
130
|
+
}, ge = d({ scope: "@voltro/plugin-mail" }), z = (e) => e ?? "*", B = () => {
|
|
129
131
|
let e = /* @__PURE__ */ new Map(), t = (e, t) => `${z(e)} ${t}`;
|
|
130
132
|
return {
|
|
131
133
|
reserve: (r, i) => n.sync(() => {
|
|
@@ -141,7 +143,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
141
143
|
}),
|
|
142
144
|
record: (r, i, a) => n.sync(() => void e.set(t(r, i), a))
|
|
143
145
|
};
|
|
144
|
-
},
|
|
146
|
+
}, _e = () => {
|
|
145
147
|
let e = null;
|
|
146
148
|
return {
|
|
147
149
|
reserve: (t, r) => {
|
|
@@ -160,7 +162,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
160
162
|
provider: r
|
|
161
163
|
}
|
|
162
164
|
} : { won: !1 };
|
|
163
|
-
}), n.catchAll((e) => n.sync(() => (
|
|
165
|
+
}), n.catchAll((e) => n.sync(() => (ge.warn("idempotency reserve failed — skipping send to avoid a double", {
|
|
164
166
|
key: r,
|
|
165
167
|
message: F(e)
|
|
166
168
|
}), { won: !1 })))) : n.succeed({ won: !0 });
|
|
@@ -170,7 +172,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
170
172
|
return a ? P(() => a.updateMany(N, {
|
|
171
173
|
resultId: i.id,
|
|
172
174
|
resultProvider: i.provider
|
|
173
|
-
}, { where: f(
|
|
175
|
+
}, { where: f(m("tenant", z(t)), m("key", r)) })).pipe(n.asVoid, n.catchAll((e) => n.sync(() => ge.warn("idempotency record failed", {
|
|
174
176
|
key: r,
|
|
175
177
|
message: F(e)
|
|
176
178
|
})))) : n.void;
|
|
@@ -179,7 +181,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
179
181
|
e = t;
|
|
180
182
|
}
|
|
181
183
|
};
|
|
182
|
-
},
|
|
184
|
+
}, ve = d({ scope: "@voltro/plugin-mail" }), V = () => {
|
|
183
185
|
let e = /* @__PURE__ */ new Map();
|
|
184
186
|
return {
|
|
185
187
|
hold: (t) => n.sync(() => void e.set(t.id, t)),
|
|
@@ -190,31 +192,31 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
190
192
|
}),
|
|
191
193
|
size: () => n.sync(() => e.size)
|
|
192
194
|
};
|
|
193
|
-
},
|
|
195
|
+
}, ye = 0, be = (e) => e.idempotencyKey ?? `held-${ye += 1}`, xe = (e) => ({
|
|
194
196
|
shouldHold: (t) => !e.providerSchedules && t !== void 0 && t.getTime() > Date.now(),
|
|
195
197
|
hold: (t) => {
|
|
196
198
|
let { scheduledAt: n, ...r } = t;
|
|
197
199
|
return e.store.hold({
|
|
198
|
-
id:
|
|
200
|
+
id: be(t),
|
|
199
201
|
dueAt: t.scheduledAt?.getTime() ?? Date.now(),
|
|
200
202
|
message: r
|
|
201
203
|
});
|
|
202
204
|
},
|
|
203
205
|
tick: (t = Date.now()) => n.gen(function* () {
|
|
204
206
|
let r = yield* e.store.claimDue(t);
|
|
205
|
-
for (let t of r) yield* e.flush(t.message).pipe(n.catchAll((e) => n.sync(() =>
|
|
207
|
+
for (let t of r) yield* e.flush(t.message).pipe(n.catchAll((e) => n.sync(() => ve.warn("scheduled flush failed", {
|
|
206
208
|
id: t.id,
|
|
207
209
|
message: String(e)
|
|
208
210
|
}))));
|
|
209
211
|
return r.length;
|
|
210
212
|
}),
|
|
211
213
|
pending: () => e.store.size()
|
|
212
|
-
}),
|
|
214
|
+
}), Se = 0, Ce = (e) => `${e}-${Se += 1}`, we = (e) => e === void 0 || e === 408 || e === 429 || e >= 500, H = (e, t) => {
|
|
213
215
|
let n = t.status;
|
|
214
|
-
return new
|
|
216
|
+
return new w({
|
|
215
217
|
provider: e,
|
|
216
218
|
message: (t.message ?? "send failed").slice(0, 500),
|
|
217
|
-
transient: e === "smtp" ? n === void 0 || n >= 400 && n < 500 :
|
|
219
|
+
transient: e === "smtp" ? n === void 0 || n >= 400 && n < 500 : we(n),
|
|
218
220
|
...n === void 0 ? {} : { status: n }
|
|
219
221
|
});
|
|
220
222
|
}, U = (e, t, r, i, a) => n.tryPromise({
|
|
@@ -243,14 +245,14 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
243
245
|
message: n.message ?? t?.message ?? String(t)
|
|
244
246
|
});
|
|
245
247
|
}
|
|
246
|
-
}), W = 500,
|
|
248
|
+
}), W = 500, Te = (e, t) => {
|
|
247
249
|
let n = e.stream === "broadcast" ? t?.broadcast ?? "broadcast" : t?.transactional;
|
|
248
250
|
return {
|
|
249
251
|
...n === void 0 ? {} : { route: n },
|
|
250
252
|
from: e.from,
|
|
251
|
-
to:
|
|
252
|
-
...e.cc ? { cc:
|
|
253
|
-
...e.bcc ? { bcc:
|
|
253
|
+
to: D(e.to),
|
|
254
|
+
...e.cc ? { cc: D(e.cc) } : {},
|
|
255
|
+
...e.bcc ? { bcc: D(e.bcc) } : {},
|
|
254
256
|
...e.replyTo ? { reply_to: [e.replyTo] } : {},
|
|
255
257
|
subject: e.subject,
|
|
256
258
|
...e.html === void 0 ? {} : { html: e.html },
|
|
@@ -268,10 +270,10 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
268
270
|
...e.contentType ? { content_type: e.contentType } : {}
|
|
269
271
|
})) } : {}
|
|
270
272
|
};
|
|
271
|
-
},
|
|
273
|
+
}, Ee = (e, t, r, i = Math.floor(Date.now() / 1e3), a = 300) => n.tryPromise({
|
|
272
274
|
try: async () => {
|
|
273
275
|
let n = (e) => {
|
|
274
|
-
throw new
|
|
276
|
+
throw new T({
|
|
275
277
|
provider: "lettermint",
|
|
276
278
|
message: e
|
|
277
279
|
});
|
|
@@ -291,11 +293,11 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
291
293
|
return n("body is not JSON");
|
|
292
294
|
}
|
|
293
295
|
},
|
|
294
|
-
catch: (e) => e instanceof
|
|
296
|
+
catch: (e) => e instanceof T ? e : new T({
|
|
295
297
|
provider: "lettermint",
|
|
296
298
|
message: e?.message ?? String(e)
|
|
297
299
|
})
|
|
298
|
-
}),
|
|
300
|
+
}), De = (e) => {
|
|
299
301
|
let t = (e.baseUrl ?? "https://api.lettermint.co/v1").replace(/\/+$/, ""), r = {
|
|
300
302
|
"x-lettermint-token": e.projectToken,
|
|
301
303
|
accept: "application/json"
|
|
@@ -307,28 +309,28 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
307
309
|
send: (n) => U("lettermint", `${t}/send`, {
|
|
308
310
|
...r,
|
|
309
311
|
...n.idempotencyKey === void 0 ? {} : { "idempotency-key": n.idempotencyKey }
|
|
310
|
-
},
|
|
312
|
+
}, Te(n, e.routes), async (e) => ({
|
|
311
313
|
id: (await e.json()).message_id ?? "lettermint",
|
|
312
314
|
provider: "lettermint"
|
|
313
315
|
})),
|
|
314
|
-
sendBatch: (i) => n.forEach(Array.from({ length: Math.ceil(i.length / W) }, (e, t) => i.slice(t * W, (t + 1) * W)), (n) => U("lettermint", `${t}/send/batch`, r, n.map((t) =>
|
|
316
|
+
sendBatch: (i) => n.forEach(Array.from({ length: Math.ceil(i.length / W) }, (e, t) => i.slice(t * W, (t + 1) * W)), (n) => U("lettermint", `${t}/send/batch`, r, n.map((t) => Te(t, e.routes)), async (e) => {
|
|
315
317
|
let t = await e.json();
|
|
316
318
|
return n.map((e, n) => ({
|
|
317
319
|
id: t[n]?.message_id ?? "lettermint",
|
|
318
320
|
provider: "lettermint"
|
|
319
321
|
}));
|
|
320
322
|
})).pipe(n.map((e) => e.flat())),
|
|
321
|
-
webhook: { verify:
|
|
323
|
+
webhook: { verify: Ee }
|
|
322
324
|
};
|
|
323
|
-
},
|
|
325
|
+
}, Oe = (e) => ({
|
|
324
326
|
from: e.from,
|
|
325
|
-
to:
|
|
327
|
+
to: D(e.to),
|
|
326
328
|
subject: e.subject,
|
|
327
329
|
html: e.html,
|
|
328
330
|
text: e.text,
|
|
329
331
|
reply_to: e.replyTo,
|
|
330
|
-
cc: e.cc ?
|
|
331
|
-
bcc: e.bcc ?
|
|
332
|
+
cc: e.cc ? D(e.cc) : void 0,
|
|
333
|
+
bcc: e.bcc ? D(e.bcc) : void 0,
|
|
332
334
|
headers: e.headers,
|
|
333
335
|
tags: e.tags?.map((e) => ({
|
|
334
336
|
name: e,
|
|
@@ -340,34 +342,34 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
340
342
|
content: e.content,
|
|
341
343
|
...e.contentType ? { content_type: e.contentType } : {}
|
|
342
344
|
}))
|
|
343
|
-
}),
|
|
345
|
+
}), ke = (e) => ({
|
|
344
346
|
name: "resend",
|
|
345
347
|
supportsScheduling: !0,
|
|
346
|
-
send: (t) => U("resend", "https://api.resend.com/emails", { authorization: `Bearer ${e.apiKey}` },
|
|
348
|
+
send: (t) => U("resend", "https://api.resend.com/emails", { authorization: `Bearer ${e.apiKey}` }, Oe(t), async (e) => ({
|
|
347
349
|
id: (await e.json()).id ?? "resend",
|
|
348
350
|
provider: "resend"
|
|
349
351
|
})),
|
|
350
|
-
sendBatch: (t) => U("resend", "https://api.resend.com/emails/batch", { authorization: `Bearer ${e.apiKey}` }, t.map(
|
|
352
|
+
sendBatch: (t) => U("resend", "https://api.resend.com/emails/batch", { authorization: `Bearer ${e.apiKey}` }, t.map(Oe), async (e) => {
|
|
351
353
|
let n = (await e.json()).data ?? [];
|
|
352
354
|
return t.map((e, t) => ({
|
|
353
355
|
id: n[t]?.id ?? "resend",
|
|
354
356
|
provider: "resend"
|
|
355
357
|
}));
|
|
356
358
|
})
|
|
357
|
-
}),
|
|
359
|
+
}), Ae = (e) => ({
|
|
358
360
|
name: "postmark",
|
|
359
361
|
send: (t) => U("postmark", "https://api.postmarkapp.com/email", {
|
|
360
362
|
"x-postmark-server-token": e.serverToken,
|
|
361
363
|
accept: "application/json"
|
|
362
364
|
}, {
|
|
363
365
|
From: t.from,
|
|
364
|
-
To:
|
|
366
|
+
To: D(t.to).join(","),
|
|
365
367
|
Subject: t.subject,
|
|
366
368
|
HtmlBody: t.html,
|
|
367
369
|
TextBody: t.text,
|
|
368
370
|
ReplyTo: t.replyTo,
|
|
369
|
-
Cc: t.cc ?
|
|
370
|
-
Bcc: t.bcc ?
|
|
371
|
+
Cc: t.cc ? D(t.cc).join(",") : void 0,
|
|
372
|
+
Bcc: t.bcc ? D(t.bcc).join(",") : void 0,
|
|
371
373
|
MessageStream: t.stream === "broadcast" ? e.streams?.broadcast ?? "broadcast" : e.streams?.transactional ?? "outbound",
|
|
372
374
|
Headers: t.headers ? Object.entries(t.headers).map(([e, t]) => ({
|
|
373
375
|
Name: e,
|
|
@@ -383,31 +385,31 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
383
385
|
id: (await e.json()).MessageID ?? "postmark",
|
|
384
386
|
provider: "postmark"
|
|
385
387
|
}))
|
|
386
|
-
}),
|
|
388
|
+
}), je = (e) => [...e.text ? [{
|
|
387
389
|
type: "text/plain",
|
|
388
390
|
value: e.text
|
|
389
391
|
}] : [], ...e.html ? [{
|
|
390
392
|
type: "text/html",
|
|
391
393
|
value: e.html
|
|
392
|
-
}] : []],
|
|
393
|
-
to:
|
|
394
|
-
cc: e.cc ?
|
|
395
|
-
bcc: e.bcc ?
|
|
394
|
+
}] : []], Me = (e) => ({
|
|
395
|
+
to: D(e.to).map((e) => ({ email: e })),
|
|
396
|
+
cc: e.cc ? D(e.cc).map((e) => ({ email: e })) : void 0,
|
|
397
|
+
bcc: e.bcc ? D(e.bcc).map((e) => ({ email: e })) : void 0,
|
|
396
398
|
subject: e.subject,
|
|
397
399
|
...e.scheduledAt ? { send_at: Math.floor(e.scheduledAt.getTime() / 1e3) } : {}
|
|
398
|
-
}),
|
|
400
|
+
}), Ne = (e) => e.attachments?.map((e) => ({
|
|
399
401
|
filename: e.filename,
|
|
400
402
|
content: e.content,
|
|
401
403
|
type: e.contentType ?? "application/octet-stream",
|
|
402
404
|
disposition: "attachment"
|
|
403
|
-
})),
|
|
405
|
+
})), Pe = (e) => {
|
|
404
406
|
let t = (e) => ({
|
|
405
|
-
personalizations: [
|
|
407
|
+
personalizations: [Me(e)],
|
|
406
408
|
from: { email: e.from },
|
|
407
409
|
reply_to: e.replyTo ? { email: e.replyTo } : void 0,
|
|
408
410
|
subject: e.subject,
|
|
409
|
-
content:
|
|
410
|
-
attachments:
|
|
411
|
+
content: je(e),
|
|
412
|
+
attachments: Ne(e)
|
|
411
413
|
});
|
|
412
414
|
return {
|
|
413
415
|
name: "sendgrid",
|
|
@@ -417,10 +419,10 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
417
419
|
provider: "sendgrid"
|
|
418
420
|
})),
|
|
419
421
|
sendBatch: (t) => U("sendgrid", "https://api.sendgrid.com/v3/mail/send", { authorization: `Bearer ${e.apiKey}` }, {
|
|
420
|
-
personalizations: t.map(
|
|
422
|
+
personalizations: t.map(Me),
|
|
421
423
|
from: { email: t[0]?.from },
|
|
422
424
|
subject: t[0]?.subject,
|
|
423
|
-
content:
|
|
425
|
+
content: je(t[0] ?? {})
|
|
424
426
|
}, async (e) => {
|
|
425
427
|
let n = e.headers.get("x-message-id") ?? "sendgrid";
|
|
426
428
|
return t.map((e, t) => ({
|
|
@@ -429,14 +431,14 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
429
431
|
}));
|
|
430
432
|
})
|
|
431
433
|
};
|
|
432
|
-
},
|
|
434
|
+
}, Fe = (e, t) => {
|
|
433
435
|
let n = t, r = n.response?.status;
|
|
434
436
|
return H(e, {
|
|
435
437
|
...typeof r == "number" ? { status: r } : {},
|
|
436
438
|
message: n.message ?? String(t)
|
|
437
439
|
});
|
|
438
|
-
},
|
|
439
|
-
let i = yield* (yield*
|
|
440
|
+
}, Ie = (e, t, r) => n.gen(function* () {
|
|
441
|
+
let i = yield* (yield* S.HttpClient).execute(t);
|
|
440
442
|
if (i.status < 200 || i.status >= 300) {
|
|
441
443
|
let t = yield* i.text.pipe(n.catchAll(() => n.succeed("")));
|
|
442
444
|
return yield* n.fail(H(e, {
|
|
@@ -445,19 +447,19 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
445
447
|
}));
|
|
446
448
|
}
|
|
447
449
|
return yield* r(i);
|
|
448
|
-
}).pipe(n.catchAll((t) => t instanceof
|
|
450
|
+
}).pipe(n.catchAll((t) => t instanceof w ? n.fail(t) : n.fail(Fe(e, t))), n.provide(te.layer)), Le = (e) => ({ sign: async (t, n, r) => {
|
|
449
451
|
let { createHash: i, createHmac: a } = await import("node:crypto"), o = (e) => i("sha256").update(e).digest("hex"), s = (e, t) => a("sha256", e).update(t).digest(), c = (/* @__PURE__ */ new Date()).toISOString().replace(/[:-]|\.\d{3}/g, ""), l = c.slice(0, 8), u = `host:${new URL(t).host}\nx-amz-date:${c}\n` + (e.sessionToken ? `x-amz-security-token:${e.sessionToken}\n` : ""), d = e.sessionToken ? "host;x-amz-date;x-amz-security-token" : "host;x-amz-date", f = `POST\n${n}\n\n${u}\n${d}\n${o(r)}`, p = `${l}/${e.region}/ses/aws4_request`, m = `AWS4-HMAC-SHA256\n${c}\n${p}\n${o(f)}`, h = a("sha256", s(s(s(s(`AWS4${e.secretAccessKey}`, l), e.region), "ses"), "aws4_request")).update(m).digest("hex");
|
|
450
452
|
return {
|
|
451
453
|
authorization: `AWS4-HMAC-SHA256 Credential=${e.accessKeyId}/${p}, SignedHeaders=${d}, Signature=${h}`,
|
|
452
454
|
"x-amz-date": c,
|
|
453
455
|
...e.sessionToken ? { "x-amz-security-token": e.sessionToken } : {}
|
|
454
456
|
};
|
|
455
|
-
} }),
|
|
457
|
+
} }), Re = (e) => JSON.stringify({
|
|
456
458
|
FromEmailAddress: e.from,
|
|
457
459
|
Destination: {
|
|
458
|
-
ToAddresses:
|
|
459
|
-
...e.cc ? { CcAddresses:
|
|
460
|
-
...e.bcc ? { BccAddresses:
|
|
460
|
+
ToAddresses: D(e.to),
|
|
461
|
+
...e.cc ? { CcAddresses: D(e.cc) } : {},
|
|
462
|
+
...e.bcc ? { BccAddresses: D(e.bcc) } : {}
|
|
461
463
|
},
|
|
462
464
|
...e.replyTo ? { ReplyToAddresses: [e.replyTo] } : {},
|
|
463
465
|
Content: { Simple: {
|
|
@@ -476,41 +478,41 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
476
478
|
} } : {}
|
|
477
479
|
}
|
|
478
480
|
} }
|
|
479
|
-
}),
|
|
480
|
-
let t =
|
|
481
|
+
}), ze = (e) => {
|
|
482
|
+
let t = Le(e), r = `https://email.${e.region}.amazonaws.com`, i = "/v2/email/outbound-emails";
|
|
481
483
|
return {
|
|
482
484
|
name: "ses",
|
|
483
485
|
send: (e) => n.gen(function* () {
|
|
484
|
-
let a =
|
|
486
|
+
let a = Re(e), o = yield* n.tryPromise({
|
|
485
487
|
try: () => t.sign(r, i, a),
|
|
486
488
|
catch: (e) => H("ses", { message: e?.message ?? String(e) })
|
|
487
489
|
});
|
|
488
|
-
return yield*
|
|
490
|
+
return yield* Ie("ses", C.post(`${r}${i}`).pipe(C.setHeaders({
|
|
489
491
|
"content-type": "application/json",
|
|
490
492
|
...o
|
|
491
|
-
}),
|
|
493
|
+
}), C.bodyUnsafeJson(JSON.parse(a))), (e) => e.json.pipe(n.map((e) => ({
|
|
492
494
|
id: e.MessageId ?? "ses",
|
|
493
495
|
provider: "ses"
|
|
494
496
|
}))));
|
|
495
497
|
})
|
|
496
498
|
};
|
|
497
|
-
},
|
|
499
|
+
}, Be = (e) => {
|
|
498
500
|
let t = [["from", e.from], ["subject", e.subject]];
|
|
499
|
-
for (let n of
|
|
500
|
-
if (e.cc) for (let n of
|
|
501
|
-
if (e.bcc) for (let n of
|
|
501
|
+
for (let n of D(e.to)) t.push(["to", n]);
|
|
502
|
+
if (e.cc) for (let n of D(e.cc)) t.push(["cc", n]);
|
|
503
|
+
if (e.bcc) for (let n of D(e.bcc)) t.push(["bcc", n]);
|
|
502
504
|
if (e.replyTo && t.push(["h:Reply-To", e.replyTo]), e.html && t.push(["html", e.html]), e.text && t.push(["text", e.text]), e.headers) for (let [n, r] of Object.entries(e.headers)) t.push([`h:${n}`, r]);
|
|
503
505
|
if (e.tags) for (let n of e.tags) t.push(["o:tag", n]);
|
|
504
506
|
return e.scheduledAt && t.push(["o:deliverytime", e.scheduledAt.toUTCString()]), t;
|
|
505
|
-
},
|
|
507
|
+
}, Ve = (e) => {
|
|
506
508
|
let t = `${e.baseUrl ?? "https://api.mailgun.net"}/v3/${e.domain}/messages`;
|
|
507
509
|
return {
|
|
508
510
|
name: "mailgun",
|
|
509
511
|
supportsScheduling: !0,
|
|
510
512
|
send: (r) => {
|
|
511
513
|
let i = new URLSearchParams();
|
|
512
|
-
for (let [e, t] of
|
|
513
|
-
return
|
|
514
|
+
for (let [e, t] of Be(r)) i.append(e, t);
|
|
515
|
+
return Ie("mailgun", C.post(t).pipe(C.setHeader("authorization", `Basic ${Buffer.from(`api:${e.apiKey}`).toString("base64")}`), C.bodyText(i.toString(), "application/x-www-form-urlencoded")), (e) => e.json.pipe(n.map((e) => ({
|
|
514
516
|
id: e.id ?? "mailgun",
|
|
515
517
|
provider: "mailgun"
|
|
516
518
|
}))));
|
|
@@ -535,9 +537,9 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
535
537
|
return {
|
|
536
538
|
id: (await t.sendMail({
|
|
537
539
|
from: r.from,
|
|
538
|
-
to:
|
|
539
|
-
cc: r.cc ?
|
|
540
|
-
bcc: r.bcc ?
|
|
540
|
+
to: D(r.to),
|
|
541
|
+
cc: r.cc ? D(r.cc) : void 0,
|
|
542
|
+
bcc: r.bcc ? D(r.bcc) : void 0,
|
|
541
543
|
replyTo: r.replyTo,
|
|
542
544
|
subject: r.subject,
|
|
543
545
|
html: r.html,
|
|
@@ -562,41 +564,41 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
562
564
|
}
|
|
563
565
|
})
|
|
564
566
|
};
|
|
565
|
-
},
|
|
567
|
+
}, He = (e) => ({
|
|
566
568
|
name: "console",
|
|
567
569
|
send: (t) => n.sync(() => {
|
|
568
|
-
let n = `✉ to=${
|
|
570
|
+
let n = `✉ to=${D(t.to).join(",")} subject=${JSON.stringify(t.subject)}`;
|
|
569
571
|
return e ? e(n, { from: t.from }) : process.stdout.write(`[mail:console] ${n}\n`), {
|
|
570
|
-
id:
|
|
572
|
+
id: Ce("console"),
|
|
571
573
|
provider: "console"
|
|
572
574
|
};
|
|
573
575
|
})
|
|
574
|
-
}), K = [],
|
|
576
|
+
}), K = [], Ue = () => [...K], We = () => {
|
|
575
577
|
K.length = 0;
|
|
576
|
-
},
|
|
578
|
+
}, Ge = () => ({
|
|
577
579
|
name: "memory",
|
|
578
580
|
send: (e) => n.sync(() => (K.push(e), {
|
|
579
|
-
id:
|
|
581
|
+
id: Ce("mem"),
|
|
580
582
|
provider: "memory"
|
|
581
583
|
}))
|
|
582
|
-
}), q = d({ scope: "@voltro/plugin-mail" }),
|
|
584
|
+
}), q = d({ scope: "@voltro/plugin-mail" }), Ke = (e) => {
|
|
583
585
|
if (e.provider !== void 0 && typeof e.provider != "string") return e.provider;
|
|
584
586
|
let t = e.env ?? {}, n = e.provider ?? t.MAIL_PROVIDER ?? "console", r = (e, t) => {
|
|
585
587
|
if (!t) throw Error(`@voltro/plugin-mail: provider '${n}' needs apiKey or ${e}`);
|
|
586
588
|
return t;
|
|
587
589
|
};
|
|
588
590
|
switch (n) {
|
|
589
|
-
case "lettermint": return
|
|
590
|
-
case "resend": return
|
|
591
|
-
case "postmark": return
|
|
592
|
-
case "sendgrid": return
|
|
593
|
-
case "ses": return
|
|
591
|
+
case "lettermint": return De({ projectToken: r("LETTERMINT_PROJECT_TOKEN", e.apiKey ?? t.LETTERMINT_PROJECT_TOKEN) });
|
|
592
|
+
case "resend": return ke({ apiKey: r("RESEND_API_KEY", e.apiKey ?? t.RESEND_API_KEY) });
|
|
593
|
+
case "postmark": return Ae({ serverToken: r("POSTMARK_SERVER_TOKEN", e.apiKey ?? t.POSTMARK_SERVER_TOKEN) });
|
|
594
|
+
case "sendgrid": return Pe({ apiKey: r("SENDGRID_API_KEY", e.apiKey ?? t.SENDGRID_API_KEY) });
|
|
595
|
+
case "ses": return ze({
|
|
594
596
|
region: r("AWS_REGION", t.AWS_REGION),
|
|
595
597
|
accessKeyId: r("AWS_ACCESS_KEY_ID", t.AWS_ACCESS_KEY_ID),
|
|
596
598
|
secretAccessKey: r("AWS_SECRET_ACCESS_KEY", t.AWS_SECRET_ACCESS_KEY),
|
|
597
599
|
...t.AWS_SESSION_TOKEN ? { sessionToken: t.AWS_SESSION_TOKEN } : {}
|
|
598
600
|
});
|
|
599
|
-
case "mailgun": return
|
|
601
|
+
case "mailgun": return Ve({
|
|
600
602
|
apiKey: r("MAILGUN_API_KEY", e.apiKey ?? t.MAILGUN_API_KEY),
|
|
601
603
|
domain: r("MAILGUN_DOMAIN", t.MAILGUN_DOMAIN),
|
|
602
604
|
...t.MAILGUN_BASE_URL ? { baseUrl: t.MAILGUN_BASE_URL } : {}
|
|
@@ -610,8 +612,8 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
610
612
|
pass: t.SMTP_PASS
|
|
611
613
|
} } : {}
|
|
612
614
|
});
|
|
613
|
-
case "memory": return
|
|
614
|
-
default: return
|
|
615
|
+
case "memory": return Ge();
|
|
616
|
+
default: return He((e, t) => q.info(e, t));
|
|
615
617
|
}
|
|
616
618
|
}, J = (e) => {
|
|
617
619
|
let t = e.attempts ?? 3, r = e.suppression ?? R(), i = e.allowlist, a = e.production !== !0 && i !== void 0 && i.length > 0, s = (e) => i.some((t) => t.toLowerCase() === e.toLowerCase()), c = o.recurs(t).pipe(o.whileInput((e) => e.transient)), l = /* @__PURE__ */ new Map();
|
|
@@ -621,14 +623,14 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
621
623
|
let e = d.keys().next().value;
|
|
622
624
|
e !== void 0 && d.delete(e);
|
|
623
625
|
}
|
|
624
|
-
}, m = e.idempotency ?? B(), h =
|
|
626
|
+
}, m = e.idempotency ?? B(), h = xe({
|
|
625
627
|
store: e.schedule ?? V(),
|
|
626
628
|
providerSchedules: e.provider.supportsScheduling === !0,
|
|
627
629
|
flush: (e) => v(e)
|
|
628
630
|
}), g = (t) => {
|
|
629
631
|
if (t.stream !== "broadcast" || e.provider.managesUnsubscribe === !0) return n.succeed(void 0);
|
|
630
632
|
let r = t.unsubscribe;
|
|
631
|
-
if (r === void 0 || !r.oneClickUrl.startsWith("https://")) return n.fail(new
|
|
633
|
+
if (r === void 0 || !r.oneClickUrl.startsWith("https://")) return n.fail(new w({
|
|
632
634
|
provider: e.provider.name,
|
|
633
635
|
message: `a broadcast needs \`unsubscribe: { oneClickUrl }\` (an https URL) — '${e.provider.name}' does not unsubscribe recipients itself`,
|
|
634
636
|
transient: !1
|
|
@@ -640,12 +642,12 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
640
642
|
});
|
|
641
643
|
}, _ = (t) => n.gen(function* () {
|
|
642
644
|
let i = t.from ?? e.from;
|
|
643
|
-
if (!i) return yield* n.fail(new
|
|
645
|
+
if (!i) return yield* n.fail(new w({
|
|
644
646
|
provider: e.provider.name,
|
|
645
647
|
message: "no `from` — set mailPlugin({ from }) or pass message.from",
|
|
646
648
|
transient: !1
|
|
647
649
|
}));
|
|
648
|
-
let o =
|
|
650
|
+
let o = D(t.to);
|
|
649
651
|
if (a) {
|
|
650
652
|
let n = o.filter(s);
|
|
651
653
|
if (n.length === 0) return q.warn("mail skipped — no recipient in allowlist (non-production)", {
|
|
@@ -704,7 +706,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
704
706
|
}
|
|
705
707
|
};
|
|
706
708
|
}), ee = (t) => {
|
|
707
|
-
let r =
|
|
709
|
+
let r = D(t.to);
|
|
708
710
|
return e.provider.send(t).pipe(n.retry(c), n.tap((i) => n.sync(() => {
|
|
709
711
|
q.info("mail sent", {
|
|
710
712
|
provider: i.provider,
|
|
@@ -742,7 +744,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
742
744
|
dueAt: t.scheduledAt?.toISOString()
|
|
743
745
|
}), e.onSent?.({
|
|
744
746
|
id: "scheduled",
|
|
745
|
-
to:
|
|
747
|
+
to: D(n.message.to),
|
|
746
748
|
from: n.message.from,
|
|
747
749
|
subject: n.message.subject,
|
|
748
750
|
provider: "scheduled",
|
|
@@ -762,7 +764,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
762
764
|
return yield* m.record(e, t, i), i;
|
|
763
765
|
}), b = (e) => n.suspend(() => {
|
|
764
766
|
let t = u(e.template, e.locale);
|
|
765
|
-
if (!t) return n.fail(new
|
|
767
|
+
if (!t) return n.fail(new w({
|
|
766
768
|
provider: "template",
|
|
767
769
|
message: `unknown template '${e.template}'${e.locale ? ` (locale '${e.locale}')` : ""}`,
|
|
768
770
|
transient: !1
|
|
@@ -808,10 +810,10 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
808
810
|
i && (q.info("mail sent (batch)", {
|
|
809
811
|
provider: i.provider,
|
|
810
812
|
id: i.id,
|
|
811
|
-
to:
|
|
813
|
+
to: D(t.to).join(",")
|
|
812
814
|
}), e.onSent?.({
|
|
813
815
|
id: i.id,
|
|
814
|
-
to:
|
|
816
|
+
to: D(t.to),
|
|
815
817
|
from: t.from,
|
|
816
818
|
subject: t.subject,
|
|
817
819
|
provider: i.provider,
|
|
@@ -841,7 +843,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
841
843
|
},
|
|
842
844
|
scheduler: h
|
|
843
845
|
};
|
|
844
|
-
},
|
|
846
|
+
}, qe = (e) => J(e).service, Je = (e) => i.succeed(E, qe(e)), Y = (e, t, n, r, i, a = {}) => {
|
|
845
847
|
if (!t) return null;
|
|
846
848
|
let o = a.metadata !== null && typeof a.metadata == "object" ? Object.fromEntries(Object.entries(a.metadata).filter(([, e]) => typeof e == "string")) : void 0;
|
|
847
849
|
return {
|
|
@@ -853,36 +855,36 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
853
855
|
...a.eventId === void 0 ? {} : { eventId: a.eventId },
|
|
854
856
|
...o === void 0 ? {} : { metadata: o }
|
|
855
857
|
};
|
|
856
|
-
},
|
|
858
|
+
}, Ye = (e) => e === "message.hard_bounced" ? "bounced" : e === "message.spam_complaint" ? "complained" : e === "message.unsubscribed" ? "unsubscribed" : e === "message.delivered" ? "delivered" : e === "message.opened" ? "opened" : e === "message.clicked" ? "clicked" : "unknown", Xe = (e) => e === "hard_bounce" ? "bounced" : e === "spam_complaint" ? "complained" : e === "unsubscribe" ? "unsubscribed" : "unknown", Ze = (e) => e === "email.bounced" ? "bounced" : e === "email.complained" ? "complained" : e === "email.delivered" ? "delivered" : e === "email.opened" ? "opened" : e === "email.clicked" ? "clicked" : "unknown", Qe = (e) => e === "Bounce" ? "bounced" : e === "SpamComplaint" ? "complained" : e === "Delivery" ? "delivered" : e === "Open" ? "opened" : e === "Click" ? "clicked" : "unknown", $e = (e) => e === "bounce" || e === "dropped" ? "bounced" : e === "spamreport" ? "complained" : e === "delivered" ? "delivered" : e === "open" ? "opened" : e === "click" ? "clicked" : "unknown", X = (e, t) => {
|
|
857
859
|
let n = [], r = (e) => {
|
|
858
860
|
e && n.push(e);
|
|
859
861
|
};
|
|
860
862
|
if (e === "sendgrid" && Array.isArray(t)) {
|
|
861
|
-
for (let e of t) r(Y(
|
|
863
|
+
for (let e of t) r(Y($e(String(e.event)), e.email, "sendgrid", e.sg_message_id, e));
|
|
862
864
|
return n;
|
|
863
865
|
}
|
|
864
866
|
let i = t ?? {};
|
|
865
867
|
if (e === "lettermint") {
|
|
866
868
|
let e = String(i.event), a = i.data ?? {}, o = typeof i.id == "string" ? i.id : void 0;
|
|
867
|
-
return e === "suppression.added" ? (a.type === "email" && r(Y(
|
|
869
|
+
return e === "suppression.added" ? (a.type === "email" && r(Y(Xe(String(a.reason)), a.value, "lettermint", void 0, t, { eventId: o })), n) : e === "suppression.removed" ? (a.type === "email" && r(Y("unsuppressed", a.value, "lettermint", void 0, t, { eventId: o })), n) : (e.startsWith("message.") && r(Y(Ye(e), a.recipient, "lettermint", a.message_id, t, {
|
|
868
870
|
eventId: o,
|
|
869
871
|
metadata: a.metadata
|
|
870
872
|
})), n);
|
|
871
873
|
}
|
|
872
874
|
if (e === "resend") {
|
|
873
875
|
let e = i.data ?? {}, a = Array.isArray(e.to) ? e.to[0] : e.to ?? e.email;
|
|
874
|
-
return r(Y(
|
|
876
|
+
return r(Y(Ze(String(i.type)), a, "resend", e.email_id, t)), n;
|
|
875
877
|
}
|
|
876
878
|
if (e === "postmark") {
|
|
877
879
|
let e = i.Email ?? i.Recipient;
|
|
878
|
-
return r(Y(
|
|
880
|
+
return r(Y(Qe(String(i.RecordType)), e, "postmark", i.MessageID, t, { metadata: i.Metadata })), n;
|
|
879
881
|
}
|
|
880
882
|
return n;
|
|
881
|
-
},
|
|
883
|
+
}, et = (e, t, r, i = null) => n.gen(function* () {
|
|
882
884
|
let n = X(e, t);
|
|
883
885
|
for (let e of n) e.type === "bounced" || e.type === "complained" ? yield* r.suppress(i, e.email, e.type) : e.type === "unsuppressed" && (yield* r.unsuppress(i, e.email));
|
|
884
886
|
return n;
|
|
885
|
-
}),
|
|
887
|
+
}), tt = (e = 200) => {
|
|
886
888
|
let t = [];
|
|
887
889
|
return {
|
|
888
890
|
record: (n) => {
|
|
@@ -893,18 +895,18 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
893
895
|
t.length = 0;
|
|
894
896
|
}
|
|
895
897
|
};
|
|
896
|
-
},
|
|
898
|
+
}, nt = {
|
|
897
899
|
lettermint: "api.lettermint.co",
|
|
898
900
|
resend: "api.resend.com",
|
|
899
901
|
postmark: "api.postmarkapp.com",
|
|
900
902
|
sendgrid: "api.sendgrid.com"
|
|
901
|
-
},
|
|
903
|
+
}, rt = (e) => {
|
|
902
904
|
try {
|
|
903
905
|
return new URL(e).host || void 0;
|
|
904
906
|
} catch {
|
|
905
907
|
return;
|
|
906
908
|
}
|
|
907
|
-
},
|
|
909
|
+
}, it = (e) => {
|
|
908
910
|
let t = e.env ?? {}, n = (e) => [{
|
|
909
911
|
destination: e,
|
|
910
912
|
purpose: "outgoing email: recipients, subject and body",
|
|
@@ -915,12 +917,12 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
915
917
|
let t = e.provider.name;
|
|
916
918
|
return t === "console" || t === "memory" ? [] : n(`runtime:the '${t}' mail provider`);
|
|
917
919
|
}
|
|
918
|
-
let r = e.provider ?? t.MAIL_PROVIDER ?? "console", i =
|
|
920
|
+
let r = e.provider ?? t.MAIL_PROVIDER ?? "console", i = nt[r];
|
|
919
921
|
if (i !== void 0) return n(i);
|
|
920
922
|
switch (r) {
|
|
921
923
|
case "ses": return n(t.AWS_REGION ? `email.${t.AWS_REGION}.amazonaws.com` : "runtime:email.<AWS_REGION>.amazonaws.com");
|
|
922
|
-
case "mailgun": return n((t.MAILGUN_BASE_URL ?
|
|
923
|
-
case "smtp": return n((t.SMTP_URL ?
|
|
924
|
+
case "mailgun": return n((t.MAILGUN_BASE_URL ? rt(t.MAILGUN_BASE_URL) : void 0) ?? "api.mailgun.net");
|
|
925
|
+
case "smtp": return n((t.SMTP_URL ? rt(t.SMTP_URL) : void 0) ?? (t.SMTP_HOST ? `${t.SMTP_HOST}${t.SMTP_PORT ? `:${t.SMTP_PORT}` : ""}` : void 0) ?? "runtime:SMTP_URL or SMTP_HOST");
|
|
924
926
|
default: return [];
|
|
925
927
|
}
|
|
926
928
|
}, Z = (e, t) => {
|
|
@@ -943,7 +945,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
943
945
|
case "Suspend": return Z(e.f(), t + 1);
|
|
944
946
|
default: return null;
|
|
945
947
|
}
|
|
946
|
-
},
|
|
948
|
+
}, at = (e) => Z(e.ast, 0), Q = u([
|
|
947
949
|
{
|
|
948
950
|
name: "MAIL_PROVIDER",
|
|
949
951
|
required: !1,
|
|
@@ -1064,11 +1066,11 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
1064
1066
|
secret: !1,
|
|
1065
1067
|
description: "Comma-separated recipient allowlist; in dev, mail to addresses outside it is skipped."
|
|
1066
1068
|
}
|
|
1067
|
-
]),
|
|
1069
|
+
]), ot = "@voltro/plugin-mail", $ = (e, t) => ({
|
|
1068
1070
|
status: e,
|
|
1069
1071
|
contentType: "application/json",
|
|
1070
1072
|
body: JSON.stringify(t)
|
|
1071
|
-
}),
|
|
1073
|
+
}), st = (e, t, r, i) => {
|
|
1072
1074
|
let a = e.webhook;
|
|
1073
1075
|
if (a === void 0) throw Error(`@voltro/plugin-mail: provider '${e.name}' has no webhook verifier — mailPlugin({ webhook }) needs one (lettermint)`);
|
|
1074
1076
|
let o = r.secret ?? Q.read("MAIL_WEBHOOK_SECRET");
|
|
@@ -1096,46 +1098,46 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
1096
1098
|
return $(200, { received: c.length });
|
|
1097
1099
|
}
|
|
1098
1100
|
};
|
|
1099
|
-
},
|
|
1100
|
-
let a = process.env, s =
|
|
1101
|
+
}, ct = (e = {}) => {
|
|
1102
|
+
let a = process.env, s = Ke({
|
|
1101
1103
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
1102
1104
|
...e.apiKey === void 0 ? {} : { apiKey: e.apiKey },
|
|
1103
1105
|
env: a
|
|
1104
|
-
}), u = Q.read("MAIL_ALLOWLIST"), d = e.allowlist ?? (u ? u.split(",").map((e) => e.trim()).filter(Boolean) : void 0), f = a.NODE_ENV === "production", p = e.suppression === void 0 || e.suppression === "memory" ? R() : e.suppression === "postgres" ?
|
|
1105
|
-
e.idempotency === "postgres" &&
|
|
1106
|
+
}), u = Q.read("MAIL_ALLOWLIST"), d = e.allowlist ?? (u ? u.split(",").map((e) => e.trim()).filter(Boolean) : void 0), f = a.NODE_ENV === "production", p = e.suppression === void 0 || e.suppression === "memory" ? R() : e.suppression === "postgres" ? he() : e.suppression, m = e.idempotency === void 0 || e.idempotency === "memory" ? B() : e.idempotency === "postgres" ? _e() : e.idempotency, h = e.schedule ?? V(), g = [...e.suppression === "postgres" ? [de] : [], ...e.idempotency === "postgres" ? [fe] : []];
|
|
1107
|
+
e.idempotency === "postgres" && ee({
|
|
1106
1108
|
source: "plugin",
|
|
1107
1109
|
table: N,
|
|
1108
1110
|
timeColumn: "createdAt",
|
|
1109
|
-
ttlMs:
|
|
1111
|
+
ttlMs: v(process.env.VOLTRO_MAIL_IDEMPOTENCY_TTL_HOURS, 720),
|
|
1110
1112
|
label: "mail idempotency keys"
|
|
1111
1113
|
});
|
|
1112
|
-
let
|
|
1114
|
+
let _ = tt(), y = J({
|
|
1113
1115
|
provider: s,
|
|
1114
1116
|
suppression: p,
|
|
1115
1117
|
idempotency: m,
|
|
1116
1118
|
schedule: h,
|
|
1117
|
-
onSent:
|
|
1119
|
+
onSent: _.record,
|
|
1118
1120
|
...e.from === void 0 ? {} : { from: e.from },
|
|
1119
1121
|
...e.replyTo === void 0 ? {} : { replyTo: e.replyTo },
|
|
1120
1122
|
...d === void 0 ? {} : { allowlist: d },
|
|
1121
1123
|
...e.attempts === void 0 ? {} : { attempts: e.attempts },
|
|
1122
1124
|
...e.templates === void 0 ? {} : { templates: e.templates },
|
|
1123
1125
|
production: f
|
|
1124
|
-
}), b = i.succeed(
|
|
1126
|
+
}), b = i.succeed(E, y.service), x = s.supportsScheduling !== !0, te = e.scheduleTickMs ?? 1e3, S, C = /* @__PURE__ */ new Map();
|
|
1125
1127
|
for (let t of e.templates ?? []) C.set(t.locale ? `${t.name}:${t.locale}` : t.name, t);
|
|
1126
1128
|
let w = (e, t) => (t ? C.get(`${e}:${t}`) : void 0) ?? C.get(e) ?? j(e, t), T = (e) => ({
|
|
1127
1129
|
name: e.name,
|
|
1128
1130
|
...e.locale ? { locale: e.locale } : {},
|
|
1129
|
-
props: e.preview ??
|
|
1130
|
-
}),
|
|
1131
|
+
props: e.preview ?? at(e.props)
|
|
1132
|
+
}), ne = () => {
|
|
1131
1133
|
let t = /* @__PURE__ */ new Map();
|
|
1132
1134
|
for (let n of e.templates ?? []) t.set(n.locale ? `${n.name}:${n.locale}` : n.name, T(n));
|
|
1133
|
-
for (let e of
|
|
1135
|
+
for (let e of ce()) {
|
|
1134
1136
|
let n = j(e.name, e.locale);
|
|
1135
1137
|
n && t.set(e.locale ? `${e.name}:${e.locale}` : e.name, T(n));
|
|
1136
1138
|
}
|
|
1137
1139
|
return [...t.values()];
|
|
1138
|
-
}, D = e.webhook === void 0 ? [] : [
|
|
1140
|
+
}, D = e.webhook === void 0 ? [] : [st(s, p, e.webhook, f)], re = [
|
|
1139
1141
|
{
|
|
1140
1142
|
method: "GET",
|
|
1141
1143
|
path: "/outbox",
|
|
@@ -1143,7 +1145,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
1143
1145
|
description: "Recent sent mail (dev capture buffer), newest first",
|
|
1144
1146
|
handler: () => n.succeed({
|
|
1145
1147
|
kind: "json",
|
|
1146
|
-
data: { sends:
|
|
1148
|
+
data: { sends: _.list() }
|
|
1147
1149
|
})
|
|
1148
1150
|
},
|
|
1149
1151
|
{
|
|
@@ -1153,7 +1155,7 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
1153
1155
|
description: "Discovered + registered email templates",
|
|
1154
1156
|
handler: () => n.succeed({
|
|
1155
1157
|
kind: "json",
|
|
1156
|
-
data: { templates:
|
|
1158
|
+
data: { templates: ne() }
|
|
1157
1159
|
})
|
|
1158
1160
|
},
|
|
1159
1161
|
{
|
|
@@ -1191,28 +1193,29 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
1191
1193
|
};
|
|
1192
1194
|
})
|
|
1193
1195
|
}
|
|
1196
|
+
], O = [
|
|
1197
|
+
"network:outbound:*",
|
|
1198
|
+
"inspect:read",
|
|
1199
|
+
"inspect:write",
|
|
1200
|
+
...g.length > 0 ? ["store:write"] : []
|
|
1194
1201
|
];
|
|
1195
1202
|
return {
|
|
1196
1203
|
...c({
|
|
1197
1204
|
name: l({
|
|
1198
|
-
base:
|
|
1205
|
+
base: ot,
|
|
1199
1206
|
alias: e.alias,
|
|
1200
1207
|
instance: e.name
|
|
1201
1208
|
}),
|
|
1202
|
-
baseName:
|
|
1209
|
+
baseName: ot,
|
|
1203
1210
|
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.",
|
|
1204
|
-
permissions:
|
|
1205
|
-
"network:outbound:*",
|
|
1206
|
-
"inspect:read",
|
|
1207
|
-
"inspect:write"
|
|
1208
|
-
],
|
|
1211
|
+
permissions: O,
|
|
1209
1212
|
declaredEnv: Q.declared,
|
|
1210
|
-
egress:
|
|
1213
|
+
egress: it({
|
|
1211
1214
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
1212
1215
|
env: a
|
|
1213
1216
|
}),
|
|
1214
1217
|
services: b,
|
|
1215
|
-
inspectEndpoints:
|
|
1218
|
+
inspectEndpoints: re,
|
|
1216
1219
|
...D.length > 0 ? { httpRoutes: D } : {},
|
|
1217
1220
|
...g.length > 0 ? { extendSchema: { tables: g } } : {},
|
|
1218
1221
|
bindDataStore: async (e) => {
|
|
@@ -1241,4 +1244,4 @@ var C = class extends s.TaggedError()("MailError", {
|
|
|
1241
1244
|
};
|
|
1242
1245
|
};
|
|
1243
1246
|
//#endregion
|
|
1244
|
-
export {
|
|
1247
|
+
export { w as MailError, E as MailService, T as MailWebhookError, qe as buildMailService, J as buildMailServiceWithScheduler, le as clearGlobalTemplates, We as clearMailBuffer, He as consoleProvider, tt as createOutbox, ne as defineEmail, se as globalTemplateCount, et as handleMailEvents, be as heldId, ue as isEmailTemplate, De as lettermintProvider, ce as listGlobalTemplates, j as lookupGlobalTemplate, it as mailEgressOf, ct as mailPlugin, Je as mailServiceLayer, Ve as mailgunProvider, xe as makeScheduler, B as memoryIdempotencyStore, Ge as memoryProvider, V as memoryScheduleStore, R as memorySuppressionStore, X as parseMailEvent, _e as postgresIdempotencyStore, he as postgresSuppressionStore, Ae as postmarkProvider, Ue as readMailBuffer, oe as registerEmailTemplates, k as renderTemplate, ke as resendProvider, Ke as resolveProvider, Pe as sendgridProvider, ze as sesProvider, G as smtpProvider, Ee as verifyLettermintSignature };
|