@voltro/plugin-mail 0.82.0 → 0.83.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +361 -0
- package/THIRD-PARTY-NOTICES.md +1 -463
- package/dist/index.d.ts +27 -21
- package/dist/index.js +411 -552
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,313 +1,231 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { definePlugin as
|
|
3
|
-
import { pluginEnv as
|
|
4
|
-
import { createLogger as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { FetchHttpClient as _, HttpClient as v, HttpClientRequest as y } from "@effect/platform";
|
|
1
|
+
import { Context as e, Duration as t, Effect as n, Fiber as r, Layer as i, ParseResult as a, Schedule as o, Schema as s } from "effect";
|
|
2
|
+
import { definePlugin as c, pluginInstanceName as l } from "@voltro/protocol";
|
|
3
|
+
import { pluginEnv as u } from "@voltro/env";
|
|
4
|
+
import { createLogger as d } from "@voltro/logger";
|
|
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";
|
|
8
7
|
//#region src/types.ts
|
|
9
|
-
var
|
|
10
|
-
provider:
|
|
11
|
-
message:
|
|
12
|
-
transient:
|
|
13
|
-
status:
|
|
14
|
-
}) {},
|
|
15
|
-
provider:
|
|
16
|
-
message:
|
|
17
|
-
}) {},
|
|
8
|
+
var w = class extends s.TaggedError()("MailError", {
|
|
9
|
+
provider: s.String,
|
|
10
|
+
message: s.String,
|
|
11
|
+
transient: s.Boolean,
|
|
12
|
+
status: s.optional(s.Number)
|
|
13
|
+
}) {}, T = class extends s.TaggedError()("MailWebhookError", {
|
|
14
|
+
provider: s.String,
|
|
15
|
+
message: s.String
|
|
16
|
+
}) {}, ne = (e) => ({
|
|
18
17
|
name: e.name,
|
|
19
18
|
props: e.props,
|
|
20
19
|
subject: e.subject,
|
|
21
20
|
render: e.render,
|
|
22
21
|
...e.locale === void 0 ? {} : { locale: e.locale },
|
|
23
22
|
...e.preview === void 0 ? {} : { preview: e.preview }
|
|
24
|
-
}),
|
|
23
|
+
}), E = class extends e.Tag("@voltro/plugin-mail/MailService")() {}, D = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, re = (e) => {
|
|
25
24
|
try {
|
|
26
|
-
let t =
|
|
25
|
+
let t = a.ArrayFormatter.formatErrorSync(e);
|
|
27
26
|
return t.length === 0 ? String(e) : t.map((e) => `${e.path.length > 0 ? e.path.join(".") : "(root)"}: ${e.message}`).join("; ");
|
|
28
27
|
} catch {
|
|
29
28
|
return String(e);
|
|
30
29
|
}
|
|
31
|
-
},
|
|
32
|
-
if (!
|
|
30
|
+
}, O = null, ie = async () => {
|
|
31
|
+
if (!O) {
|
|
33
32
|
let e = await import("@react-email/render");
|
|
34
|
-
if (
|
|
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)");
|
|
35
34
|
}
|
|
36
|
-
return
|
|
37
|
-
},
|
|
38
|
-
let
|
|
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({
|
|
39
38
|
provider: "template",
|
|
40
|
-
message: `invalid props for '${e.name}': ${
|
|
39
|
+
message: `invalid props for '${e.name}': ${re(t)}`.slice(0, 500),
|
|
41
40
|
transient: !1
|
|
42
|
-
}))), a =
|
|
41
|
+
}))), a = r.subject(i), o = r.render(i);
|
|
43
42
|
return {
|
|
44
43
|
subject: a,
|
|
45
|
-
html: yield*
|
|
46
|
-
try: async () => (await
|
|
47
|
-
catch: (t) => new
|
|
44
|
+
html: yield* n.tryPromise({
|
|
45
|
+
try: async () => (await ie())(o, { plainText: !1 }),
|
|
46
|
+
catch: (t) => new w({
|
|
48
47
|
provider: "template",
|
|
49
48
|
message: `render failed for '${e.name}': ${String(t)}`.slice(0, 500),
|
|
50
49
|
transient: !1
|
|
51
50
|
})
|
|
52
51
|
}),
|
|
53
|
-
text: yield*
|
|
54
|
-
try: async () => (await
|
|
55
|
-
catch: (t) => new
|
|
52
|
+
text: yield* n.tryPromise({
|
|
53
|
+
try: async () => (await ie())(o, { plainText: !0 }),
|
|
54
|
+
catch: (t) => new w({
|
|
56
55
|
provider: "template",
|
|
57
56
|
message: `render(text) failed for '${e.name}': ${String(t)}`.slice(0, 500),
|
|
58
57
|
transient: !1
|
|
59
58
|
})
|
|
60
59
|
})
|
|
61
60
|
};
|
|
62
|
-
}),
|
|
63
|
-
for (let t of e)
|
|
64
|
-
},
|
|
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 ? {
|
|
65
64
|
name: e.name,
|
|
66
65
|
locale: e.locale
|
|
67
|
-
} : { name: e.name }),
|
|
66
|
+
} : { name: e.name }), le = () => A.clear(), ue = (e) => {
|
|
68
67
|
if (typeof e != "object" || !e) return !1;
|
|
69
68
|
let t = e;
|
|
70
69
|
return typeof t.name == "string" && typeof t.subject == "function" && typeof t.render == "function" && t.props !== void 0;
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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)}`;
|
|
90
94
|
return {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
password: e.DB_PASSWORD ?? e.PG_PASSWORD ?? "app",
|
|
95
|
-
database: e.DB_DATABASE ?? e.PG_DATABASE ?? "app"
|
|
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)))
|
|
96
98
|
};
|
|
97
|
-
},
|
|
98
|
-
let
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
username: e.succeed(n.username),
|
|
102
|
-
password: e.succeed(c.make(n.password)),
|
|
103
|
-
database: e.succeed(n.database),
|
|
104
|
-
...t.maxConnections === void 0 ? {} : { maxConnections: e.succeed(t.maxConnections) }
|
|
99
|
+
}, he = () => {
|
|
100
|
+
let e = null, t = (e, t) => ({
|
|
101
|
+
tenant: L(e),
|
|
102
|
+
email: I(t)
|
|
105
103
|
});
|
|
106
104
|
return {
|
|
107
|
-
isSuppressed: (
|
|
108
|
-
let
|
|
109
|
-
|
|
110
|
-
let o = r.gen(function* () {
|
|
111
|
-
let r = yield* h.SqlClient;
|
|
112
|
-
return (yield* r`
|
|
113
|
-
SELECT 1 AS one FROM ${r(n)}
|
|
114
|
-
WHERE email = ${j(t)} AND tenant IN (${N(e)}, ${M}) LIMIT 1`).length > 0;
|
|
115
|
-
});
|
|
116
|
-
return r.tryPromise(() => a.runPromise(o)).pipe(r.catchAll(() => r.succeed(!1)));
|
|
117
|
-
},
|
|
118
|
-
suppress: (e, t, a) => {
|
|
119
|
-
let o = i;
|
|
120
|
-
if (!o) return r.void;
|
|
121
|
-
let s = r.gen(function* () {
|
|
122
|
-
let r = yield* h.SqlClient;
|
|
123
|
-
yield* r`
|
|
124
|
-
INSERT INTO ${r(n)} (tenant, email, reason, created_at)
|
|
125
|
-
VALUES (${N(e)}, ${j(t)}, ${a}, ${Date.now()})
|
|
126
|
-
ON CONFLICT (tenant, email) DO UPDATE SET reason = ${a}, created_at = ${Date.now()}`;
|
|
127
|
-
});
|
|
128
|
-
return r.tryPromise(() => o.runPromise(s)).pipe(r.catchAll((e) => r.sync(() => oe.warn("suppress write failed", {
|
|
129
|
-
email: j(t),
|
|
130
|
-
message: String(e)
|
|
131
|
-
}))));
|
|
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);
|
|
132
108
|
},
|
|
133
|
-
|
|
134
|
-
let
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
})
|
|
140
|
-
|
|
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;
|
|
141
119
|
},
|
|
142
|
-
|
|
143
|
-
let
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
CREATE TABLE IF NOT EXISTS ${e(n)} (
|
|
148
|
-
tenant TEXT NOT NULL,
|
|
149
|
-
email TEXT NOT NULL,
|
|
150
|
-
reason TEXT,
|
|
151
|
-
created_at BIGINT NOT NULL,
|
|
152
|
-
PRIMARY KEY (tenant, email)
|
|
153
|
-
)`;
|
|
154
|
-
})), i = t;
|
|
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));
|
|
155
125
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
i = null, e && await e.dispose();
|
|
126
|
+
bind: async (t) => {
|
|
127
|
+
e = t;
|
|
159
128
|
}
|
|
160
129
|
};
|
|
161
|
-
},
|
|
162
|
-
let e = /* @__PURE__ */ new Map(), t = (e, t) => `${
|
|
130
|
+
}, ge = d({ scope: "@voltro/plugin-mail" }), z = (e) => e ?? "*", B = () => {
|
|
131
|
+
let e = /* @__PURE__ */ new Map(), t = (e, t) => `${z(e)} ${t}`;
|
|
163
132
|
return {
|
|
164
|
-
reserve: (
|
|
165
|
-
let
|
|
166
|
-
if (e.has(
|
|
167
|
-
let t = e.get(
|
|
133
|
+
reserve: (r, i) => n.sync(() => {
|
|
134
|
+
let n = t(r, i);
|
|
135
|
+
if (e.has(n)) {
|
|
136
|
+
let t = e.get(n);
|
|
168
137
|
return t ? {
|
|
169
138
|
won: !1,
|
|
170
139
|
prior: t
|
|
171
140
|
} : { won: !1 };
|
|
172
141
|
}
|
|
173
|
-
return e.set(
|
|
142
|
+
return e.set(n, null), { won: !0 };
|
|
174
143
|
}),
|
|
175
|
-
record: (
|
|
144
|
+
record: (r, i, a) => n.sync(() => void e.set(t(r, i), a))
|
|
176
145
|
};
|
|
177
|
-
},
|
|
178
|
-
let
|
|
179
|
-
if (t) {
|
|
180
|
-
let e = new URL(t);
|
|
181
|
-
return {
|
|
182
|
-
host: e.hostname || "localhost",
|
|
183
|
-
port: e.port ? Number(e.port) : 5432,
|
|
184
|
-
username: decodeURIComponent(e.username || "app"),
|
|
185
|
-
password: decodeURIComponent(e.password || "app"),
|
|
186
|
-
database: e.pathname.replace(/^\//, "") || "app"
|
|
187
|
-
};
|
|
188
|
-
}
|
|
146
|
+
}, _e = () => {
|
|
147
|
+
let e = null;
|
|
189
148
|
return {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
username: e.succeed(n.username),
|
|
201
|
-
password: e.succeed(c.make(n.password)),
|
|
202
|
-
database: e.succeed(n.database),
|
|
203
|
-
...t.maxConnections === void 0 ? {} : { maxConnections: e.succeed(t.maxConnections) }
|
|
204
|
-
});
|
|
205
|
-
return {
|
|
206
|
-
reserve: (e, t) => {
|
|
207
|
-
let a = i;
|
|
208
|
-
if (!a) return r.succeed({ won: !0 });
|
|
209
|
-
let o = r.gen(function* () {
|
|
210
|
-
let r = yield* h.SqlClient;
|
|
211
|
-
if ((yield* r`
|
|
212
|
-
INSERT INTO ${r(n)} (tenant, key, created_at)
|
|
213
|
-
VALUES (${F(e)}, ${t}, ${Date.now()})
|
|
214
|
-
ON CONFLICT (tenant, key) DO NOTHING
|
|
215
|
-
RETURNING key`).length > 0) return { won: !0 };
|
|
216
|
-
let i = (yield* r`
|
|
217
|
-
SELECT result_id, result_provider FROM ${r(n)}
|
|
218
|
-
WHERE tenant = ${F(e)} AND key = ${t} LIMIT 1`)[0];
|
|
219
|
-
return i && i.result_id && i.result_provider ? {
|
|
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" ? {
|
|
220
159
|
won: !1,
|
|
221
160
|
prior: {
|
|
222
|
-
id:
|
|
223
|
-
provider:
|
|
161
|
+
id: n,
|
|
162
|
+
provider: r
|
|
224
163
|
}
|
|
225
164
|
} : { won: !1 };
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}), { 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 });
|
|
231
169
|
},
|
|
232
|
-
record: (
|
|
233
|
-
let
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
241
|
-
return r.tryPromise(() => o.runPromise(s)).pipe(r.catchAll((e) => r.sync(() => le.warn("idempotency record failed", {
|
|
242
|
-
key: t,
|
|
243
|
-
message: String(e)
|
|
244
|
-
}))));
|
|
245
|
-
},
|
|
246
|
-
activate: async (e) => {
|
|
247
|
-
let t = o.make(a(ue(e)));
|
|
248
|
-
await t.runPromise(r.gen(function* () {
|
|
249
|
-
let e = yield* h.SqlClient;
|
|
250
|
-
yield* e`
|
|
251
|
-
CREATE TABLE IF NOT EXISTS ${e(n)} (
|
|
252
|
-
tenant TEXT NOT NULL,
|
|
253
|
-
key TEXT NOT NULL,
|
|
254
|
-
result_id TEXT,
|
|
255
|
-
result_provider TEXT,
|
|
256
|
-
created_at BIGINT NOT NULL,
|
|
257
|
-
PRIMARY KEY (tenant, key)
|
|
258
|
-
)`;
|
|
259
|
-
})), i = t;
|
|
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;
|
|
260
179
|
},
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
i = null, e && await e.dispose();
|
|
180
|
+
bind: async (t) => {
|
|
181
|
+
e = t;
|
|
264
182
|
}
|
|
265
183
|
};
|
|
266
|
-
},
|
|
184
|
+
}, ve = d({ scope: "@voltro/plugin-mail" }), V = () => {
|
|
267
185
|
let e = /* @__PURE__ */ new Map();
|
|
268
186
|
return {
|
|
269
|
-
hold: (t) =>
|
|
270
|
-
claimDue: (t) =>
|
|
187
|
+
hold: (t) => n.sync(() => void e.set(t.id, t)),
|
|
188
|
+
claimDue: (t) => n.sync(() => {
|
|
271
189
|
let n = [];
|
|
272
190
|
for (let [r, i] of e) i.dueAt <= t && (n.push(i), e.delete(r));
|
|
273
191
|
return n;
|
|
274
192
|
}),
|
|
275
|
-
size: () =>
|
|
193
|
+
size: () => n.sync(() => e.size)
|
|
276
194
|
};
|
|
277
|
-
},
|
|
195
|
+
}, ye = 0, be = (e) => e.idempotencyKey ?? `held-${ye += 1}`, xe = (e) => ({
|
|
278
196
|
shouldHold: (t) => !e.providerSchedules && t !== void 0 && t.getTime() > Date.now(),
|
|
279
197
|
hold: (t) => {
|
|
280
198
|
let { scheduledAt: n, ...r } = t;
|
|
281
199
|
return e.store.hold({
|
|
282
|
-
id:
|
|
200
|
+
id: be(t),
|
|
283
201
|
dueAt: t.scheduledAt?.getTime() ?? Date.now(),
|
|
284
202
|
message: r
|
|
285
203
|
});
|
|
286
204
|
},
|
|
287
|
-
tick: (t = Date.now()) =>
|
|
288
|
-
let
|
|
289
|
-
for (let t of
|
|
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", {
|
|
290
208
|
id: t.id,
|
|
291
209
|
message: String(e)
|
|
292
210
|
}))));
|
|
293
|
-
return
|
|
211
|
+
return r.length;
|
|
294
212
|
}),
|
|
295
213
|
pending: () => e.store.size()
|
|
296
|
-
}),
|
|
214
|
+
}), Se = 0, Ce = (e) => `${e}-${Se += 1}`, we = (e) => e === void 0 || e === 408 || e === 429 || e >= 500, H = (e, t) => {
|
|
297
215
|
let n = t.status;
|
|
298
|
-
return new
|
|
216
|
+
return new w({
|
|
299
217
|
provider: e,
|
|
300
218
|
message: (t.message ?? "send failed").slice(0, 500),
|
|
301
|
-
transient: e === "smtp" ? n === void 0 || n >= 400 && n < 500 :
|
|
219
|
+
transient: e === "smtp" ? n === void 0 || n >= 400 && n < 500 : we(n),
|
|
302
220
|
...n === void 0 ? {} : { status: n }
|
|
303
221
|
});
|
|
304
|
-
},
|
|
222
|
+
}, U = (e, t, r, i, a) => n.tryPromise({
|
|
305
223
|
try: async () => {
|
|
306
224
|
let e = await fetch(t, {
|
|
307
225
|
method: "POST",
|
|
308
226
|
headers: {
|
|
309
227
|
"content-type": "application/json",
|
|
310
|
-
...
|
|
228
|
+
...r
|
|
311
229
|
},
|
|
312
230
|
body: JSON.stringify(i)
|
|
313
231
|
});
|
|
@@ -322,19 +240,19 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
322
240
|
},
|
|
323
241
|
catch: (t) => {
|
|
324
242
|
let n = t;
|
|
325
|
-
return
|
|
243
|
+
return H(e, {
|
|
326
244
|
...typeof n.status == "number" ? { status: n.status } : {},
|
|
327
245
|
message: n.message ?? t?.message ?? String(t)
|
|
328
246
|
});
|
|
329
247
|
}
|
|
330
|
-
}),
|
|
248
|
+
}), W = 500, Te = (e, t) => {
|
|
331
249
|
let n = e.stream === "broadcast" ? t?.broadcast ?? "broadcast" : t?.transactional;
|
|
332
250
|
return {
|
|
333
251
|
...n === void 0 ? {} : { route: n },
|
|
334
252
|
from: e.from,
|
|
335
|
-
to:
|
|
336
|
-
...e.cc ? { cc:
|
|
337
|
-
...e.bcc ? { bcc:
|
|
253
|
+
to: D(e.to),
|
|
254
|
+
...e.cc ? { cc: D(e.cc) } : {},
|
|
255
|
+
...e.bcc ? { bcc: D(e.bcc) } : {},
|
|
338
256
|
...e.replyTo ? { reply_to: [e.replyTo] } : {},
|
|
339
257
|
subject: e.subject,
|
|
340
258
|
...e.html === void 0 ? {} : { html: e.html },
|
|
@@ -352,35 +270,35 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
352
270
|
...e.contentType ? { content_type: e.contentType } : {}
|
|
353
271
|
})) } : {}
|
|
354
272
|
};
|
|
355
|
-
},
|
|
273
|
+
}, Ee = (e, t, r, i = Math.floor(Date.now() / 1e3), a = 300) => n.tryPromise({
|
|
356
274
|
try: async () => {
|
|
357
|
-
let
|
|
358
|
-
throw new
|
|
275
|
+
let n = (e) => {
|
|
276
|
+
throw new T({
|
|
359
277
|
provider: "lettermint",
|
|
360
278
|
message: e
|
|
361
279
|
});
|
|
362
280
|
}, o = (t) => Object.entries(e).find(([e]) => e.toLowerCase() === t)?.[1];
|
|
363
|
-
|
|
364
|
-
let s = o("x-lettermint-signature") ??
|
|
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 = [];
|
|
365
283
|
for (let e of s.split(",")) {
|
|
366
284
|
let [t, n] = e.trim().split("=", 2);
|
|
367
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());
|
|
368
286
|
}
|
|
369
|
-
(l === void 0 || u.length === 0) &&
|
|
370
|
-
let { createHmac: d, timingSafeEqual: f } = await import("node:crypto"), p = d("sha256",
|
|
371
|
-
u.some((e) => f(Buffer.from(e, "hex"), p)) ||
|
|
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");
|
|
372
290
|
try {
|
|
373
291
|
return JSON.parse(t);
|
|
374
292
|
} catch {
|
|
375
|
-
return
|
|
293
|
+
return n("body is not JSON");
|
|
376
294
|
}
|
|
377
295
|
},
|
|
378
|
-
catch: (e) => e instanceof
|
|
296
|
+
catch: (e) => e instanceof T ? e : new T({
|
|
379
297
|
provider: "lettermint",
|
|
380
298
|
message: e?.message ?? String(e)
|
|
381
299
|
})
|
|
382
|
-
}),
|
|
383
|
-
let t = (e.baseUrl ?? "https://api.lettermint.co/v1").replace(/\/+$/, ""),
|
|
300
|
+
}), De = (e) => {
|
|
301
|
+
let t = (e.baseUrl ?? "https://api.lettermint.co/v1").replace(/\/+$/, ""), r = {
|
|
384
302
|
"x-lettermint-token": e.projectToken,
|
|
385
303
|
accept: "application/json"
|
|
386
304
|
};
|
|
@@ -388,31 +306,31 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
388
306
|
name: "lettermint",
|
|
389
307
|
supportsScheduling: !0,
|
|
390
308
|
managesUnsubscribe: !0,
|
|
391
|
-
send: (
|
|
392
|
-
...
|
|
393
|
-
...
|
|
394
|
-
},
|
|
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) => ({
|
|
395
313
|
id: (await e.json()).message_id ?? "lettermint",
|
|
396
314
|
provider: "lettermint"
|
|
397
315
|
})),
|
|
398
|
-
sendBatch: (i) =>
|
|
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) => {
|
|
399
317
|
let t = await e.json();
|
|
400
|
-
return
|
|
318
|
+
return n.map((e, n) => ({
|
|
401
319
|
id: t[n]?.message_id ?? "lettermint",
|
|
402
320
|
provider: "lettermint"
|
|
403
321
|
}));
|
|
404
|
-
})).pipe(
|
|
405
|
-
webhook: { verify:
|
|
322
|
+
})).pipe(n.map((e) => e.flat())),
|
|
323
|
+
webhook: { verify: Ee }
|
|
406
324
|
};
|
|
407
|
-
},
|
|
325
|
+
}, Oe = (e) => ({
|
|
408
326
|
from: e.from,
|
|
409
|
-
to:
|
|
327
|
+
to: D(e.to),
|
|
410
328
|
subject: e.subject,
|
|
411
329
|
html: e.html,
|
|
412
330
|
text: e.text,
|
|
413
331
|
reply_to: e.replyTo,
|
|
414
|
-
cc: e.cc ?
|
|
415
|
-
bcc: e.bcc ?
|
|
332
|
+
cc: e.cc ? D(e.cc) : void 0,
|
|
333
|
+
bcc: e.bcc ? D(e.bcc) : void 0,
|
|
416
334
|
headers: e.headers,
|
|
417
335
|
tags: e.tags?.map((e) => ({
|
|
418
336
|
name: e,
|
|
@@ -424,34 +342,34 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
424
342
|
content: e.content,
|
|
425
343
|
...e.contentType ? { content_type: e.contentType } : {}
|
|
426
344
|
}))
|
|
427
|
-
}),
|
|
345
|
+
}), ke = (e) => ({
|
|
428
346
|
name: "resend",
|
|
429
347
|
supportsScheduling: !0,
|
|
430
|
-
send: (t) =>
|
|
348
|
+
send: (t) => U("resend", "https://api.resend.com/emails", { authorization: `Bearer ${e.apiKey}` }, Oe(t), async (e) => ({
|
|
431
349
|
id: (await e.json()).id ?? "resend",
|
|
432
350
|
provider: "resend"
|
|
433
351
|
})),
|
|
434
|
-
sendBatch: (t) =>
|
|
352
|
+
sendBatch: (t) => U("resend", "https://api.resend.com/emails/batch", { authorization: `Bearer ${e.apiKey}` }, t.map(Oe), async (e) => {
|
|
435
353
|
let n = (await e.json()).data ?? [];
|
|
436
354
|
return t.map((e, t) => ({
|
|
437
355
|
id: n[t]?.id ?? "resend",
|
|
438
356
|
provider: "resend"
|
|
439
357
|
}));
|
|
440
358
|
})
|
|
441
|
-
}),
|
|
359
|
+
}), Ae = (e) => ({
|
|
442
360
|
name: "postmark",
|
|
443
|
-
send: (t) =>
|
|
361
|
+
send: (t) => U("postmark", "https://api.postmarkapp.com/email", {
|
|
444
362
|
"x-postmark-server-token": e.serverToken,
|
|
445
363
|
accept: "application/json"
|
|
446
364
|
}, {
|
|
447
365
|
From: t.from,
|
|
448
|
-
To:
|
|
366
|
+
To: D(t.to).join(","),
|
|
449
367
|
Subject: t.subject,
|
|
450
368
|
HtmlBody: t.html,
|
|
451
369
|
TextBody: t.text,
|
|
452
370
|
ReplyTo: t.replyTo,
|
|
453
|
-
Cc: t.cc ?
|
|
454
|
-
Bcc: t.bcc ?
|
|
371
|
+
Cc: t.cc ? D(t.cc).join(",") : void 0,
|
|
372
|
+
Bcc: t.bcc ? D(t.bcc).join(",") : void 0,
|
|
455
373
|
MessageStream: t.stream === "broadcast" ? e.streams?.broadcast ?? "broadcast" : e.streams?.transactional ?? "outbound",
|
|
456
374
|
Headers: t.headers ? Object.entries(t.headers).map(([e, t]) => ({
|
|
457
375
|
Name: e,
|
|
@@ -467,44 +385,44 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
467
385
|
id: (await e.json()).MessageID ?? "postmark",
|
|
468
386
|
provider: "postmark"
|
|
469
387
|
}))
|
|
470
|
-
}),
|
|
388
|
+
}), je = (e) => [...e.text ? [{
|
|
471
389
|
type: "text/plain",
|
|
472
390
|
value: e.text
|
|
473
391
|
}] : [], ...e.html ? [{
|
|
474
392
|
type: "text/html",
|
|
475
393
|
value: e.html
|
|
476
|
-
}] : []],
|
|
477
|
-
to:
|
|
478
|
-
cc: e.cc ?
|
|
479
|
-
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,
|
|
480
398
|
subject: e.subject,
|
|
481
399
|
...e.scheduledAt ? { send_at: Math.floor(e.scheduledAt.getTime() / 1e3) } : {}
|
|
482
|
-
}),
|
|
400
|
+
}), Ne = (e) => e.attachments?.map((e) => ({
|
|
483
401
|
filename: e.filename,
|
|
484
402
|
content: e.content,
|
|
485
403
|
type: e.contentType ?? "application/octet-stream",
|
|
486
404
|
disposition: "attachment"
|
|
487
|
-
})),
|
|
405
|
+
})), Pe = (e) => {
|
|
488
406
|
let t = (e) => ({
|
|
489
|
-
personalizations: [
|
|
407
|
+
personalizations: [Me(e)],
|
|
490
408
|
from: { email: e.from },
|
|
491
409
|
reply_to: e.replyTo ? { email: e.replyTo } : void 0,
|
|
492
410
|
subject: e.subject,
|
|
493
|
-
content:
|
|
494
|
-
attachments:
|
|
411
|
+
content: je(e),
|
|
412
|
+
attachments: Ne(e)
|
|
495
413
|
});
|
|
496
414
|
return {
|
|
497
415
|
name: "sendgrid",
|
|
498
416
|
supportsScheduling: !0,
|
|
499
|
-
send: (n) =>
|
|
417
|
+
send: (n) => U("sendgrid", "https://api.sendgrid.com/v3/mail/send", { authorization: `Bearer ${e.apiKey}` }, t(n), async (e) => ({
|
|
500
418
|
id: e.headers.get("x-message-id") ?? "sendgrid",
|
|
501
419
|
provider: "sendgrid"
|
|
502
420
|
})),
|
|
503
|
-
sendBatch: (t) =>
|
|
504
|
-
personalizations: t.map(
|
|
421
|
+
sendBatch: (t) => U("sendgrid", "https://api.sendgrid.com/v3/mail/send", { authorization: `Bearer ${e.apiKey}` }, {
|
|
422
|
+
personalizations: t.map(Me),
|
|
505
423
|
from: { email: t[0]?.from },
|
|
506
424
|
subject: t[0]?.subject,
|
|
507
|
-
content:
|
|
425
|
+
content: je(t[0] ?? {})
|
|
508
426
|
}, async (e) => {
|
|
509
427
|
let n = e.headers.get("x-message-id") ?? "sendgrid";
|
|
510
428
|
return t.map((e, t) => ({
|
|
@@ -513,35 +431,35 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
513
431
|
}));
|
|
514
432
|
})
|
|
515
433
|
};
|
|
516
|
-
},
|
|
434
|
+
}, Fe = (e, t) => {
|
|
517
435
|
let n = t, r = n.response?.status;
|
|
518
|
-
return
|
|
436
|
+
return H(e, {
|
|
519
437
|
...typeof r == "number" ? { status: r } : {},
|
|
520
438
|
message: n.message ?? String(t)
|
|
521
439
|
});
|
|
522
|
-
},
|
|
523
|
-
let i = yield* (yield*
|
|
440
|
+
}, Ie = (e, t, r) => n.gen(function* () {
|
|
441
|
+
let i = yield* (yield* S.HttpClient).execute(t);
|
|
524
442
|
if (i.status < 200 || i.status >= 300) {
|
|
525
|
-
let t = yield* i.text.pipe(
|
|
526
|
-
return yield*
|
|
443
|
+
let t = yield* i.text.pipe(n.catchAll(() => n.succeed("")));
|
|
444
|
+
return yield* n.fail(H(e, {
|
|
527
445
|
status: i.status,
|
|
528
446
|
message: t || `HTTP ${i.status}`
|
|
529
447
|
}));
|
|
530
448
|
}
|
|
531
|
-
return yield*
|
|
532
|
-
}).pipe(
|
|
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) => {
|
|
533
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");
|
|
534
452
|
return {
|
|
535
453
|
authorization: `AWS4-HMAC-SHA256 Credential=${e.accessKeyId}/${p}, SignedHeaders=${d}, Signature=${h}`,
|
|
536
454
|
"x-amz-date": c,
|
|
537
455
|
...e.sessionToken ? { "x-amz-security-token": e.sessionToken } : {}
|
|
538
456
|
};
|
|
539
|
-
} }),
|
|
457
|
+
} }), Re = (e) => JSON.stringify({
|
|
540
458
|
FromEmailAddress: e.from,
|
|
541
459
|
Destination: {
|
|
542
|
-
ToAddresses:
|
|
543
|
-
...e.cc ? { CcAddresses:
|
|
544
|
-
...e.bcc ? { BccAddresses:
|
|
460
|
+
ToAddresses: D(e.to),
|
|
461
|
+
...e.cc ? { CcAddresses: D(e.cc) } : {},
|
|
462
|
+
...e.bcc ? { BccAddresses: D(e.bcc) } : {}
|
|
545
463
|
},
|
|
546
464
|
...e.replyTo ? { ReplyToAddresses: [e.replyTo] } : {},
|
|
547
465
|
Content: { Simple: {
|
|
@@ -560,51 +478,51 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
560
478
|
} } : {}
|
|
561
479
|
}
|
|
562
480
|
} }
|
|
563
|
-
}),
|
|
564
|
-
let t =
|
|
481
|
+
}), ze = (e) => {
|
|
482
|
+
let t = Le(e), r = `https://email.${e.region}.amazonaws.com`, i = "/v2/email/outbound-emails";
|
|
565
483
|
return {
|
|
566
484
|
name: "ses",
|
|
567
|
-
send: (e) =>
|
|
568
|
-
let a =
|
|
569
|
-
try: () => t.sign(
|
|
570
|
-
catch: (e) =>
|
|
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) })
|
|
571
489
|
});
|
|
572
|
-
return yield*
|
|
490
|
+
return yield* Ie("ses", C.post(`${r}${i}`).pipe(C.setHeaders({
|
|
573
491
|
"content-type": "application/json",
|
|
574
492
|
...o
|
|
575
|
-
}),
|
|
493
|
+
}), C.bodyUnsafeJson(JSON.parse(a))), (e) => e.json.pipe(n.map((e) => ({
|
|
576
494
|
id: e.MessageId ?? "ses",
|
|
577
495
|
provider: "ses"
|
|
578
496
|
}))));
|
|
579
497
|
})
|
|
580
498
|
};
|
|
581
|
-
},
|
|
499
|
+
}, Be = (e) => {
|
|
582
500
|
let t = [["from", e.from], ["subject", e.subject]];
|
|
583
|
-
for (let n of
|
|
584
|
-
if (e.cc) for (let n of
|
|
585
|
-
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]);
|
|
586
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]);
|
|
587
505
|
if (e.tags) for (let n of e.tags) t.push(["o:tag", n]);
|
|
588
506
|
return e.scheduledAt && t.push(["o:deliverytime", e.scheduledAt.toUTCString()]), t;
|
|
589
|
-
},
|
|
507
|
+
}, Ve = (e) => {
|
|
590
508
|
let t = `${e.baseUrl ?? "https://api.mailgun.net"}/v3/${e.domain}/messages`;
|
|
591
509
|
return {
|
|
592
510
|
name: "mailgun",
|
|
593
511
|
supportsScheduling: !0,
|
|
594
|
-
send: (
|
|
512
|
+
send: (r) => {
|
|
595
513
|
let i = new URLSearchParams();
|
|
596
|
-
for (let [e, t] of
|
|
597
|
-
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) => ({
|
|
598
516
|
id: e.id ?? "mailgun",
|
|
599
517
|
provider: "mailgun"
|
|
600
518
|
}))));
|
|
601
519
|
}
|
|
602
520
|
};
|
|
603
|
-
},
|
|
521
|
+
}, G = (e) => {
|
|
604
522
|
let t = null;
|
|
605
523
|
return {
|
|
606
524
|
name: "smtp",
|
|
607
|
-
send: (
|
|
525
|
+
send: (r) => n.tryPromise({
|
|
608
526
|
try: async () => {
|
|
609
527
|
if (!t) {
|
|
610
528
|
let n = await import("nodemailer"), r = n.createTransport ?? n.default?.createTransport;
|
|
@@ -618,16 +536,16 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
618
536
|
}
|
|
619
537
|
return {
|
|
620
538
|
id: (await t.sendMail({
|
|
621
|
-
from:
|
|
622
|
-
to:
|
|
623
|
-
cc:
|
|
624
|
-
bcc:
|
|
625
|
-
replyTo:
|
|
626
|
-
subject:
|
|
627
|
-
html:
|
|
628
|
-
text:
|
|
629
|
-
headers:
|
|
630
|
-
attachments:
|
|
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) => ({
|
|
631
549
|
filename: e.filename,
|
|
632
550
|
content: e.content,
|
|
633
551
|
encoding: "base64",
|
|
@@ -639,53 +557,53 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
639
557
|
},
|
|
640
558
|
catch: (e) => {
|
|
641
559
|
let t = e;
|
|
642
|
-
return
|
|
560
|
+
return H("smtp", {
|
|
643
561
|
...typeof t.responseCode == "number" ? { status: t.responseCode } : {},
|
|
644
562
|
message: t.message ?? String(e)
|
|
645
563
|
});
|
|
646
564
|
}
|
|
647
565
|
})
|
|
648
566
|
};
|
|
649
|
-
},
|
|
567
|
+
}, He = (e) => ({
|
|
650
568
|
name: "console",
|
|
651
|
-
send: (t) =>
|
|
652
|
-
let n = `✉ to=${
|
|
569
|
+
send: (t) => n.sync(() => {
|
|
570
|
+
let n = `✉ to=${D(t.to).join(",")} subject=${JSON.stringify(t.subject)}`;
|
|
653
571
|
return e ? e(n, { from: t.from }) : process.stdout.write(`[mail:console] ${n}\n`), {
|
|
654
|
-
id:
|
|
572
|
+
id: Ce("console"),
|
|
655
573
|
provider: "console"
|
|
656
574
|
};
|
|
657
575
|
})
|
|
658
|
-
}),
|
|
659
|
-
|
|
660
|
-
},
|
|
576
|
+
}), K = [], Ue = () => [...K], We = () => {
|
|
577
|
+
K.length = 0;
|
|
578
|
+
}, Ge = () => ({
|
|
661
579
|
name: "memory",
|
|
662
|
-
send: (e) =>
|
|
663
|
-
id:
|
|
580
|
+
send: (e) => n.sync(() => (K.push(e), {
|
|
581
|
+
id: Ce("mem"),
|
|
664
582
|
provider: "memory"
|
|
665
583
|
}))
|
|
666
|
-
}),
|
|
584
|
+
}), q = d({ scope: "@voltro/plugin-mail" }), Ke = (e) => {
|
|
667
585
|
if (e.provider !== void 0 && typeof e.provider != "string") return e.provider;
|
|
668
586
|
let t = e.env ?? {}, n = e.provider ?? t.MAIL_PROVIDER ?? "console", r = (e, t) => {
|
|
669
587
|
if (!t) throw Error(`@voltro/plugin-mail: provider '${n}' needs apiKey or ${e}`);
|
|
670
588
|
return t;
|
|
671
589
|
};
|
|
672
590
|
switch (n) {
|
|
673
|
-
case "lettermint": return
|
|
674
|
-
case "resend": return
|
|
675
|
-
case "postmark": return
|
|
676
|
-
case "sendgrid": return
|
|
677
|
-
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({
|
|
678
596
|
region: r("AWS_REGION", t.AWS_REGION),
|
|
679
597
|
accessKeyId: r("AWS_ACCESS_KEY_ID", t.AWS_ACCESS_KEY_ID),
|
|
680
598
|
secretAccessKey: r("AWS_SECRET_ACCESS_KEY", t.AWS_SECRET_ACCESS_KEY),
|
|
681
599
|
...t.AWS_SESSION_TOKEN ? { sessionToken: t.AWS_SESSION_TOKEN } : {}
|
|
682
600
|
});
|
|
683
|
-
case "mailgun": return
|
|
601
|
+
case "mailgun": return Ve({
|
|
684
602
|
apiKey: r("MAILGUN_API_KEY", e.apiKey ?? t.MAILGUN_API_KEY),
|
|
685
603
|
domain: r("MAILGUN_DOMAIN", t.MAILGUN_DOMAIN),
|
|
686
604
|
...t.MAILGUN_BASE_URL ? { baseUrl: t.MAILGUN_BASE_URL } : {}
|
|
687
605
|
});
|
|
688
|
-
case "smtp": return t.SMTP_URL ?
|
|
606
|
+
case "smtp": return t.SMTP_URL ? G({ url: t.SMTP_URL }) : G({
|
|
689
607
|
host: r("SMTP_URL/SMTP_HOST", t.SMTP_HOST),
|
|
690
608
|
...t.SMTP_PORT ? { port: Number(t.SMTP_PORT) } : {},
|
|
691
609
|
secure: t.SMTP_SECURE === "true",
|
|
@@ -694,50 +612,50 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
694
612
|
pass: t.SMTP_PASS
|
|
695
613
|
} } : {}
|
|
696
614
|
});
|
|
697
|
-
case "memory": return
|
|
698
|
-
default: return
|
|
615
|
+
case "memory": return Ge();
|
|
616
|
+
default: return He((e, t) => q.info(e, t));
|
|
699
617
|
}
|
|
700
|
-
},
|
|
701
|
-
let t = e.attempts ?? 3,
|
|
702
|
-
for (let t of e.templates ?? [])
|
|
703
|
-
let u = (e, t) => (t ?
|
|
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) => {
|
|
704
622
|
if (d.set(e, t), d.size > 500) {
|
|
705
623
|
let e = d.keys().next().value;
|
|
706
624
|
e !== void 0 && d.delete(e);
|
|
707
625
|
}
|
|
708
|
-
}, m = e.idempotency ??
|
|
709
|
-
store: e.schedule ??
|
|
626
|
+
}, m = e.idempotency ?? B(), h = xe({
|
|
627
|
+
store: e.schedule ?? V(),
|
|
710
628
|
providerSchedules: e.provider.supportsScheduling === !0,
|
|
711
|
-
flush: (e) =>
|
|
629
|
+
flush: (e) => v(e)
|
|
712
630
|
}), g = (t) => {
|
|
713
|
-
if (t.stream !== "broadcast" || e.provider.managesUnsubscribe === !0) return
|
|
714
|
-
let
|
|
715
|
-
if (
|
|
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({
|
|
716
634
|
provider: e.provider.name,
|
|
717
635
|
message: `a broadcast needs \`unsubscribe: { oneClickUrl }\` (an https URL) — '${e.provider.name}' does not unsubscribe recipients itself`,
|
|
718
636
|
transient: !1
|
|
719
637
|
}));
|
|
720
|
-
let i = [`<${
|
|
721
|
-
return
|
|
638
|
+
let i = [`<${r.oneClickUrl}>`, ...r.mailto === void 0 ? [] : [`<mailto:${r.mailto.replace(/^mailto:/, "")}>`]];
|
|
639
|
+
return n.succeed({
|
|
722
640
|
"List-Unsubscribe": i.join(", "),
|
|
723
641
|
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
|
|
724
642
|
});
|
|
725
|
-
}, _ = (t) =>
|
|
643
|
+
}, _ = (t) => n.gen(function* () {
|
|
726
644
|
let i = t.from ?? e.from;
|
|
727
|
-
if (!i) return yield*
|
|
645
|
+
if (!i) return yield* n.fail(new w({
|
|
728
646
|
provider: e.provider.name,
|
|
729
647
|
message: "no `from` — set mailPlugin({ from }) or pass message.from",
|
|
730
648
|
transient: !1
|
|
731
649
|
}));
|
|
732
|
-
let
|
|
650
|
+
let o = D(t.to);
|
|
733
651
|
if (a) {
|
|
734
|
-
let n =
|
|
735
|
-
if (n.length === 0) return
|
|
736
|
-
to:
|
|
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(","),
|
|
737
655
|
subject: t.subject
|
|
738
656
|
}), e.onSent?.({
|
|
739
657
|
id: "skipped-allowlist",
|
|
740
|
-
to:
|
|
658
|
+
to: o,
|
|
741
659
|
from: i,
|
|
742
660
|
subject: t.subject,
|
|
743
661
|
provider: "skipped",
|
|
@@ -750,16 +668,16 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
750
668
|
provider: "skipped"
|
|
751
669
|
}
|
|
752
670
|
};
|
|
753
|
-
|
|
671
|
+
o = n;
|
|
754
672
|
}
|
|
755
673
|
let c = t.tenantId ?? null, l = [];
|
|
756
|
-
for (let e of
|
|
757
|
-
if (l.length === 0) return
|
|
758
|
-
to:
|
|
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(","),
|
|
759
677
|
subject: t.subject
|
|
760
678
|
}), e.onSent?.({
|
|
761
679
|
id: "skipped-suppressed",
|
|
762
|
-
to:
|
|
680
|
+
to: o,
|
|
763
681
|
from: i,
|
|
764
682
|
subject: t.subject,
|
|
765
683
|
provider: "suppressed",
|
|
@@ -772,7 +690,7 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
772
690
|
provider: "suppressed"
|
|
773
691
|
}
|
|
774
692
|
};
|
|
775
|
-
|
|
693
|
+
o = l;
|
|
776
694
|
let u = t.replyTo ?? e.replyTo, d = yield* g(t), f = d === void 0 ? t.headers : {
|
|
777
695
|
...t.headers,
|
|
778
696
|
...d
|
|
@@ -782,35 +700,35 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
782
700
|
message: {
|
|
783
701
|
...t,
|
|
784
702
|
from: i,
|
|
785
|
-
to:
|
|
703
|
+
to: o,
|
|
786
704
|
...u === void 0 ? {} : { replyTo: u },
|
|
787
705
|
...f === void 0 ? {} : { headers: f }
|
|
788
706
|
}
|
|
789
707
|
};
|
|
790
|
-
}),
|
|
791
|
-
let
|
|
792
|
-
return e.provider.send(t).pipe(
|
|
793
|
-
|
|
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", {
|
|
794
712
|
provider: i.provider,
|
|
795
713
|
id: i.id,
|
|
796
|
-
to:
|
|
714
|
+
to: r.join(",")
|
|
797
715
|
}), e.onSent?.({
|
|
798
716
|
id: i.id,
|
|
799
|
-
to:
|
|
717
|
+
to: r,
|
|
800
718
|
from: t.from,
|
|
801
719
|
subject: t.subject,
|
|
802
720
|
provider: i.provider,
|
|
803
721
|
ok: !0,
|
|
804
722
|
ts: Date.now()
|
|
805
723
|
});
|
|
806
|
-
})),
|
|
807
|
-
|
|
724
|
+
})), n.tapError((i) => n.sync(() => {
|
|
725
|
+
q.warn("mail failed", {
|
|
808
726
|
provider: i.provider,
|
|
809
727
|
status: i.status,
|
|
810
728
|
message: i.message
|
|
811
729
|
}), e.onSent?.({
|
|
812
730
|
id: `err-${Date.now()}`,
|
|
813
|
-
to:
|
|
731
|
+
to: r,
|
|
814
732
|
from: t.from,
|
|
815
733
|
subject: t.subject,
|
|
816
734
|
provider: i.provider,
|
|
@@ -819,14 +737,14 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
819
737
|
ts: Date.now()
|
|
820
738
|
});
|
|
821
739
|
})));
|
|
822
|
-
},
|
|
740
|
+
}, v = (t) => n.gen(function* () {
|
|
823
741
|
let n = yield* _(t);
|
|
824
|
-
return n.kind === "skip" ? n.result : h.shouldHold(t.scheduledAt) ? (yield* h.hold(n.message),
|
|
742
|
+
return n.kind === "skip" ? n.result : h.shouldHold(t.scheduledAt) ? (yield* h.hold(n.message), q.info("mail scheduled (held for later flush)", {
|
|
825
743
|
subject: n.message.subject,
|
|
826
744
|
dueAt: t.scheduledAt?.toISOString()
|
|
827
745
|
}), e.onSent?.({
|
|
828
746
|
id: "scheduled",
|
|
829
|
-
to:
|
|
747
|
+
to: D(n.message.to),
|
|
830
748
|
from: n.message.from,
|
|
831
749
|
subject: n.message.subject,
|
|
832
750
|
provider: "scheduled",
|
|
@@ -835,24 +753,24 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
835
753
|
}), {
|
|
836
754
|
id: "scheduled",
|
|
837
755
|
provider: "scheduled"
|
|
838
|
-
}) : yield*
|
|
839
|
-
}),
|
|
840
|
-
let
|
|
841
|
-
if (!
|
|
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 ?? {
|
|
842
760
|
id: "idempotent-replay",
|
|
843
761
|
provider: "idempotent"
|
|
844
762
|
};
|
|
845
|
-
let i = yield*
|
|
763
|
+
let i = yield* r;
|
|
846
764
|
return yield* m.record(e, t, i), i;
|
|
847
|
-
}),
|
|
765
|
+
}), b = (e) => n.suspend(() => {
|
|
848
766
|
let t = u(e.template, e.locale);
|
|
849
|
-
if (!t) return
|
|
767
|
+
if (!t) return n.fail(new w({
|
|
850
768
|
provider: "template",
|
|
851
769
|
message: `unknown template '${e.template}'${e.locale ? ` (locale '${e.locale}')` : ""}`,
|
|
852
770
|
transient: !1
|
|
853
771
|
}));
|
|
854
|
-
let
|
|
855
|
-
return (i ?
|
|
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) => ({
|
|
856
774
|
to: e.to,
|
|
857
775
|
subject: t.subject,
|
|
858
776
|
html: t.html,
|
|
@@ -871,31 +789,31 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
871
789
|
...e.unsubscribe === void 0 ? {} : { unsubscribe: e.unsubscribe },
|
|
872
790
|
...e.metadata === void 0 ? {} : { metadata: e.metadata }
|
|
873
791
|
})));
|
|
874
|
-
}),
|
|
792
|
+
}), x = (e) => "template" in e ? b(e) : n.succeed(e);
|
|
875
793
|
return {
|
|
876
794
|
service: {
|
|
877
|
-
send: (e) =>
|
|
878
|
-
let t = yield*
|
|
879
|
-
return yield*
|
|
795
|
+
send: (e) => n.gen(function* () {
|
|
796
|
+
let t = yield* x(e);
|
|
797
|
+
return yield* y(t.tenantId ?? null, t.idempotencyKey, v(t));
|
|
880
798
|
}),
|
|
881
|
-
sendBatch: (t) =>
|
|
882
|
-
let
|
|
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));
|
|
883
801
|
if (e.provider.sendBatch && !i) {
|
|
884
802
|
let t = [], i = [];
|
|
885
|
-
for (let e of
|
|
803
|
+
for (let e of r) {
|
|
886
804
|
let n = yield* _(e);
|
|
887
805
|
n.kind === "skip" ? i.push(n.result) : (i.push(t.length), t.push(n.message));
|
|
888
806
|
}
|
|
889
|
-
let a = t.length > 0 ? yield* e.provider.sendBatch(t).pipe(
|
|
890
|
-
t.forEach((t,
|
|
891
|
-
let i = n
|
|
892
|
-
i && (
|
|
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)", {
|
|
893
811
|
provider: i.provider,
|
|
894
812
|
id: i.id,
|
|
895
|
-
to:
|
|
813
|
+
to: D(t.to).join(",")
|
|
896
814
|
}), e.onSent?.({
|
|
897
815
|
id: i.id,
|
|
898
|
-
to:
|
|
816
|
+
to: D(t.to),
|
|
899
817
|
from: t.from,
|
|
900
818
|
subject: t.subject,
|
|
901
819
|
provider: i.provider,
|
|
@@ -903,7 +821,7 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
903
821
|
ts: Date.now()
|
|
904
822
|
}));
|
|
905
823
|
});
|
|
906
|
-
})),
|
|
824
|
+
})), n.tapError((e) => n.sync(() => q.warn("mail batch failed", {
|
|
907
825
|
provider: e.provider,
|
|
908
826
|
status: e.status,
|
|
909
827
|
message: e.message
|
|
@@ -913,11 +831,11 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
913
831
|
provider: e.provider.name
|
|
914
832
|
} : t);
|
|
915
833
|
}
|
|
916
|
-
return yield*
|
|
834
|
+
return yield* n.forEach(r, (e) => y(e.tenantId ?? null, e.idempotencyKey, v(e)));
|
|
917
835
|
}),
|
|
918
|
-
suppress:
|
|
919
|
-
unsuppress:
|
|
920
|
-
isSuppressed:
|
|
836
|
+
suppress: r.suppress,
|
|
837
|
+
unsuppress: r.unsuppress,
|
|
838
|
+
isSuppressed: r.isSuppressed,
|
|
921
839
|
provider: {
|
|
922
840
|
name: e.provider.name,
|
|
923
841
|
managesUnsubscribe: e.provider.managesUnsubscribe === !0
|
|
@@ -925,7 +843,7 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
925
843
|
},
|
|
926
844
|
scheduler: h
|
|
927
845
|
};
|
|
928
|
-
},
|
|
846
|
+
}, qe = (e) => J(e).service, Je = (e) => i.succeed(E, qe(e)), Y = (e, t, n, r, i, a = {}) => {
|
|
929
847
|
if (!t) return null;
|
|
930
848
|
let o = a.metadata !== null && typeof a.metadata == "object" ? Object.fromEntries(Object.entries(a.metadata).filter(([, e]) => typeof e == "string")) : void 0;
|
|
931
849
|
return {
|
|
@@ -937,36 +855,36 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
937
855
|
...a.eventId === void 0 ? {} : { eventId: a.eventId },
|
|
938
856
|
...o === void 0 ? {} : { metadata: o }
|
|
939
857
|
};
|
|
940
|
-
},
|
|
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) => {
|
|
941
859
|
let n = [], r = (e) => {
|
|
942
860
|
e && n.push(e);
|
|
943
861
|
};
|
|
944
862
|
if (e === "sendgrid" && Array.isArray(t)) {
|
|
945
|
-
for (let e of t) r(
|
|
863
|
+
for (let e of t) r(Y($e(String(e.event)), e.email, "sendgrid", e.sg_message_id, e));
|
|
946
864
|
return n;
|
|
947
865
|
}
|
|
948
866
|
let i = t ?? {};
|
|
949
867
|
if (e === "lettermint") {
|
|
950
868
|
let e = String(i.event), a = i.data ?? {}, o = typeof i.id == "string" ? i.id : void 0;
|
|
951
|
-
return e === "suppression.added" ? (a.type === "email" && r(
|
|
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, {
|
|
952
870
|
eventId: o,
|
|
953
871
|
metadata: a.metadata
|
|
954
872
|
})), n);
|
|
955
873
|
}
|
|
956
874
|
if (e === "resend") {
|
|
957
875
|
let e = i.data ?? {}, a = Array.isArray(e.to) ? e.to[0] : e.to ?? e.email;
|
|
958
|
-
return r(
|
|
876
|
+
return r(Y(Ze(String(i.type)), a, "resend", e.email_id, t)), n;
|
|
959
877
|
}
|
|
960
878
|
if (e === "postmark") {
|
|
961
879
|
let e = i.Email ?? i.Recipient;
|
|
962
|
-
return r(
|
|
880
|
+
return r(Y(Qe(String(i.RecordType)), e, "postmark", i.MessageID, t, { metadata: i.Metadata })), n;
|
|
963
881
|
}
|
|
964
882
|
return n;
|
|
965
|
-
},
|
|
966
|
-
let
|
|
967
|
-
for (let e of
|
|
968
|
-
return
|
|
969
|
-
}),
|
|
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) => {
|
|
970
888
|
let t = [];
|
|
971
889
|
return {
|
|
972
890
|
record: (n) => {
|
|
@@ -977,18 +895,18 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
977
895
|
t.length = 0;
|
|
978
896
|
}
|
|
979
897
|
};
|
|
980
|
-
},
|
|
898
|
+
}, nt = {
|
|
981
899
|
lettermint: "api.lettermint.co",
|
|
982
900
|
resend: "api.resend.com",
|
|
983
901
|
postmark: "api.postmarkapp.com",
|
|
984
902
|
sendgrid: "api.sendgrid.com"
|
|
985
|
-
},
|
|
903
|
+
}, rt = (e) => {
|
|
986
904
|
try {
|
|
987
905
|
return new URL(e).host || void 0;
|
|
988
906
|
} catch {
|
|
989
907
|
return;
|
|
990
908
|
}
|
|
991
|
-
},
|
|
909
|
+
}, it = (e) => {
|
|
992
910
|
let t = e.env ?? {}, n = (e) => [{
|
|
993
911
|
destination: e,
|
|
994
912
|
purpose: "outgoing email: recipients, subject and body",
|
|
@@ -999,15 +917,15 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
999
917
|
let t = e.provider.name;
|
|
1000
918
|
return t === "console" || t === "memory" ? [] : n(`runtime:the '${t}' mail provider`);
|
|
1001
919
|
}
|
|
1002
|
-
let r = e.provider ?? t.MAIL_PROVIDER ?? "console", i =
|
|
920
|
+
let r = e.provider ?? t.MAIL_PROVIDER ?? "console", i = nt[r];
|
|
1003
921
|
if (i !== void 0) return n(i);
|
|
1004
922
|
switch (r) {
|
|
1005
923
|
case "ses": return n(t.AWS_REGION ? `email.${t.AWS_REGION}.amazonaws.com` : "runtime:email.<AWS_REGION>.amazonaws.com");
|
|
1006
|
-
case "mailgun": return n((t.MAILGUN_BASE_URL ?
|
|
1007
|
-
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");
|
|
1008
926
|
default: return [];
|
|
1009
927
|
}
|
|
1010
|
-
},
|
|
928
|
+
}, Z = (e, t) => {
|
|
1011
929
|
if (t > 6) return null;
|
|
1012
930
|
switch (e._tag) {
|
|
1013
931
|
case "StringKeyword": return "";
|
|
@@ -1017,17 +935,17 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
1017
935
|
case "Enums": return e.enums.length > 0 ? e.enums[0][1] : null;
|
|
1018
936
|
case "TypeLiteral": {
|
|
1019
937
|
let n = {};
|
|
1020
|
-
for (let r of e.propertySignatures) typeof r.name == "string" && (n[r.name] =
|
|
938
|
+
for (let r of e.propertySignatures) typeof r.name == "string" && (n[r.name] = Z(r.type, t + 1));
|
|
1021
939
|
return n;
|
|
1022
940
|
}
|
|
1023
941
|
case "TupleType": return [];
|
|
1024
|
-
case "Union": return e.types.length > 0 ?
|
|
1025
|
-
case "Refinement": return
|
|
1026
|
-
case "Transformation": return
|
|
1027
|
-
case "Suspend": 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);
|
|
1028
946
|
default: return null;
|
|
1029
947
|
}
|
|
1030
|
-
},
|
|
948
|
+
}, at = (e) => Z(e.ast, 0), Q = u([
|
|
1031
949
|
{
|
|
1032
950
|
name: "MAIL_PROVIDER",
|
|
1033
951
|
required: !1,
|
|
@@ -1147,101 +1065,29 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
1147
1065
|
required: !1,
|
|
1148
1066
|
secret: !1,
|
|
1149
1067
|
description: "Comma-separated recipient allowlist; in dev, mail to addresses outside it is skipped."
|
|
1150
|
-
},
|
|
1151
|
-
{
|
|
1152
|
-
name: "DB_URL",
|
|
1153
|
-
required: !1,
|
|
1154
|
-
secret: !0,
|
|
1155
|
-
description: "Postgres connection URL for the postgres suppression store (credential-carrying). Falls back to DB_PRIMARY_URL."
|
|
1156
|
-
},
|
|
1157
|
-
{
|
|
1158
|
-
name: "DB_PRIMARY_URL",
|
|
1159
|
-
required: !1,
|
|
1160
|
-
secret: !0,
|
|
1161
|
-
description: "Fallback Postgres connection URL for the postgres suppression store (credential-carrying)."
|
|
1162
|
-
},
|
|
1163
|
-
{
|
|
1164
|
-
name: "DB_HOST",
|
|
1165
|
-
required: !1,
|
|
1166
|
-
secret: !1,
|
|
1167
|
-
description: "Postgres host for the postgres suppression store when no URL is set. Falls back to PG_HOST, then localhost."
|
|
1168
|
-
},
|
|
1169
|
-
{
|
|
1170
|
-
name: "PG_HOST",
|
|
1171
|
-
required: !1,
|
|
1172
|
-
secret: !1,
|
|
1173
|
-
description: "Fallback Postgres host for the postgres suppression store."
|
|
1174
|
-
},
|
|
1175
|
-
{
|
|
1176
|
-
name: "DB_PORT",
|
|
1177
|
-
required: !1,
|
|
1178
|
-
secret: !1,
|
|
1179
|
-
description: "Postgres port for the postgres suppression store when no URL is set. Falls back to PG_PORT, then 5432."
|
|
1180
|
-
},
|
|
1181
|
-
{
|
|
1182
|
-
name: "PG_PORT",
|
|
1183
|
-
required: !1,
|
|
1184
|
-
secret: !1,
|
|
1185
|
-
description: "Fallback Postgres port for the postgres suppression store."
|
|
1186
|
-
},
|
|
1187
|
-
{
|
|
1188
|
-
name: "DB_USER",
|
|
1189
|
-
required: !1,
|
|
1190
|
-
secret: !1,
|
|
1191
|
-
description: "Postgres username for the postgres suppression store when no URL is set. Falls back to PG_USER, then \"app\"."
|
|
1192
|
-
},
|
|
1193
|
-
{
|
|
1194
|
-
name: "PG_USER",
|
|
1195
|
-
required: !1,
|
|
1196
|
-
secret: !1,
|
|
1197
|
-
description: "Fallback Postgres username for the postgres suppression store."
|
|
1198
|
-
},
|
|
1199
|
-
{
|
|
1200
|
-
name: "DB_PASSWORD",
|
|
1201
|
-
required: !1,
|
|
1202
|
-
secret: !0,
|
|
1203
|
-
description: "Postgres password for the postgres suppression store when no URL is set. Falls back to PG_PASSWORD."
|
|
1204
|
-
},
|
|
1205
|
-
{
|
|
1206
|
-
name: "PG_PASSWORD",
|
|
1207
|
-
required: !1,
|
|
1208
|
-
secret: !0,
|
|
1209
|
-
description: "Fallback Postgres password for the postgres suppression store."
|
|
1210
|
-
},
|
|
1211
|
-
{
|
|
1212
|
-
name: "DB_DATABASE",
|
|
1213
|
-
required: !1,
|
|
1214
|
-
secret: !1,
|
|
1215
|
-
description: "Postgres database name for the postgres suppression store when no URL is set. Falls back to PG_DATABASE, then \"app\"."
|
|
1216
|
-
},
|
|
1217
|
-
{
|
|
1218
|
-
name: "PG_DATABASE",
|
|
1219
|
-
required: !1,
|
|
1220
|
-
secret: !1,
|
|
1221
|
-
description: "Fallback Postgres database name for the postgres suppression store."
|
|
1222
1068
|
}
|
|
1223
|
-
]),
|
|
1069
|
+
]), ot = "@voltro/plugin-mail", $ = (e, t) => ({
|
|
1224
1070
|
status: e,
|
|
1225
1071
|
contentType: "application/json",
|
|
1226
1072
|
body: JSON.stringify(t)
|
|
1227
|
-
}),
|
|
1073
|
+
}), st = (e, t, r, i) => {
|
|
1228
1074
|
let a = e.webhook;
|
|
1229
1075
|
if (a === void 0) throw Error(`@voltro/plugin-mail: provider '${e.name}' has no webhook verifier — mailPlugin({ webhook }) needs one (lettermint)`);
|
|
1230
|
-
let o =
|
|
1076
|
+
let o = r.secret ?? Q.read("MAIL_WEBHOOK_SECRET");
|
|
1231
1077
|
if (!o && i) throw Error("@voltro/plugin-mail: mailPlugin({ webhook }) needs a signing secret in production — set MAIL_WEBHOOK_SECRET");
|
|
1232
1078
|
return {
|
|
1233
1079
|
method: "POST",
|
|
1234
|
-
path:
|
|
1080
|
+
path: r.path ?? "/webhooks/mail",
|
|
1235
1081
|
handle: async (i) => {
|
|
1236
1082
|
if (!o) return $(503, { error: "mail webhook secret not configured" });
|
|
1237
|
-
let s = await
|
|
1083
|
+
let s = await n.runPromise(n.either(a.verify(i.headers, new TextDecoder().decode(i.rawBody), o)));
|
|
1238
1084
|
if (s._tag === "Left") return $(401, { error: s.left.message });
|
|
1239
|
-
let c =
|
|
1085
|
+
let c = X(e.name, s.right);
|
|
1240
1086
|
try {
|
|
1241
1087
|
for (let e of c) {
|
|
1242
|
-
e.type === "bounced" || e.type === "complained" ? await
|
|
1243
|
-
let a =
|
|
1244
|
-
|
|
1088
|
+
e.type === "bounced" || e.type === "complained" ? await n.runPromise(t.suppress(null, e.email, e.type)) : e.type === "unsuppressed" && await n.runPromise(t.unsuppress(null, e.email));
|
|
1089
|
+
let a = r.onEvent?.(e, { store: i.store });
|
|
1090
|
+
n.isEffect(a) ? await n.runPromise(a) : await a;
|
|
1245
1091
|
}
|
|
1246
1092
|
} catch (e) {
|
|
1247
1093
|
return {
|
|
@@ -1252,46 +1098,54 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
1252
1098
|
return $(200, { received: c.length });
|
|
1253
1099
|
}
|
|
1254
1100
|
};
|
|
1255
|
-
},
|
|
1256
|
-
let
|
|
1101
|
+
}, ct = (e = {}) => {
|
|
1102
|
+
let a = process.env, s = Ke({
|
|
1257
1103
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
1258
1104
|
...e.apiKey === void 0 ? {} : { apiKey: e.apiKey },
|
|
1259
|
-
env:
|
|
1260
|
-
}),
|
|
1261
|
-
|
|
1105
|
+
env: a
|
|
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({
|
|
1108
|
+
source: "plugin",
|
|
1109
|
+
table: N,
|
|
1110
|
+
timeColumn: "createdAt",
|
|
1111
|
+
ttlMs: v(process.env.VOLTRO_MAIL_IDEMPOTENCY_TTL_HOURS, 720),
|
|
1112
|
+
label: "mail idempotency keys"
|
|
1113
|
+
});
|
|
1114
|
+
let _ = tt(), y = J({
|
|
1115
|
+
provider: s,
|
|
1262
1116
|
suppression: p,
|
|
1263
1117
|
idempotency: m,
|
|
1264
1118
|
schedule: h,
|
|
1265
|
-
onSent:
|
|
1119
|
+
onSent: _.record,
|
|
1266
1120
|
...e.from === void 0 ? {} : { from: e.from },
|
|
1267
1121
|
...e.replyTo === void 0 ? {} : { replyTo: e.replyTo },
|
|
1268
|
-
...
|
|
1122
|
+
...d === void 0 ? {} : { allowlist: d },
|
|
1269
1123
|
...e.attempts === void 0 ? {} : { attempts: e.attempts },
|
|
1270
1124
|
...e.templates === void 0 ? {} : { templates: e.templates },
|
|
1271
|
-
production:
|
|
1272
|
-
}),
|
|
1273
|
-
for (let t of e.templates ?? [])
|
|
1274
|
-
let w = (e, t) => (t ?
|
|
1125
|
+
production: f
|
|
1126
|
+
}), b = i.succeed(E, y.service), x = s.supportsScheduling !== !0, te = e.scheduleTickMs ?? 1e3, S, C = /* @__PURE__ */ new Map();
|
|
1127
|
+
for (let t of e.templates ?? []) C.set(t.locale ? `${t.name}:${t.locale}` : t.name, t);
|
|
1128
|
+
let w = (e, t) => (t ? C.get(`${e}:${t}`) : void 0) ?? C.get(e) ?? j(e, t), T = (e) => ({
|
|
1275
1129
|
name: e.name,
|
|
1276
1130
|
...e.locale ? { locale: e.locale } : {},
|
|
1277
|
-
props: e.preview ??
|
|
1278
|
-
}),
|
|
1131
|
+
props: e.preview ?? at(e.props)
|
|
1132
|
+
}), ne = () => {
|
|
1279
1133
|
let t = /* @__PURE__ */ new Map();
|
|
1280
1134
|
for (let n of e.templates ?? []) t.set(n.locale ? `${n.name}:${n.locale}` : n.name, T(n));
|
|
1281
|
-
for (let e of
|
|
1282
|
-
let n =
|
|
1135
|
+
for (let e of ce()) {
|
|
1136
|
+
let n = j(e.name, e.locale);
|
|
1283
1137
|
n && t.set(e.locale ? `${e.name}:${e.locale}` : e.name, T(n));
|
|
1284
1138
|
}
|
|
1285
1139
|
return [...t.values()];
|
|
1286
|
-
}, D = e.webhook === void 0 ? [] : [
|
|
1140
|
+
}, D = e.webhook === void 0 ? [] : [st(s, p, e.webhook, f)], re = [
|
|
1287
1141
|
{
|
|
1288
1142
|
method: "GET",
|
|
1289
1143
|
path: "/outbox",
|
|
1290
1144
|
scope: "process",
|
|
1291
1145
|
description: "Recent sent mail (dev capture buffer), newest first",
|
|
1292
|
-
handler: () =>
|
|
1146
|
+
handler: () => n.succeed({
|
|
1293
1147
|
kind: "json",
|
|
1294
|
-
data: { sends:
|
|
1148
|
+
data: { sends: _.list() }
|
|
1295
1149
|
})
|
|
1296
1150
|
},
|
|
1297
1151
|
{
|
|
@@ -1299,16 +1153,16 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
1299
1153
|
path: "/templates",
|
|
1300
1154
|
scope: "declaration",
|
|
1301
1155
|
description: "Discovered + registered email templates",
|
|
1302
|
-
handler: () =>
|
|
1156
|
+
handler: () => n.succeed({
|
|
1303
1157
|
kind: "json",
|
|
1304
|
-
data: { templates:
|
|
1158
|
+
data: { templates: ne() }
|
|
1305
1159
|
})
|
|
1306
1160
|
},
|
|
1307
1161
|
{
|
|
1308
1162
|
method: "POST",
|
|
1309
1163
|
path: "/preview",
|
|
1310
1164
|
description: "Render a template to html + text without sending — body { template, props, locale? }",
|
|
1311
|
-
handler: (e) =>
|
|
1165
|
+
handler: (e) => n.gen(function* () {
|
|
1312
1166
|
let t = {};
|
|
1313
1167
|
try {
|
|
1314
1168
|
t = JSON.parse(e.body || "{}");
|
|
@@ -1324,11 +1178,11 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
1324
1178
|
status: 400,
|
|
1325
1179
|
data: { error: "missing \"template\"" }
|
|
1326
1180
|
};
|
|
1327
|
-
let
|
|
1328
|
-
return
|
|
1181
|
+
let r = w(t.template, t.locale);
|
|
1182
|
+
return r ? yield* k(r, t.props).pipe(n.map((e) => ({
|
|
1329
1183
|
kind: "json",
|
|
1330
1184
|
data: e
|
|
1331
|
-
})),
|
|
1185
|
+
})), n.catchAll((e) => n.succeed({
|
|
1332
1186
|
kind: "json",
|
|
1333
1187
|
status: 400,
|
|
1334
1188
|
data: { error: e.message }
|
|
@@ -1339,50 +1193,55 @@ var b = class extends u.TaggedError()("MailError", {
|
|
|
1339
1193
|
};
|
|
1340
1194
|
})
|
|
1341
1195
|
}
|
|
1196
|
+
], O = [
|
|
1197
|
+
"network:outbound:*",
|
|
1198
|
+
"inspect:read",
|
|
1199
|
+
"inspect:write",
|
|
1200
|
+
...g.length > 0 ? ["store:write"] : []
|
|
1342
1201
|
];
|
|
1343
1202
|
return {
|
|
1344
|
-
...
|
|
1345
|
-
name:
|
|
1346
|
-
base:
|
|
1203
|
+
...c({
|
|
1204
|
+
name: l({
|
|
1205
|
+
base: ot,
|
|
1347
1206
|
alias: e.alias,
|
|
1348
1207
|
instance: e.name
|
|
1349
1208
|
}),
|
|
1350
|
-
baseName:
|
|
1209
|
+
baseName: ot,
|
|
1351
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.",
|
|
1352
|
-
permissions:
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
"inspect:write"
|
|
1356
|
-
],
|
|
1357
|
-
declaredEnv: Z.declared,
|
|
1358
|
-
egress: Ye({
|
|
1211
|
+
permissions: O,
|
|
1212
|
+
declaredEnv: Q.declared,
|
|
1213
|
+
egress: it({
|
|
1359
1214
|
...e.provider === void 0 ? {} : { provider: e.provider },
|
|
1360
|
-
env:
|
|
1215
|
+
env: a
|
|
1361
1216
|
}),
|
|
1362
|
-
services:
|
|
1363
|
-
inspectEndpoints:
|
|
1217
|
+
services: b,
|
|
1218
|
+
inspectEndpoints: re,
|
|
1364
1219
|
...D.length > 0 ? { httpRoutes: D } : {},
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1220
|
+
...g.length > 0 ? { extendSchema: { tables: g } } : {},
|
|
1221
|
+
bindDataStore: async (e) => {
|
|
1222
|
+
await p.bind?.(e), await m.bind?.(e);
|
|
1223
|
+
},
|
|
1224
|
+
onActivate: (r) => n.gen(function* () {
|
|
1225
|
+
if (p.activate && (yield* n.promise(() => p.activate(r.env))), m.activate && (yield* n.promise(() => m.activate(r.env))), x) {
|
|
1226
|
+
let e = y.scheduler.tick().pipe(n.repeat(o.spaced(t.millis(Math.max(50, te)))));
|
|
1227
|
+
S = n.runFork(e);
|
|
1369
1228
|
}
|
|
1370
|
-
|
|
1371
|
-
provider:
|
|
1229
|
+
r.logger.info("mail active", {
|
|
1230
|
+
provider: s.name,
|
|
1372
1231
|
from: e.from ?? null,
|
|
1373
|
-
allowlist:
|
|
1232
|
+
allowlist: d?.length ?? 0,
|
|
1374
1233
|
suppression: typeof e.suppression == "string" ? e.suppression : e.suppression ? "custom" : "memory",
|
|
1375
1234
|
idempotency: typeof e.idempotency == "string" ? e.idempotency : e.idempotency ? "custom" : "memory",
|
|
1376
|
-
scheduling:
|
|
1377
|
-
production:
|
|
1235
|
+
scheduling: s.supportsScheduling === !0 ? "native" : "held",
|
|
1236
|
+
production: f
|
|
1378
1237
|
});
|
|
1379
1238
|
}),
|
|
1380
|
-
onDeactivate: () =>
|
|
1381
|
-
|
|
1239
|
+
onDeactivate: () => n.gen(function* () {
|
|
1240
|
+
S !== void 0 && (yield* r.interrupt(S), S = void 0), p.deactivate && (yield* n.promise(() => p.deactivate())), m.deactivate && (yield* n.promise(() => m.deactivate()));
|
|
1382
1241
|
})
|
|
1383
1242
|
}),
|
|
1384
|
-
service:
|
|
1243
|
+
service: y.service
|
|
1385
1244
|
};
|
|
1386
1245
|
};
|
|
1387
1246
|
//#endregion
|
|
1388
|
-
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 };
|