@voltro/plugin-mail 0.84.0 → 0.86.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 +1215 -0
- package/THIRD-PARTY-NOTICES.md +108 -736
- package/dist/egress.js +34 -0
- package/dist/events.js +46 -0
- package/dist/idempotency.js +62 -0
- package/dist/index.d.ts +15 -18
- package/dist/index.js +97 -1026
- package/dist/outbox.js +15 -0
- package/dist/providers.js +377 -0
- package/dist/registry.js +13 -0
- package/dist/render.js +44 -0
- package/dist/scheduler.js +36 -0
- package/dist/service.js +279 -0
- package/dist/skeleton.js +24 -0
- package/dist/storeTables.js +28 -0
- package/dist/suppression.js +46 -0
- package/dist/types.js +20 -0
- package/package.json +6 -8
package/dist/index.js
CHANGED
|
@@ -1,951 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
render: e.render,
|
|
21
|
-
...e.locale === void 0 ? {} : { locale: e.locale },
|
|
22
|
-
...e.preview === void 0 ? {} : { preview: e.preview }
|
|
23
|
-
}), E = class extends e.Tag("@voltro/plugin-mail/MailService")() {}, D = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, re = (e) => {
|
|
24
|
-
try {
|
|
25
|
-
let t = a.ArrayFormatter.formatErrorSync(e);
|
|
26
|
-
return t.length === 0 ? String(e) : t.map((e) => `${e.path.length > 0 ? e.path.join(".") : "(root)"}: ${e.message}`).join("; ");
|
|
27
|
-
} catch {
|
|
28
|
-
return String(e);
|
|
29
|
-
}
|
|
30
|
-
}, O = null, ie = async () => {
|
|
31
|
-
if (!O) {
|
|
32
|
-
let e = await import("@react-email/render");
|
|
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
|
-
}
|
|
35
|
-
return O;
|
|
36
|
-
}, k = (e, t) => n.gen(function* () {
|
|
37
|
-
let r = e, i = yield* s.decodeUnknown(r.props)(t).pipe(n.mapError((t) => new w({
|
|
38
|
-
provider: "template",
|
|
39
|
-
message: `invalid props for '${e.name}': ${re(t)}`.slice(0, 500),
|
|
40
|
-
transient: !1
|
|
41
|
-
}))), a = r.subject(i), o = r.render(i);
|
|
42
|
-
return {
|
|
43
|
-
subject: a,
|
|
44
|
-
html: yield* n.tryPromise({
|
|
45
|
-
try: async () => (await ie())(o, { plainText: !1 }),
|
|
46
|
-
catch: (t) => new w({
|
|
47
|
-
provider: "template",
|
|
48
|
-
message: `render failed for '${e.name}': ${String(t)}`.slice(0, 500),
|
|
49
|
-
transient: !1
|
|
50
|
-
})
|
|
51
|
-
}),
|
|
52
|
-
text: yield* n.tryPromise({
|
|
53
|
-
try: async () => (await ie())(o, { plainText: !0 }),
|
|
54
|
-
catch: (t) => new w({
|
|
55
|
-
provider: "template",
|
|
56
|
-
message: `render(text) failed for '${e.name}': ${String(t)}`.slice(0, 500),
|
|
57
|
-
transient: !1
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
};
|
|
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
|
-
name: e.name,
|
|
65
|
-
locale: e.locale
|
|
66
|
-
} : { name: e.name }), le = () => A.clear(), ue = (e) => {
|
|
67
|
-
if (typeof e != "object" || !e) return !1;
|
|
68
|
-
let t = e;
|
|
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", 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()
|
|
85
|
-
}).unique("mailIdempotencyKey", ["tenant", "key"]).nonReactive(), P = (e) => n.tryPromise({
|
|
86
|
-
try: e,
|
|
87
|
-
catch: (e) => e
|
|
88
|
-
}), F = (e) => {
|
|
89
|
-
let t = [];
|
|
90
|
-
for (let n = e, r = 0; n != null && r < 4; n = n.cause, r++) t.push(n instanceof Error ? n.message : String(n));
|
|
91
|
-
return t.join(" ← ");
|
|
92
|
-
}, pe = d({ scope: "@voltro/plugin-mail" }), I = (e) => e.trim().toLowerCase(), me = "*", L = (e) => e ?? me, R = () => {
|
|
93
|
-
let e = /* @__PURE__ */ new Set(), t = (e, t) => `${L(e)}\u0000${I(t)}`;
|
|
94
|
-
return {
|
|
95
|
-
isSuppressed: (r, i) => n.sync(() => e.has(t(r, i)) || e.has(t(null, i))),
|
|
96
|
-
suppress: (r, i) => n.sync(() => void e.add(t(r, i))),
|
|
97
|
-
unsuppress: (r, i) => n.sync(() => void e.delete(t(r, i)))
|
|
98
|
-
};
|
|
99
|
-
}, he = () => {
|
|
100
|
-
let e = null, t = (e, t) => ({
|
|
101
|
-
tenant: L(e),
|
|
102
|
-
email: I(t)
|
|
103
|
-
});
|
|
104
|
-
return {
|
|
105
|
-
isSuppressed: (t, r) => {
|
|
106
|
-
let i = e;
|
|
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);
|
|
108
|
-
},
|
|
109
|
-
suppress: (r, i, a) => {
|
|
110
|
-
let o = e;
|
|
111
|
-
return o ? P(() => o.upsert(M, {
|
|
112
|
-
...t(r, i),
|
|
113
|
-
reason: a,
|
|
114
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
115
|
-
}, { conflictColumns: ["tenant", "email"] })).pipe(n.asVoid, n.catchAll((e) => n.sync(() => pe.warn("suppress write failed", {
|
|
116
|
-
email: I(i),
|
|
117
|
-
message: F(e)
|
|
118
|
-
})))) : n.void;
|
|
119
|
-
},
|
|
120
|
-
unsuppress: (r, i) => {
|
|
121
|
-
let a = e;
|
|
122
|
-
if (!a) return n.void;
|
|
123
|
-
let { tenant: o, email: s } = t(r, i);
|
|
124
|
-
return P(() => a.deleteMany(M, { where: f(m("tenant", o), m("email", s)) })).pipe(n.asVoid, n.catchAll(() => n.void));
|
|
125
|
-
},
|
|
126
|
-
bind: async (t) => {
|
|
127
|
-
e = t;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}, ge = d({ scope: "@voltro/plugin-mail" }), z = (e) => e ?? "*", B = () => {
|
|
131
|
-
let e = /* @__PURE__ */ new Map(), t = (e, t) => `${z(e)} ${t}`;
|
|
132
|
-
return {
|
|
133
|
-
reserve: (r, i) => n.sync(() => {
|
|
134
|
-
let n = t(r, i);
|
|
135
|
-
if (e.has(n)) {
|
|
136
|
-
let t = e.get(n);
|
|
137
|
-
return t ? {
|
|
138
|
-
won: !1,
|
|
139
|
-
prior: t
|
|
140
|
-
} : { won: !1 };
|
|
141
|
-
}
|
|
142
|
-
return e.set(n, null), { won: !0 };
|
|
143
|
-
}),
|
|
144
|
-
record: (r, i, a) => n.sync(() => void e.set(t(r, i), a))
|
|
145
|
-
};
|
|
146
|
-
}, _e = () => {
|
|
147
|
-
let e = null;
|
|
148
|
-
return {
|
|
149
|
-
reserve: (t, r) => {
|
|
150
|
-
let i = e;
|
|
151
|
-
return i ? P(() => i.insertIgnoreWithOutcome(N, {
|
|
152
|
-
tenant: z(t),
|
|
153
|
-
key: r,
|
|
154
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
155
|
-
}, { conflictColumns: ["tenant", "key"] })).pipe(n.map(({ row: e, outcome: t }) => {
|
|
156
|
-
if (t === "inserted") return { won: !0 };
|
|
157
|
-
let n = e.resultId, r = e.resultProvider;
|
|
158
|
-
return typeof n == "string" && typeof r == "string" ? {
|
|
159
|
-
won: !1,
|
|
160
|
-
prior: {
|
|
161
|
-
id: n,
|
|
162
|
-
provider: r
|
|
163
|
-
}
|
|
164
|
-
} : { won: !1 };
|
|
165
|
-
}), n.catchAll((e) => n.sync(() => (ge.warn("idempotency reserve failed — skipping send to avoid a double", {
|
|
166
|
-
key: r,
|
|
167
|
-
message: F(e)
|
|
168
|
-
}), { won: !1 })))) : n.succeed({ won: !0 });
|
|
169
|
-
},
|
|
170
|
-
record: (t, r, i) => {
|
|
171
|
-
let a = e;
|
|
172
|
-
return a ? P(() => a.updateMany(N, {
|
|
173
|
-
resultId: i.id,
|
|
174
|
-
resultProvider: i.provider
|
|
175
|
-
}, { where: f(m("tenant", z(t)), m("key", r)) })).pipe(n.asVoid, n.catchAll((e) => n.sync(() => ge.warn("idempotency record failed", {
|
|
176
|
-
key: r,
|
|
177
|
-
message: F(e)
|
|
178
|
-
})))) : n.void;
|
|
179
|
-
},
|
|
180
|
-
bind: async (t) => {
|
|
181
|
-
e = t;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}, ve = d({ scope: "@voltro/plugin-mail" }), V = () => {
|
|
185
|
-
let e = /* @__PURE__ */ new Map();
|
|
186
|
-
return {
|
|
187
|
-
hold: (t) => n.sync(() => void e.set(t.id, t)),
|
|
188
|
-
claimDue: (t) => n.sync(() => {
|
|
189
|
-
let n = [];
|
|
190
|
-
for (let [r, i] of e) i.dueAt <= t && (n.push(i), e.delete(r));
|
|
191
|
-
return n;
|
|
192
|
-
}),
|
|
193
|
-
size: () => n.sync(() => e.size)
|
|
194
|
-
};
|
|
195
|
-
}, ye = 0, be = (e) => e.idempotencyKey ?? `held-${ye += 1}`, xe = (e) => ({
|
|
196
|
-
shouldHold: (t) => !e.providerSchedules && t !== void 0 && t.getTime() > Date.now(),
|
|
197
|
-
hold: (t) => {
|
|
198
|
-
let { scheduledAt: n, ...r } = t;
|
|
199
|
-
return e.store.hold({
|
|
200
|
-
id: be(t),
|
|
201
|
-
dueAt: t.scheduledAt?.getTime() ?? Date.now(),
|
|
202
|
-
message: r
|
|
203
|
-
});
|
|
204
|
-
},
|
|
205
|
-
tick: (t = Date.now()) => n.gen(function* () {
|
|
206
|
-
let r = yield* e.store.claimDue(t);
|
|
207
|
-
for (let t of r) yield* e.flush(t.message).pipe(n.catchAll((e) => n.sync(() => ve.warn("scheduled flush failed", {
|
|
208
|
-
id: t.id,
|
|
209
|
-
message: String(e)
|
|
210
|
-
}))));
|
|
211
|
-
return r.length;
|
|
212
|
-
}),
|
|
213
|
-
pending: () => e.store.size()
|
|
214
|
-
}), Se = 0, Ce = (e) => `${e}-${Se += 1}`, we = (e) => e === void 0 || e === 408 || e === 429 || e >= 500, H = (e, t) => {
|
|
215
|
-
let n = t.status;
|
|
216
|
-
return new w({
|
|
217
|
-
provider: e,
|
|
218
|
-
message: (t.message ?? "send failed").slice(0, 500),
|
|
219
|
-
transient: e === "smtp" ? n === void 0 || n >= 400 && n < 500 : we(n),
|
|
220
|
-
...n === void 0 ? {} : { status: n }
|
|
221
|
-
});
|
|
222
|
-
}, U = (e, t, r, i, a) => n.tryPromise({
|
|
223
|
-
try: async () => {
|
|
224
|
-
let e = await fetch(t, {
|
|
225
|
-
method: "POST",
|
|
226
|
-
headers: {
|
|
227
|
-
"content-type": "application/json",
|
|
228
|
-
...r
|
|
229
|
-
},
|
|
230
|
-
body: JSON.stringify(i)
|
|
231
|
-
});
|
|
232
|
-
if (!e.ok) {
|
|
233
|
-
let t = await e.text().catch(() => "");
|
|
234
|
-
throw {
|
|
235
|
-
status: e.status,
|
|
236
|
-
message: t || `HTTP ${e.status}`
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
return await a(e);
|
|
240
|
-
},
|
|
241
|
-
catch: (t) => {
|
|
242
|
-
let n = t;
|
|
243
|
-
return H(e, {
|
|
244
|
-
...typeof n.status == "number" ? { status: n.status } : {},
|
|
245
|
-
message: n.message ?? t?.message ?? String(t)
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}), W = 500, Te = (e, t) => {
|
|
249
|
-
let n = e.stream === "broadcast" ? t?.broadcast ?? "broadcast" : t?.transactional;
|
|
250
|
-
return {
|
|
251
|
-
...n === void 0 ? {} : { route: n },
|
|
252
|
-
from: e.from,
|
|
253
|
-
to: D(e.to),
|
|
254
|
-
...e.cc ? { cc: D(e.cc) } : {},
|
|
255
|
-
...e.bcc ? { bcc: D(e.bcc) } : {},
|
|
256
|
-
...e.replyTo ? { reply_to: [e.replyTo] } : {},
|
|
257
|
-
subject: e.subject,
|
|
258
|
-
...e.html === void 0 ? {} : { html: e.html },
|
|
259
|
-
...e.text === void 0 ? {} : { text: e.text },
|
|
260
|
-
...e.headers ? { headers: e.headers } : {},
|
|
261
|
-
...e.metadata ? { metadata: e.metadata } : {},
|
|
262
|
-
...e.tags && e.tags.length > 0 ? { tags: e.tags.map((e) => ({
|
|
263
|
-
name: e,
|
|
264
|
-
value: "1"
|
|
265
|
-
})) } : {},
|
|
266
|
-
...e.scheduledAt ? { scheduled_at: e.scheduledAt.toISOString() } : {},
|
|
267
|
-
...e.attachments ? { attachments: e.attachments.map((e) => ({
|
|
268
|
-
filename: e.filename,
|
|
269
|
-
content: e.content,
|
|
270
|
-
...e.contentType ? { content_type: e.contentType } : {}
|
|
271
|
-
})) } : {}
|
|
272
|
-
};
|
|
273
|
-
}, Ee = (e, t, r, i = Math.floor(Date.now() / 1e3), a = 300) => n.tryPromise({
|
|
274
|
-
try: async () => {
|
|
275
|
-
let n = (e) => {
|
|
276
|
-
throw new T({
|
|
277
|
-
provider: "lettermint",
|
|
278
|
-
message: e
|
|
279
|
-
});
|
|
280
|
-
}, o = (t) => Object.entries(e).find(([e]) => e.toLowerCase() === t)?.[1];
|
|
281
|
-
r === "" && n("no webhook secret configured"), t === "" && n("empty body");
|
|
282
|
-
let s = o("x-lettermint-signature") ?? n("missing x-lettermint-signature"), c = o("x-lettermint-delivery") ?? n("missing x-lettermint-delivery"), l, u = [];
|
|
283
|
-
for (let e of s.split(",")) {
|
|
284
|
-
let [t, n] = e.trim().split("=", 2);
|
|
285
|
-
t === "t" && n !== void 0 && /^\d+$/.test(n) ? l = n : t === "v1" && n !== void 0 && /^[a-fA-F0-9]{64}$/.test(n) && u.push(n.toLowerCase());
|
|
286
|
-
}
|
|
287
|
-
(l === void 0 || u.length === 0) && n("malformed signature header"), c !== l && n("signature and delivery timestamps differ"), Math.abs(i - Number(l)) > a && n("signature timestamp outside the tolerance");
|
|
288
|
-
let { createHmac: d, timingSafeEqual: f } = await import("node:crypto"), p = d("sha256", r).update(`${l}.`).update(t).digest();
|
|
289
|
-
u.some((e) => f(Buffer.from(e, "hex"), p)) || n("signature does not match");
|
|
290
|
-
try {
|
|
291
|
-
return JSON.parse(t);
|
|
292
|
-
} catch {
|
|
293
|
-
return n("body is not JSON");
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
catch: (e) => e instanceof T ? e : new T({
|
|
297
|
-
provider: "lettermint",
|
|
298
|
-
message: e?.message ?? String(e)
|
|
299
|
-
})
|
|
300
|
-
}), De = (e) => {
|
|
301
|
-
let t = (e.baseUrl ?? "https://api.lettermint.co/v1").replace(/\/+$/, ""), r = {
|
|
302
|
-
"x-lettermint-token": e.projectToken,
|
|
303
|
-
accept: "application/json"
|
|
304
|
-
};
|
|
305
|
-
return {
|
|
306
|
-
name: "lettermint",
|
|
307
|
-
supportsScheduling: !0,
|
|
308
|
-
managesUnsubscribe: !0,
|
|
309
|
-
send: (n) => U("lettermint", `${t}/send`, {
|
|
310
|
-
...r,
|
|
311
|
-
...n.idempotencyKey === void 0 ? {} : { "idempotency-key": n.idempotencyKey }
|
|
312
|
-
}, Te(n, e.routes), async (e) => ({
|
|
313
|
-
id: (await e.json()).message_id ?? "lettermint",
|
|
314
|
-
provider: "lettermint"
|
|
315
|
-
})),
|
|
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) => {
|
|
317
|
-
let t = await e.json();
|
|
318
|
-
return n.map((e, n) => ({
|
|
319
|
-
id: t[n]?.message_id ?? "lettermint",
|
|
320
|
-
provider: "lettermint"
|
|
321
|
-
}));
|
|
322
|
-
})).pipe(n.map((e) => e.flat())),
|
|
323
|
-
webhook: { verify: Ee }
|
|
324
|
-
};
|
|
325
|
-
}, Oe = (e) => ({
|
|
326
|
-
from: e.from,
|
|
327
|
-
to: D(e.to),
|
|
328
|
-
subject: e.subject,
|
|
329
|
-
html: e.html,
|
|
330
|
-
text: e.text,
|
|
331
|
-
reply_to: e.replyTo,
|
|
332
|
-
cc: e.cc ? D(e.cc) : void 0,
|
|
333
|
-
bcc: e.bcc ? D(e.bcc) : void 0,
|
|
334
|
-
headers: e.headers,
|
|
335
|
-
tags: e.tags?.map((e) => ({
|
|
336
|
-
name: e,
|
|
337
|
-
value: "1"
|
|
338
|
-
})),
|
|
339
|
-
...e.scheduledAt ? { scheduled_at: e.scheduledAt.toISOString() } : {},
|
|
340
|
-
attachments: e.attachments?.map((e) => ({
|
|
341
|
-
filename: e.filename,
|
|
342
|
-
content: e.content,
|
|
343
|
-
...e.contentType ? { content_type: e.contentType } : {}
|
|
344
|
-
}))
|
|
345
|
-
}), ke = (e) => ({
|
|
346
|
-
name: "resend",
|
|
347
|
-
supportsScheduling: !0,
|
|
348
|
-
send: (t) => U("resend", "https://api.resend.com/emails", { authorization: `Bearer ${e.apiKey}` }, Oe(t), async (e) => ({
|
|
349
|
-
id: (await e.json()).id ?? "resend",
|
|
350
|
-
provider: "resend"
|
|
351
|
-
})),
|
|
352
|
-
sendBatch: (t) => U("resend", "https://api.resend.com/emails/batch", { authorization: `Bearer ${e.apiKey}` }, t.map(Oe), async (e) => {
|
|
353
|
-
let n = (await e.json()).data ?? [];
|
|
354
|
-
return t.map((e, t) => ({
|
|
355
|
-
id: n[t]?.id ?? "resend",
|
|
356
|
-
provider: "resend"
|
|
357
|
-
}));
|
|
358
|
-
})
|
|
359
|
-
}), Ae = (e) => ({
|
|
360
|
-
name: "postmark",
|
|
361
|
-
send: (t) => U("postmark", "https://api.postmarkapp.com/email", {
|
|
362
|
-
"x-postmark-server-token": e.serverToken,
|
|
363
|
-
accept: "application/json"
|
|
364
|
-
}, {
|
|
365
|
-
From: t.from,
|
|
366
|
-
To: D(t.to).join(","),
|
|
367
|
-
Subject: t.subject,
|
|
368
|
-
HtmlBody: t.html,
|
|
369
|
-
TextBody: t.text,
|
|
370
|
-
ReplyTo: t.replyTo,
|
|
371
|
-
Cc: t.cc ? D(t.cc).join(",") : void 0,
|
|
372
|
-
Bcc: t.bcc ? D(t.bcc).join(",") : void 0,
|
|
373
|
-
MessageStream: t.stream === "broadcast" ? e.streams?.broadcast ?? "broadcast" : e.streams?.transactional ?? "outbound",
|
|
374
|
-
Headers: t.headers ? Object.entries(t.headers).map(([e, t]) => ({
|
|
375
|
-
Name: e,
|
|
376
|
-
Value: t
|
|
377
|
-
})) : void 0,
|
|
378
|
-
Metadata: t.metadata,
|
|
379
|
-
Attachments: t.attachments?.map((e) => ({
|
|
380
|
-
Name: e.filename,
|
|
381
|
-
Content: e.content,
|
|
382
|
-
ContentType: e.contentType ?? "application/octet-stream"
|
|
383
|
-
}))
|
|
384
|
-
}, async (e) => ({
|
|
385
|
-
id: (await e.json()).MessageID ?? "postmark",
|
|
386
|
-
provider: "postmark"
|
|
387
|
-
}))
|
|
388
|
-
}), je = (e) => [...e.text ? [{
|
|
389
|
-
type: "text/plain",
|
|
390
|
-
value: e.text
|
|
391
|
-
}] : [], ...e.html ? [{
|
|
392
|
-
type: "text/html",
|
|
393
|
-
value: e.html
|
|
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,
|
|
398
|
-
subject: e.subject,
|
|
399
|
-
...e.scheduledAt ? { send_at: Math.floor(e.scheduledAt.getTime() / 1e3) } : {}
|
|
400
|
-
}), Ne = (e) => e.attachments?.map((e) => ({
|
|
401
|
-
filename: e.filename,
|
|
402
|
-
content: e.content,
|
|
403
|
-
type: e.contentType ?? "application/octet-stream",
|
|
404
|
-
disposition: "attachment"
|
|
405
|
-
})), Pe = (e) => {
|
|
406
|
-
let t = (e) => ({
|
|
407
|
-
personalizations: [Me(e)],
|
|
408
|
-
from: { email: e.from },
|
|
409
|
-
reply_to: e.replyTo ? { email: e.replyTo } : void 0,
|
|
410
|
-
subject: e.subject,
|
|
411
|
-
content: je(e),
|
|
412
|
-
attachments: Ne(e)
|
|
413
|
-
});
|
|
414
|
-
return {
|
|
415
|
-
name: "sendgrid",
|
|
416
|
-
supportsScheduling: !0,
|
|
417
|
-
send: (n) => U("sendgrid", "https://api.sendgrid.com/v3/mail/send", { authorization: `Bearer ${e.apiKey}` }, t(n), async (e) => ({
|
|
418
|
-
id: e.headers.get("x-message-id") ?? "sendgrid",
|
|
419
|
-
provider: "sendgrid"
|
|
420
|
-
})),
|
|
421
|
-
sendBatch: (t) => U("sendgrid", "https://api.sendgrid.com/v3/mail/send", { authorization: `Bearer ${e.apiKey}` }, {
|
|
422
|
-
personalizations: t.map(Me),
|
|
423
|
-
from: { email: t[0]?.from },
|
|
424
|
-
subject: t[0]?.subject,
|
|
425
|
-
content: je(t[0] ?? {})
|
|
426
|
-
}, async (e) => {
|
|
427
|
-
let n = e.headers.get("x-message-id") ?? "sendgrid";
|
|
428
|
-
return t.map((e, t) => ({
|
|
429
|
-
id: `${n}-${t}`,
|
|
430
|
-
provider: "sendgrid"
|
|
431
|
-
}));
|
|
432
|
-
})
|
|
433
|
-
};
|
|
434
|
-
}, Fe = (e, t) => {
|
|
435
|
-
let n = t, r = n.response?.status;
|
|
436
|
-
return H(e, {
|
|
437
|
-
...typeof r == "number" ? { status: r } : {},
|
|
438
|
-
message: n.message ?? String(t)
|
|
439
|
-
});
|
|
440
|
-
}, Ie = (e, t, r) => n.gen(function* () {
|
|
441
|
-
let i = yield* (yield* S.HttpClient).execute(t);
|
|
442
|
-
if (i.status < 200 || i.status >= 300) {
|
|
443
|
-
let t = yield* i.text.pipe(n.catchAll(() => n.succeed("")));
|
|
444
|
-
return yield* n.fail(H(e, {
|
|
445
|
-
status: i.status,
|
|
446
|
-
message: t || `HTTP ${i.status}`
|
|
447
|
-
}));
|
|
448
|
-
}
|
|
449
|
-
return yield* r(i);
|
|
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) => {
|
|
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");
|
|
452
|
-
return {
|
|
453
|
-
authorization: `AWS4-HMAC-SHA256 Credential=${e.accessKeyId}/${p}, SignedHeaders=${d}, Signature=${h}`,
|
|
454
|
-
"x-amz-date": c,
|
|
455
|
-
...e.sessionToken ? { "x-amz-security-token": e.sessionToken } : {}
|
|
456
|
-
};
|
|
457
|
-
} }), Re = (e) => JSON.stringify({
|
|
458
|
-
FromEmailAddress: e.from,
|
|
459
|
-
Destination: {
|
|
460
|
-
ToAddresses: D(e.to),
|
|
461
|
-
...e.cc ? { CcAddresses: D(e.cc) } : {},
|
|
462
|
-
...e.bcc ? { BccAddresses: D(e.bcc) } : {}
|
|
463
|
-
},
|
|
464
|
-
...e.replyTo ? { ReplyToAddresses: [e.replyTo] } : {},
|
|
465
|
-
Content: { Simple: {
|
|
466
|
-
Subject: {
|
|
467
|
-
Data: e.subject,
|
|
468
|
-
Charset: "UTF-8"
|
|
469
|
-
},
|
|
470
|
-
Body: {
|
|
471
|
-
...e.html ? { Html: {
|
|
472
|
-
Data: e.html,
|
|
473
|
-
Charset: "UTF-8"
|
|
474
|
-
} } : {},
|
|
475
|
-
...e.text ? { Text: {
|
|
476
|
-
Data: e.text,
|
|
477
|
-
Charset: "UTF-8"
|
|
478
|
-
} } : {}
|
|
479
|
-
}
|
|
480
|
-
} }
|
|
481
|
-
}), ze = (e) => {
|
|
482
|
-
let t = Le(e), r = `https://email.${e.region}.amazonaws.com`, i = "/v2/email/outbound-emails";
|
|
483
|
-
return {
|
|
484
|
-
name: "ses",
|
|
485
|
-
send: (e) => n.gen(function* () {
|
|
486
|
-
let a = Re(e), o = yield* n.tryPromise({
|
|
487
|
-
try: () => t.sign(r, i, a),
|
|
488
|
-
catch: (e) => H("ses", { message: e?.message ?? String(e) })
|
|
489
|
-
});
|
|
490
|
-
return yield* Ie("ses", C.post(`${r}${i}`).pipe(C.setHeaders({
|
|
491
|
-
"content-type": "application/json",
|
|
492
|
-
...o
|
|
493
|
-
}), C.bodyUnsafeJson(JSON.parse(a))), (e) => e.json.pipe(n.map((e) => ({
|
|
494
|
-
id: e.MessageId ?? "ses",
|
|
495
|
-
provider: "ses"
|
|
496
|
-
}))));
|
|
497
|
-
})
|
|
498
|
-
};
|
|
499
|
-
}, Be = (e) => {
|
|
500
|
-
let t = [["from", e.from], ["subject", e.subject]];
|
|
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]);
|
|
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]);
|
|
505
|
-
if (e.tags) for (let n of e.tags) t.push(["o:tag", n]);
|
|
506
|
-
return e.scheduledAt && t.push(["o:deliverytime", e.scheduledAt.toUTCString()]), t;
|
|
507
|
-
}, Ve = (e) => {
|
|
508
|
-
let t = `${e.baseUrl ?? "https://api.mailgun.net"}/v3/${e.domain}/messages`;
|
|
509
|
-
return {
|
|
510
|
-
name: "mailgun",
|
|
511
|
-
supportsScheduling: !0,
|
|
512
|
-
send: (r) => {
|
|
513
|
-
let i = new URLSearchParams();
|
|
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) => ({
|
|
516
|
-
id: e.id ?? "mailgun",
|
|
517
|
-
provider: "mailgun"
|
|
518
|
-
}))));
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
}, G = (e) => {
|
|
522
|
-
let t = null;
|
|
523
|
-
return {
|
|
524
|
-
name: "smtp",
|
|
525
|
-
send: (r) => n.tryPromise({
|
|
526
|
-
try: async () => {
|
|
527
|
-
if (!t) {
|
|
528
|
-
let n = await import("nodemailer"), r = n.createTransport ?? n.default?.createTransport;
|
|
529
|
-
if (!r) throw Error("nodemailer: createTransport export not found");
|
|
530
|
-
t = r(e.url ?? {
|
|
531
|
-
host: e.host,
|
|
532
|
-
port: e.port,
|
|
533
|
-
secure: e.secure,
|
|
534
|
-
...e.auth ? { auth: e.auth } : {}
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
id: (await t.sendMail({
|
|
539
|
-
from: r.from,
|
|
540
|
-
to: D(r.to),
|
|
541
|
-
cc: r.cc ? D(r.cc) : void 0,
|
|
542
|
-
bcc: r.bcc ? D(r.bcc) : void 0,
|
|
543
|
-
replyTo: r.replyTo,
|
|
544
|
-
subject: r.subject,
|
|
545
|
-
html: r.html,
|
|
546
|
-
text: r.text,
|
|
547
|
-
headers: r.headers,
|
|
548
|
-
attachments: r.attachments?.map((e) => ({
|
|
549
|
-
filename: e.filename,
|
|
550
|
-
content: e.content,
|
|
551
|
-
encoding: "base64",
|
|
552
|
-
...e.contentType ? { contentType: e.contentType } : {}
|
|
553
|
-
}))
|
|
554
|
-
}))?.messageId ?? "smtp",
|
|
555
|
-
provider: "smtp"
|
|
556
|
-
};
|
|
557
|
-
},
|
|
558
|
-
catch: (e) => {
|
|
559
|
-
let t = e;
|
|
560
|
-
return H("smtp", {
|
|
561
|
-
...typeof t.responseCode == "number" ? { status: t.responseCode } : {},
|
|
562
|
-
message: t.message ?? String(e)
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
})
|
|
566
|
-
};
|
|
567
|
-
}, He = (e) => ({
|
|
568
|
-
name: "console",
|
|
569
|
-
send: (t) => n.sync(() => {
|
|
570
|
-
let n = `✉ to=${D(t.to).join(",")} subject=${JSON.stringify(t.subject)}`;
|
|
571
|
-
return e ? e(n, { from: t.from }) : process.stdout.write(`[mail:console] ${n}\n`), {
|
|
572
|
-
id: Ce("console"),
|
|
573
|
-
provider: "console"
|
|
574
|
-
};
|
|
575
|
-
})
|
|
576
|
-
}), K = [], Ue = () => [...K], We = () => {
|
|
577
|
-
K.length = 0;
|
|
578
|
-
}, Ge = () => ({
|
|
579
|
-
name: "memory",
|
|
580
|
-
send: (e) => n.sync(() => (K.push(e), {
|
|
581
|
-
id: Ce("mem"),
|
|
582
|
-
provider: "memory"
|
|
583
|
-
}))
|
|
584
|
-
}), q = d({ scope: "@voltro/plugin-mail" }), Ke = (e) => {
|
|
585
|
-
if (e.provider !== void 0 && typeof e.provider != "string") return e.provider;
|
|
586
|
-
let t = e.env ?? {}, n = e.provider ?? t.MAIL_PROVIDER ?? "console", r = (e, t) => {
|
|
587
|
-
if (!t) throw Error(`@voltro/plugin-mail: provider '${n}' needs apiKey or ${e}`);
|
|
588
|
-
return t;
|
|
589
|
-
};
|
|
590
|
-
switch (n) {
|
|
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({
|
|
596
|
-
region: r("AWS_REGION", t.AWS_REGION),
|
|
597
|
-
accessKeyId: r("AWS_ACCESS_KEY_ID", t.AWS_ACCESS_KEY_ID),
|
|
598
|
-
secretAccessKey: r("AWS_SECRET_ACCESS_KEY", t.AWS_SECRET_ACCESS_KEY),
|
|
599
|
-
...t.AWS_SESSION_TOKEN ? { sessionToken: t.AWS_SESSION_TOKEN } : {}
|
|
600
|
-
});
|
|
601
|
-
case "mailgun": return Ve({
|
|
602
|
-
apiKey: r("MAILGUN_API_KEY", e.apiKey ?? t.MAILGUN_API_KEY),
|
|
603
|
-
domain: r("MAILGUN_DOMAIN", t.MAILGUN_DOMAIN),
|
|
604
|
-
...t.MAILGUN_BASE_URL ? { baseUrl: t.MAILGUN_BASE_URL } : {}
|
|
605
|
-
});
|
|
606
|
-
case "smtp": return t.SMTP_URL ? G({ url: t.SMTP_URL }) : G({
|
|
607
|
-
host: r("SMTP_URL/SMTP_HOST", t.SMTP_HOST),
|
|
608
|
-
...t.SMTP_PORT ? { port: Number(t.SMTP_PORT) } : {},
|
|
609
|
-
secure: t.SMTP_SECURE === "true",
|
|
610
|
-
...t.SMTP_USER && t.SMTP_PASS ? { auth: {
|
|
611
|
-
user: t.SMTP_USER,
|
|
612
|
-
pass: t.SMTP_PASS
|
|
613
|
-
} } : {}
|
|
614
|
-
});
|
|
615
|
-
case "memory": return Ge();
|
|
616
|
-
default: return He((e, t) => q.info(e, t));
|
|
617
|
-
}
|
|
618
|
-
}, J = (e) => {
|
|
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();
|
|
620
|
-
for (let t of e.templates ?? []) l.set(t.locale ? `${t.name}:${t.locale}` : t.name, t);
|
|
621
|
-
let u = (e, t) => (t ? l.get(`${e}:${t}`) : void 0) ?? l.get(e) ?? j(e, t), d = /* @__PURE__ */ new Map(), f = (e, t) => `${e.name}|${e.locale ?? ""}|${JSON.stringify(t ?? null)}`, p = (e, t) => {
|
|
622
|
-
if (d.set(e, t), d.size > 500) {
|
|
623
|
-
let e = d.keys().next().value;
|
|
624
|
-
e !== void 0 && d.delete(e);
|
|
625
|
-
}
|
|
626
|
-
}, m = e.idempotency ?? B(), h = xe({
|
|
627
|
-
store: e.schedule ?? V(),
|
|
628
|
-
providerSchedules: e.provider.supportsScheduling === !0,
|
|
629
|
-
flush: (e) => v(e)
|
|
630
|
-
}), g = (t) => {
|
|
631
|
-
if (t.stream !== "broadcast" || e.provider.managesUnsubscribe === !0) return n.succeed(void 0);
|
|
632
|
-
let r = t.unsubscribe;
|
|
633
|
-
if (r === void 0 || !r.oneClickUrl.startsWith("https://")) return n.fail(new w({
|
|
634
|
-
provider: e.provider.name,
|
|
635
|
-
message: `a broadcast needs \`unsubscribe: { oneClickUrl }\` (an https URL) — '${e.provider.name}' does not unsubscribe recipients itself`,
|
|
636
|
-
transient: !1
|
|
637
|
-
}));
|
|
638
|
-
let i = [`<${r.oneClickUrl}>`, ...r.mailto === void 0 ? [] : [`<mailto:${r.mailto.replace(/^mailto:/, "")}>`]];
|
|
639
|
-
return n.succeed({
|
|
640
|
-
"List-Unsubscribe": i.join(", "),
|
|
641
|
-
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
|
|
642
|
-
});
|
|
643
|
-
}, _ = (t) => n.gen(function* () {
|
|
644
|
-
let i = t.from ?? e.from;
|
|
645
|
-
if (!i) return yield* n.fail(new w({
|
|
646
|
-
provider: e.provider.name,
|
|
647
|
-
message: "no `from` — set mailPlugin({ from }) or pass message.from",
|
|
648
|
-
transient: !1
|
|
649
|
-
}));
|
|
650
|
-
let o = D(t.to);
|
|
651
|
-
if (a) {
|
|
652
|
-
let n = o.filter(s);
|
|
653
|
-
if (n.length === 0) return q.warn("mail skipped — no recipient in allowlist (non-production)", {
|
|
654
|
-
to: o.join(","),
|
|
655
|
-
subject: t.subject
|
|
656
|
-
}), e.onSent?.({
|
|
657
|
-
id: "skipped-allowlist",
|
|
658
|
-
to: o,
|
|
659
|
-
from: i,
|
|
660
|
-
subject: t.subject,
|
|
661
|
-
provider: "skipped",
|
|
662
|
-
ok: !0,
|
|
663
|
-
ts: Date.now()
|
|
664
|
-
}), {
|
|
665
|
-
kind: "skip",
|
|
666
|
-
result: {
|
|
667
|
-
id: "skipped-allowlist",
|
|
668
|
-
provider: "skipped"
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
o = n;
|
|
672
|
-
}
|
|
673
|
-
let c = t.tenantId ?? null, l = [];
|
|
674
|
-
for (let e of o) (yield* r.isSuppressed(c, e)) || l.push(e);
|
|
675
|
-
if (l.length === 0) return q.warn("mail skipped — all recipients suppressed", {
|
|
676
|
-
to: o.join(","),
|
|
677
|
-
subject: t.subject
|
|
678
|
-
}), e.onSent?.({
|
|
679
|
-
id: "skipped-suppressed",
|
|
680
|
-
to: o,
|
|
681
|
-
from: i,
|
|
682
|
-
subject: t.subject,
|
|
683
|
-
provider: "suppressed",
|
|
684
|
-
ok: !0,
|
|
685
|
-
ts: Date.now()
|
|
686
|
-
}), {
|
|
687
|
-
kind: "skip",
|
|
688
|
-
result: {
|
|
689
|
-
id: "skipped-suppressed",
|
|
690
|
-
provider: "suppressed"
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
o = l;
|
|
694
|
-
let u = t.replyTo ?? e.replyTo, d = yield* g(t), f = d === void 0 ? t.headers : {
|
|
695
|
-
...t.headers,
|
|
696
|
-
...d
|
|
697
|
-
};
|
|
698
|
-
return {
|
|
699
|
-
kind: "send",
|
|
700
|
-
message: {
|
|
701
|
-
...t,
|
|
702
|
-
from: i,
|
|
703
|
-
to: o,
|
|
704
|
-
...u === void 0 ? {} : { replyTo: u },
|
|
705
|
-
...f === void 0 ? {} : { headers: f }
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
}), ee = (t) => {
|
|
709
|
-
let r = D(t.to);
|
|
710
|
-
return e.provider.send(t).pipe(n.retry(c), n.tap((i) => n.sync(() => {
|
|
711
|
-
q.info("mail sent", {
|
|
712
|
-
provider: i.provider,
|
|
713
|
-
id: i.id,
|
|
714
|
-
to: r.join(",")
|
|
715
|
-
}), e.onSent?.({
|
|
716
|
-
id: i.id,
|
|
717
|
-
to: r,
|
|
718
|
-
from: t.from,
|
|
719
|
-
subject: t.subject,
|
|
720
|
-
provider: i.provider,
|
|
721
|
-
ok: !0,
|
|
722
|
-
ts: Date.now()
|
|
723
|
-
});
|
|
724
|
-
})), n.tapError((i) => n.sync(() => {
|
|
725
|
-
q.warn("mail failed", {
|
|
726
|
-
provider: i.provider,
|
|
727
|
-
status: i.status,
|
|
728
|
-
message: i.message
|
|
729
|
-
}), e.onSent?.({
|
|
730
|
-
id: `err-${Date.now()}`,
|
|
731
|
-
to: r,
|
|
732
|
-
from: t.from,
|
|
733
|
-
subject: t.subject,
|
|
734
|
-
provider: i.provider,
|
|
735
|
-
ok: !1,
|
|
736
|
-
error: i.message,
|
|
737
|
-
ts: Date.now()
|
|
738
|
-
});
|
|
739
|
-
})));
|
|
740
|
-
}, v = (t) => n.gen(function* () {
|
|
741
|
-
let n = yield* _(t);
|
|
742
|
-
return n.kind === "skip" ? n.result : h.shouldHold(t.scheduledAt) ? (yield* h.hold(n.message), q.info("mail scheduled (held for later flush)", {
|
|
743
|
-
subject: n.message.subject,
|
|
744
|
-
dueAt: t.scheduledAt?.toISOString()
|
|
745
|
-
}), e.onSent?.({
|
|
746
|
-
id: "scheduled",
|
|
747
|
-
to: D(n.message.to),
|
|
748
|
-
from: n.message.from,
|
|
749
|
-
subject: n.message.subject,
|
|
750
|
-
provider: "scheduled",
|
|
751
|
-
ok: !0,
|
|
752
|
-
ts: Date.now()
|
|
753
|
-
}), {
|
|
754
|
-
id: "scheduled",
|
|
755
|
-
provider: "scheduled"
|
|
756
|
-
}) : yield* ee(n.message);
|
|
757
|
-
}), y = (e, t, r) => t === void 0 ? r : n.gen(function* () {
|
|
758
|
-
let n = yield* m.reserve(e, t);
|
|
759
|
-
if (!n.won) return q.info("mail idempotent replay — skipped", { key: t }), n.prior ?? {
|
|
760
|
-
id: "idempotent-replay",
|
|
761
|
-
provider: "idempotent"
|
|
762
|
-
};
|
|
763
|
-
let i = yield* r;
|
|
764
|
-
return yield* m.record(e, t, i), i;
|
|
765
|
-
}), b = (e) => n.suspend(() => {
|
|
766
|
-
let t = u(e.template, e.locale);
|
|
767
|
-
if (!t) return n.fail(new w({
|
|
768
|
-
provider: "template",
|
|
769
|
-
message: `unknown template '${e.template}'${e.locale ? ` (locale '${e.locale}')` : ""}`,
|
|
770
|
-
transient: !1
|
|
771
|
-
}));
|
|
772
|
-
let r = f(t, e.props), i = d.get(r);
|
|
773
|
-
return (i ? n.succeed(i) : k(t, e.props).pipe(n.tap((e) => n.sync(() => p(r, e))))).pipe(n.map((t) => ({
|
|
774
|
-
to: e.to,
|
|
775
|
-
subject: t.subject,
|
|
776
|
-
html: t.html,
|
|
777
|
-
text: t.text,
|
|
778
|
-
...e.from === void 0 ? {} : { from: e.from },
|
|
779
|
-
...e.replyTo === void 0 ? {} : { replyTo: e.replyTo },
|
|
780
|
-
...e.cc === void 0 ? {} : { cc: e.cc },
|
|
781
|
-
...e.bcc === void 0 ? {} : { bcc: e.bcc },
|
|
782
|
-
...e.headers === void 0 ? {} : { headers: e.headers },
|
|
783
|
-
...e.tags === void 0 ? {} : { tags: e.tags },
|
|
784
|
-
...e.attachments === void 0 ? {} : { attachments: e.attachments },
|
|
785
|
-
...e.tenantId === void 0 ? {} : { tenantId: e.tenantId },
|
|
786
|
-
...e.scheduledAt === void 0 ? {} : { scheduledAt: e.scheduledAt },
|
|
787
|
-
...e.idempotencyKey === void 0 ? {} : { idempotencyKey: e.idempotencyKey },
|
|
788
|
-
...e.stream === void 0 ? {} : { stream: e.stream },
|
|
789
|
-
...e.unsubscribe === void 0 ? {} : { unsubscribe: e.unsubscribe },
|
|
790
|
-
...e.metadata === void 0 ? {} : { metadata: e.metadata }
|
|
791
|
-
})));
|
|
792
|
-
}), x = (e) => "template" in e ? b(e) : n.succeed(e);
|
|
793
|
-
return {
|
|
794
|
-
service: {
|
|
795
|
-
send: (e) => n.gen(function* () {
|
|
796
|
-
let t = yield* x(e);
|
|
797
|
-
return yield* y(t.tenantId ?? null, t.idempotencyKey, v(t));
|
|
798
|
-
}),
|
|
799
|
-
sendBatch: (t) => n.gen(function* () {
|
|
800
|
-
let r = yield* n.forEach(t, x), i = r.some((e) => e.idempotencyKey !== void 0 || h.shouldHold(e.scheduledAt));
|
|
801
|
-
if (e.provider.sendBatch && !i) {
|
|
802
|
-
let t = [], i = [];
|
|
803
|
-
for (let e of r) {
|
|
804
|
-
let n = yield* _(e);
|
|
805
|
-
n.kind === "skip" ? i.push(n.result) : (i.push(t.length), t.push(n.message));
|
|
806
|
-
}
|
|
807
|
-
let a = t.length > 0 ? yield* e.provider.sendBatch(t).pipe(n.retry(c), n.tap((r) => n.sync(() => {
|
|
808
|
-
t.forEach((t, n) => {
|
|
809
|
-
let i = r[n];
|
|
810
|
-
i && (q.info("mail sent (batch)", {
|
|
811
|
-
provider: i.provider,
|
|
812
|
-
id: i.id,
|
|
813
|
-
to: D(t.to).join(",")
|
|
814
|
-
}), e.onSent?.({
|
|
815
|
-
id: i.id,
|
|
816
|
-
to: D(t.to),
|
|
817
|
-
from: t.from,
|
|
818
|
-
subject: t.subject,
|
|
819
|
-
provider: i.provider,
|
|
820
|
-
ok: !0,
|
|
821
|
-
ts: Date.now()
|
|
822
|
-
}));
|
|
823
|
-
});
|
|
824
|
-
})), n.tapError((e) => n.sync(() => q.warn("mail batch failed", {
|
|
825
|
-
provider: e.provider,
|
|
826
|
-
status: e.status,
|
|
827
|
-
message: e.message
|
|
828
|
-
})))) : [];
|
|
829
|
-
return i.map((t) => typeof t == "number" ? a[t] ?? {
|
|
830
|
-
id: "batch",
|
|
831
|
-
provider: e.provider.name
|
|
832
|
-
} : t);
|
|
833
|
-
}
|
|
834
|
-
return yield* n.forEach(r, (e) => y(e.tenantId ?? null, e.idempotencyKey, v(e)));
|
|
835
|
-
}),
|
|
836
|
-
suppress: r.suppress,
|
|
837
|
-
unsuppress: r.unsuppress,
|
|
838
|
-
isSuppressed: r.isSuppressed,
|
|
839
|
-
provider: {
|
|
840
|
-
name: e.provider.name,
|
|
841
|
-
managesUnsubscribe: e.provider.managesUnsubscribe === !0
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
scheduler: h
|
|
845
|
-
};
|
|
846
|
-
}, qe = (e) => J(e).service, Je = (e) => i.succeed(E, qe(e)), Y = (e, t, n, r, i, a = {}) => {
|
|
847
|
-
if (!t) return null;
|
|
848
|
-
let o = a.metadata !== null && typeof a.metadata == "object" ? Object.fromEntries(Object.entries(a.metadata).filter(([, e]) => typeof e == "string")) : void 0;
|
|
849
|
-
return {
|
|
850
|
-
type: e,
|
|
851
|
-
email: t,
|
|
852
|
-
provider: n,
|
|
853
|
-
raw: i,
|
|
854
|
-
...r === void 0 ? {} : { messageId: r },
|
|
855
|
-
...a.eventId === void 0 ? {} : { eventId: a.eventId },
|
|
856
|
-
...o === void 0 ? {} : { metadata: o }
|
|
857
|
-
};
|
|
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) => {
|
|
859
|
-
let n = [], r = (e) => {
|
|
860
|
-
e && n.push(e);
|
|
861
|
-
};
|
|
862
|
-
if (e === "sendgrid" && Array.isArray(t)) {
|
|
863
|
-
for (let e of t) r(Y($e(String(e.event)), e.email, "sendgrid", e.sg_message_id, e));
|
|
864
|
-
return n;
|
|
865
|
-
}
|
|
866
|
-
let i = t ?? {};
|
|
867
|
-
if (e === "lettermint") {
|
|
868
|
-
let e = String(i.event), a = i.data ?? {}, o = typeof i.id == "string" ? i.id : void 0;
|
|
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, {
|
|
870
|
-
eventId: o,
|
|
871
|
-
metadata: a.metadata
|
|
872
|
-
})), n);
|
|
873
|
-
}
|
|
874
|
-
if (e === "resend") {
|
|
875
|
-
let e = i.data ?? {}, a = Array.isArray(e.to) ? e.to[0] : e.to ?? e.email;
|
|
876
|
-
return r(Y(Ze(String(i.type)), a, "resend", e.email_id, t)), n;
|
|
877
|
-
}
|
|
878
|
-
if (e === "postmark") {
|
|
879
|
-
let e = i.Email ?? i.Recipient;
|
|
880
|
-
return r(Y(Qe(String(i.RecordType)), e, "postmark", i.MessageID, t, { metadata: i.Metadata })), n;
|
|
881
|
-
}
|
|
882
|
-
return n;
|
|
883
|
-
}, et = (e, t, r, i = null) => n.gen(function* () {
|
|
884
|
-
let n = X(e, t);
|
|
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));
|
|
886
|
-
return n;
|
|
887
|
-
}), tt = (e = 200) => {
|
|
888
|
-
let t = [];
|
|
889
|
-
return {
|
|
890
|
-
record: (n) => {
|
|
891
|
-
t.push(n), t.length > e && t.splice(0, t.length - e);
|
|
892
|
-
},
|
|
893
|
-
list: () => [...t].reverse(),
|
|
894
|
-
clear: () => {
|
|
895
|
-
t.length = 0;
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
}, nt = {
|
|
899
|
-
lettermint: "api.lettermint.co",
|
|
900
|
-
resend: "api.resend.com",
|
|
901
|
-
postmark: "api.postmarkapp.com",
|
|
902
|
-
sendgrid: "api.sendgrid.com"
|
|
903
|
-
}, rt = (e) => {
|
|
904
|
-
try {
|
|
905
|
-
return new URL(e).host || void 0;
|
|
906
|
-
} catch {
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
}, it = (e) => {
|
|
910
|
-
let t = e.env ?? {}, n = (e) => [{
|
|
911
|
-
destination: e,
|
|
912
|
-
purpose: "outgoing email: recipients, subject and body",
|
|
913
|
-
data: ["personal", "content"],
|
|
914
|
-
disable: "MAIL_PROVIDER=console (or memory) logs mail instead of sending it — none leaves"
|
|
915
|
-
}];
|
|
916
|
-
if (e.provider !== void 0 && typeof e.provider != "string") {
|
|
917
|
-
let t = e.provider.name;
|
|
918
|
-
return t === "console" || t === "memory" ? [] : n(`runtime:the '${t}' mail provider`);
|
|
919
|
-
}
|
|
920
|
-
let r = e.provider ?? t.MAIL_PROVIDER ?? "console", i = nt[r];
|
|
921
|
-
if (i !== void 0) return n(i);
|
|
922
|
-
switch (r) {
|
|
923
|
-
case "ses": return n(t.AWS_REGION ? `email.${t.AWS_REGION}.amazonaws.com` : "runtime:email.<AWS_REGION>.amazonaws.com");
|
|
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");
|
|
926
|
-
default: return [];
|
|
927
|
-
}
|
|
928
|
-
}, Z = (e, t) => {
|
|
929
|
-
if (t > 6) return null;
|
|
930
|
-
switch (e._tag) {
|
|
931
|
-
case "StringKeyword": return "";
|
|
932
|
-
case "NumberKeyword": return 0;
|
|
933
|
-
case "BooleanKeyword": return !1;
|
|
934
|
-
case "Literal": return e.literal;
|
|
935
|
-
case "Enums": return e.enums.length > 0 ? e.enums[0][1] : null;
|
|
936
|
-
case "TypeLiteral": {
|
|
937
|
-
let n = {};
|
|
938
|
-
for (let r of e.propertySignatures) typeof r.name == "string" && (n[r.name] = Z(r.type, t + 1));
|
|
939
|
-
return n;
|
|
940
|
-
}
|
|
941
|
-
case "TupleType": return [];
|
|
942
|
-
case "Union": return e.types.length > 0 ? Z(e.types[0], t + 1) : null;
|
|
943
|
-
case "Refinement": return Z(e.from, t + 1);
|
|
944
|
-
case "Transformation": return Z(e.from, t + 1);
|
|
945
|
-
case "Suspend": return Z(e.f(), t + 1);
|
|
946
|
-
default: return null;
|
|
947
|
-
}
|
|
948
|
-
}, at = (e) => Z(e.ast, 0), Q = u([
|
|
1
|
+
import { MailError as e, MailService as t, MailWebhookError as n, defineEmail as r } from "./types.js";
|
|
2
|
+
import { renderTemplate as i } from "./render.js";
|
|
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 ee, mailIdempotencyTable as d, mailSuppressionTable as f } from "./storeTables.js";
|
|
5
|
+
import { memorySuppressionStore as p, postgresSuppressionStore as m } from "./suppression.js";
|
|
6
|
+
import { memoryIdempotencyStore as h, postgresIdempotencyStore as g } from "./idempotency.js";
|
|
7
|
+
import { heldId as _, makeScheduler as v, memoryScheduleStore as y } from "./scheduler.js";
|
|
8
|
+
import { clearMailBuffer as b, consoleProvider as x, lettermintProvider as S, mailgunProvider as C, memoryProvider as w, postmarkProvider as T, readMailBuffer as E, resendProvider as D, sendgridProvider as O, sesProvider as k, smtpProvider as A, verifyLettermintSignature as j } from "./providers.js";
|
|
9
|
+
import { buildMailService as M, buildMailServiceWithScheduler as N, mailServiceLayer as P, resolveProvider as F } from "./service.js";
|
|
10
|
+
import { handleMailEvents as I, parseMailEvent as L } from "./events.js";
|
|
11
|
+
import { createOutbox as R } from "./outbox.js";
|
|
12
|
+
import { mailEgressOf as z } from "./egress.js";
|
|
13
|
+
import { schemaSkeleton as te } from "./skeleton.js";
|
|
14
|
+
import { Duration as B, Effect as V, Fiber as H, Layer as U, Result as W, Schedule as G } from "effect";
|
|
15
|
+
import { definePlugin as K, pluginInstanceName as q } from "@voltro/protocol";
|
|
16
|
+
import { pluginEnv as J } from "@voltro/env";
|
|
17
|
+
import { registerRetention as Y, retentionTtlMsFromEnv as X } from "@voltro/database";
|
|
18
|
+
//#region src/index.ts
|
|
19
|
+
var Z = J([
|
|
949
20
|
{
|
|
950
21
|
name: "MAIL_PROVIDER",
|
|
951
22
|
required: !1,
|
|
@@ -1066,28 +137,28 @@ var w = class extends s.TaggedError()("MailError", {
|
|
|
1066
137
|
secret: !1,
|
|
1067
138
|
description: "Comma-separated recipient allowlist; in dev, mail to addresses outside it is skipped."
|
|
1068
139
|
}
|
|
1069
|
-
]),
|
|
140
|
+
]), Q = "@voltro/plugin-mail", $ = (e, t) => ({
|
|
1070
141
|
status: e,
|
|
1071
142
|
contentType: "application/json",
|
|
1072
143
|
body: JSON.stringify(t)
|
|
1073
|
-
}),
|
|
1074
|
-
let
|
|
1075
|
-
if (
|
|
1076
|
-
let
|
|
1077
|
-
if (!
|
|
144
|
+
}), ne = (e, t, n, r) => {
|
|
145
|
+
let i = e.webhook;
|
|
146
|
+
if (i === void 0) throw Error(`@voltro/plugin-mail: provider '${e.name}' has no webhook verifier — mailPlugin({ webhook }) needs one (lettermint)`);
|
|
147
|
+
let a = n.secret ?? Z.read("MAIL_WEBHOOK_SECRET");
|
|
148
|
+
if (!a && r) throw Error("@voltro/plugin-mail: mailPlugin({ webhook }) needs a signing secret in production — set MAIL_WEBHOOK_SECRET");
|
|
1078
149
|
return {
|
|
1079
150
|
method: "POST",
|
|
1080
|
-
path:
|
|
1081
|
-
handle: async (
|
|
1082
|
-
if (!
|
|
1083
|
-
let
|
|
1084
|
-
if (
|
|
1085
|
-
let
|
|
151
|
+
path: n.path ?? "/webhooks/mail",
|
|
152
|
+
handle: async (r) => {
|
|
153
|
+
if (!a) return $(503, { error: "mail webhook secret not configured" });
|
|
154
|
+
let o = await V.runPromise(V.result(i.verify(r.headers, new TextDecoder().decode(r.rawBody), a)));
|
|
155
|
+
if (W.isFailure(o)) return $(401, { error: o.failure.message });
|
|
156
|
+
let s = L(e.name, o.success);
|
|
1086
157
|
try {
|
|
1087
|
-
for (let e of
|
|
1088
|
-
e.type === "bounced" || e.type === "complained" ? await
|
|
1089
|
-
let
|
|
1090
|
-
|
|
158
|
+
for (let e of s) {
|
|
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));
|
|
160
|
+
let i = n.onEvent?.(e, { store: r.store });
|
|
161
|
+
V.isEffect(i) ? await V.runPromise(i) : await i;
|
|
1091
162
|
}
|
|
1092
163
|
} catch (e) {
|
|
1093
164
|
return {
|
|
@@ -1095,57 +166,57 @@ var w = class extends s.TaggedError()("MailError", {
|
|
|
1095
166
|
cause: e
|
|
1096
167
|
};
|
|
1097
168
|
}
|
|
1098
|
-
return $(200, { received:
|
|
169
|
+
return $(200, { received: s.length });
|
|
1099
170
|
}
|
|
1100
171
|
};
|
|
1101
|
-
},
|
|
1102
|
-
let
|
|
172
|
+
}, re = (e = {}) => {
|
|
173
|
+
let n = process.env, r = F({
|
|
1103
174
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
1104
175
|
...e.apiKey === void 0 ? {} : { apiKey: e.apiKey },
|
|
1105
|
-
env:
|
|
1106
|
-
}),
|
|
1107
|
-
e.idempotency === "postgres" &&
|
|
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" ? p() : e.suppression === "postgres" ? m() : e.suppression, _ = e.idempotency === void 0 || e.idempotency === "memory" ? h() : e.idempotency === "postgres" ? g() : e.idempotency, v = e.schedule ?? y(), b = [...e.suppression === "postgres" ? [f] : [], ...e.idempotency === "postgres" ? [d] : []];
|
|
178
|
+
e.idempotency === "postgres" && Y({
|
|
1108
179
|
source: "plugin",
|
|
1109
|
-
table:
|
|
180
|
+
table: ee,
|
|
1110
181
|
timeColumn: "createdAt",
|
|
1111
|
-
ttlMs:
|
|
182
|
+
ttlMs: X(process.env.VOLTRO_MAIL_IDEMPOTENCY_TTL_HOURS, 720),
|
|
1112
183
|
label: "mail idempotency keys"
|
|
1113
184
|
});
|
|
1114
|
-
let
|
|
1115
|
-
provider:
|
|
1116
|
-
suppression:
|
|
1117
|
-
idempotency:
|
|
1118
|
-
schedule:
|
|
1119
|
-
onSent:
|
|
185
|
+
let x = R(), S = N({
|
|
186
|
+
provider: r,
|
|
187
|
+
suppression: u,
|
|
188
|
+
idempotency: _,
|
|
189
|
+
schedule: v,
|
|
190
|
+
onSent: x.record,
|
|
1120
191
|
...e.from === void 0 ? {} : { from: e.from },
|
|
1121
192
|
...e.replyTo === void 0 ? {} : { replyTo: e.replyTo },
|
|
1122
|
-
...
|
|
193
|
+
...o === void 0 ? {} : { allowlist: o },
|
|
1123
194
|
...e.attempts === void 0 ? {} : { attempts: e.attempts },
|
|
1124
195
|
...e.templates === void 0 ? {} : { templates: e.templates },
|
|
1125
|
-
production:
|
|
1126
|
-
}),
|
|
1127
|
-
for (let t of e.templates ?? [])
|
|
1128
|
-
let
|
|
196
|
+
production: s
|
|
197
|
+
}), C = U.succeed(t, S.service), w = r.supportsScheduling !== !0, T = e.scheduleTickMs ?? 1e3, E, D = /* @__PURE__ */ new Map();
|
|
198
|
+
for (let t of e.templates ?? []) D.set(t.locale ? `${t.name}:${t.locale}` : t.name, t);
|
|
199
|
+
let O = (e, t) => (t ? D.get(`${e}:${t}`) : void 0) ?? D.get(e) ?? l(e, t), k = (e) => ({
|
|
1129
200
|
name: e.name,
|
|
1130
201
|
...e.locale ? { locale: e.locale } : {},
|
|
1131
|
-
props: e.preview ??
|
|
1132
|
-
}),
|
|
202
|
+
props: e.preview ?? te(e.props)
|
|
203
|
+
}), A = () => {
|
|
1133
204
|
let t = /* @__PURE__ */ new Map();
|
|
1134
|
-
for (let n of e.templates ?? []) t.set(n.locale ? `${n.name}:${n.locale}` : n.name,
|
|
1135
|
-
for (let e of
|
|
1136
|
-
let n =
|
|
1137
|
-
n && t.set(e.locale ? `${e.name}:${e.locale}` : e.name,
|
|
205
|
+
for (let n of e.templates ?? []) t.set(n.locale ? `${n.name}:${n.locale}` : n.name, k(n));
|
|
206
|
+
for (let e of c()) {
|
|
207
|
+
let n = l(e.name, e.locale);
|
|
208
|
+
n && t.set(e.locale ? `${e.name}:${e.locale}` : e.name, k(n));
|
|
1138
209
|
}
|
|
1139
210
|
return [...t.values()];
|
|
1140
|
-
},
|
|
211
|
+
}, j = e.webhook === void 0 ? [] : [ne(r, u, e.webhook, s)], M = [
|
|
1141
212
|
{
|
|
1142
213
|
method: "GET",
|
|
1143
214
|
path: "/outbox",
|
|
1144
215
|
scope: "process",
|
|
1145
216
|
description: "Recent sent mail (dev capture buffer), newest first",
|
|
1146
|
-
handler: () =>
|
|
217
|
+
handler: () => V.succeed({
|
|
1147
218
|
kind: "json",
|
|
1148
|
-
data: { sends:
|
|
219
|
+
data: { sends: x.list() }
|
|
1149
220
|
})
|
|
1150
221
|
},
|
|
1151
222
|
{
|
|
@@ -1153,16 +224,16 @@ var w = class extends s.TaggedError()("MailError", {
|
|
|
1153
224
|
path: "/templates",
|
|
1154
225
|
scope: "declaration",
|
|
1155
226
|
description: "Discovered + registered email templates",
|
|
1156
|
-
handler: () =>
|
|
227
|
+
handler: () => V.succeed({
|
|
1157
228
|
kind: "json",
|
|
1158
|
-
data: { templates:
|
|
229
|
+
data: { templates: A() }
|
|
1159
230
|
})
|
|
1160
231
|
},
|
|
1161
232
|
{
|
|
1162
233
|
method: "POST",
|
|
1163
234
|
path: "/preview",
|
|
1164
235
|
description: "Render a template to html + text without sending — body { template, props, locale? }",
|
|
1165
|
-
handler: (e) =>
|
|
236
|
+
handler: (e) => V.gen(function* () {
|
|
1166
237
|
let t = {};
|
|
1167
238
|
try {
|
|
1168
239
|
t = JSON.parse(e.body || "{}");
|
|
@@ -1178,11 +249,11 @@ var w = class extends s.TaggedError()("MailError", {
|
|
|
1178
249
|
status: 400,
|
|
1179
250
|
data: { error: "missing \"template\"" }
|
|
1180
251
|
};
|
|
1181
|
-
let
|
|
1182
|
-
return
|
|
252
|
+
let n = O(t.template, t.locale);
|
|
253
|
+
return n ? yield* i(n, t.props).pipe(V.map((e) => ({
|
|
1183
254
|
kind: "json",
|
|
1184
255
|
data: e
|
|
1185
|
-
})),
|
|
256
|
+
})), V.catch((e) => V.succeed({
|
|
1186
257
|
kind: "json",
|
|
1187
258
|
status: 400,
|
|
1188
259
|
data: { error: e.message }
|
|
@@ -1193,55 +264,55 @@ var w = class extends s.TaggedError()("MailError", {
|
|
|
1193
264
|
};
|
|
1194
265
|
})
|
|
1195
266
|
}
|
|
1196
|
-
],
|
|
267
|
+
], P = [
|
|
1197
268
|
"network:outbound:*",
|
|
1198
269
|
"inspect:read",
|
|
1199
270
|
"inspect:write",
|
|
1200
|
-
...
|
|
271
|
+
...b.length > 0 ? ["store:write"] : []
|
|
1201
272
|
];
|
|
1202
273
|
return {
|
|
1203
|
-
...
|
|
1204
|
-
name:
|
|
1205
|
-
base:
|
|
274
|
+
...K({
|
|
275
|
+
name: q({
|
|
276
|
+
base: Q,
|
|
1206
277
|
alias: e.alias,
|
|
1207
278
|
instance: e.name
|
|
1208
279
|
}),
|
|
1209
|
-
baseName:
|
|
280
|
+
baseName: Q,
|
|
1210
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.",
|
|
1211
|
-
permissions:
|
|
1212
|
-
declaredEnv:
|
|
1213
|
-
egress:
|
|
282
|
+
permissions: P,
|
|
283
|
+
declaredEnv: Z.declared,
|
|
284
|
+
egress: z({
|
|
1214
285
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
1215
|
-
env:
|
|
286
|
+
env: n
|
|
1216
287
|
}),
|
|
1217
|
-
services:
|
|
1218
|
-
inspectEndpoints:
|
|
1219
|
-
...
|
|
1220
|
-
...
|
|
288
|
+
services: C,
|
|
289
|
+
inspectEndpoints: M,
|
|
290
|
+
...j.length > 0 ? { httpRoutes: j } : {},
|
|
291
|
+
...b.length > 0 ? { extendSchema: { tables: b } } : {},
|
|
1221
292
|
bindDataStore: async (e) => {
|
|
1222
|
-
await
|
|
293
|
+
await u.bind?.(e), await _.bind?.(e);
|
|
1223
294
|
},
|
|
1224
|
-
onActivate: (
|
|
1225
|
-
if (
|
|
1226
|
-
let e =
|
|
1227
|
-
|
|
295
|
+
onActivate: (t) => V.gen(function* () {
|
|
296
|
+
if (u.activate && (yield* V.promise(() => u.activate(t.env))), _.activate && (yield* V.promise(() => _.activate(t.env))), w) {
|
|
297
|
+
let e = S.scheduler.tick().pipe(V.repeat(G.spaced(B.millis(Math.max(50, T)))));
|
|
298
|
+
E = V.runFork(e);
|
|
1228
299
|
}
|
|
1229
|
-
|
|
1230
|
-
provider:
|
|
300
|
+
t.logger.info("mail active", {
|
|
301
|
+
provider: r.name,
|
|
1231
302
|
from: e.from ?? null,
|
|
1232
|
-
allowlist:
|
|
303
|
+
allowlist: o?.length ?? 0,
|
|
1233
304
|
suppression: typeof e.suppression == "string" ? e.suppression : e.suppression ? "custom" : "memory",
|
|
1234
305
|
idempotency: typeof e.idempotency == "string" ? e.idempotency : e.idempotency ? "custom" : "memory",
|
|
1235
|
-
scheduling:
|
|
1236
|
-
production:
|
|
306
|
+
scheduling: r.supportsScheduling === !0 ? "native" : "held",
|
|
307
|
+
production: s
|
|
1237
308
|
});
|
|
1238
309
|
}),
|
|
1239
|
-
onDeactivate: () =>
|
|
1240
|
-
|
|
310
|
+
onDeactivate: () => V.gen(function* () {
|
|
311
|
+
E !== void 0 && (yield* H.interrupt(E), E = void 0), u.deactivate && (yield* V.promise(() => u.deactivate())), _.deactivate && (yield* V.promise(() => _.deactivate()));
|
|
1241
312
|
})
|
|
1242
313
|
}),
|
|
1243
|
-
service:
|
|
314
|
+
service: S.service
|
|
1244
315
|
};
|
|
1245
316
|
};
|
|
1246
317
|
//#endregion
|
|
1247
|
-
export {
|
|
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 };
|