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