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