@voltro/plugin-mail 0.84.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/egress.js ADDED
@@ -0,0 +1,34 @@
1
+ //#region src/egress.ts
2
+ var e = {
3
+ lettermint: "api.lettermint.co",
4
+ resend: "api.resend.com",
5
+ postmark: "api.postmarkapp.com",
6
+ sendgrid: "api.sendgrid.com"
7
+ }, t = (e) => {
8
+ try {
9
+ return new URL(e).host || void 0;
10
+ } catch {
11
+ return;
12
+ }
13
+ }, n = (n) => {
14
+ let r = n.env ?? {}, i = (e) => [{
15
+ destination: e,
16
+ purpose: "outgoing email: recipients, subject and body",
17
+ data: ["personal", "content"],
18
+ disable: "MAIL_PROVIDER=console (or memory) logs mail instead of sending it — none leaves"
19
+ }];
20
+ if (n.provider !== void 0 && typeof n.provider != "string") {
21
+ let e = n.provider.name;
22
+ return e === "console" || e === "memory" ? [] : i(`runtime:the '${e}' mail provider`);
23
+ }
24
+ let a = n.provider ?? r.MAIL_PROVIDER ?? "console", o = e[a];
25
+ if (o !== void 0) return i(o);
26
+ switch (a) {
27
+ case "ses": return i(r.AWS_REGION ? `email.${r.AWS_REGION}.amazonaws.com` : "runtime:email.<AWS_REGION>.amazonaws.com");
28
+ case "mailgun": return i((r.MAILGUN_BASE_URL ? t(r.MAILGUN_BASE_URL) : void 0) ?? "api.mailgun.net");
29
+ case "smtp": return i((r.SMTP_URL ? t(r.SMTP_URL) : void 0) ?? (r.SMTP_HOST ? `${r.SMTP_HOST}${r.SMTP_PORT ? `:${r.SMTP_PORT}` : ""}` : void 0) ?? "runtime:SMTP_URL or SMTP_HOST");
30
+ default: return [];
31
+ }
32
+ };
33
+ //#endregion
34
+ export { n as mailEgressOf };
package/dist/events.js ADDED
@@ -0,0 +1,46 @@
1
+ import { Effect as e } from "effect";
2
+ //#region src/events.ts
3
+ var t = (e, t, n, r, i, a = {}) => {
4
+ if (!t) return null;
5
+ let o = a.metadata !== null && typeof a.metadata == "object" ? Object.fromEntries(Object.entries(a.metadata).filter(([, e]) => typeof e == "string")) : void 0;
6
+ return {
7
+ type: e,
8
+ email: t,
9
+ provider: n,
10
+ raw: i,
11
+ ...r === void 0 ? {} : { messageId: r },
12
+ ...a.eventId === void 0 ? {} : { eventId: a.eventId },
13
+ ...o === void 0 ? {} : { metadata: o }
14
+ };
15
+ }, n = (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", r = (e) => e === "hard_bounce" ? "bounced" : e === "spam_complaint" ? "complained" : e === "unsubscribe" ? "unsubscribed" : "unknown", i = (e) => e === "email.bounced" ? "bounced" : e === "email.complained" ? "complained" : e === "email.delivered" ? "delivered" : e === "email.opened" ? "opened" : e === "email.clicked" ? "clicked" : "unknown", a = (e) => e === "Bounce" ? "bounced" : e === "SpamComplaint" ? "complained" : e === "Delivery" ? "delivered" : e === "Open" ? "opened" : e === "Click" ? "clicked" : "unknown", o = (e) => e === "bounce" || e === "dropped" ? "bounced" : e === "spamreport" ? "complained" : e === "delivered" ? "delivered" : e === "open" ? "opened" : e === "click" ? "clicked" : "unknown", s = (e, s) => {
16
+ let c = [], l = (e) => {
17
+ e && c.push(e);
18
+ };
19
+ if (e === "sendgrid" && Array.isArray(s)) {
20
+ for (let e of s) l(t(o(String(e.event)), e.email, "sendgrid", e.sg_message_id, e));
21
+ return c;
22
+ }
23
+ let u = s ?? {};
24
+ if (e === "lettermint") {
25
+ let e = String(u.event), i = u.data ?? {}, a = typeof u.id == "string" ? u.id : void 0;
26
+ return e === "suppression.added" ? (i.type === "email" && l(t(r(String(i.reason)), i.value, "lettermint", void 0, s, { eventId: a })), c) : e === "suppression.removed" ? (i.type === "email" && l(t("unsuppressed", i.value, "lettermint", void 0, s, { eventId: a })), c) : (e.startsWith("message.") && l(t(n(e), i.recipient, "lettermint", i.message_id, s, {
27
+ eventId: a,
28
+ metadata: i.metadata
29
+ })), c);
30
+ }
31
+ if (e === "resend") {
32
+ let e = u.data ?? {}, n = Array.isArray(e.to) ? e.to[0] : e.to ?? e.email;
33
+ return l(t(i(String(u.type)), n, "resend", e.email_id, s)), c;
34
+ }
35
+ if (e === "postmark") {
36
+ let e = u.Email ?? u.Recipient;
37
+ return l(t(a(String(u.RecordType)), e, "postmark", u.MessageID, s, { metadata: u.Metadata })), c;
38
+ }
39
+ return c;
40
+ }, c = (t, n, r, i = null) => e.gen(function* () {
41
+ let e = s(t, n);
42
+ for (let t of e) t.type === "bounced" || t.type === "complained" ? yield* r.suppress(i, t.email, t.type) : t.type === "unsuppressed" && (yield* r.unsuppress(i, t.email));
43
+ return e;
44
+ });
45
+ //#endregion
46
+ export { c as handleMailEvents, s as parseMailEvent };
@@ -0,0 +1,62 @@
1
+ import { MAIL_IDEMPOTENCY_TABLE as e, attempt as t, describeError as n } from "./storeTables.js";
2
+ import { Effect as r } from "effect";
3
+ import { createLogger as i } from "@voltro/logger";
4
+ import { and as a, eq as o } from "@voltro/database";
5
+ //#region src/idempotency.ts
6
+ var s = i({ scope: "@voltro/plugin-mail" }), c = (e) => e ?? "*", l = () => {
7
+ let e = /* @__PURE__ */ new Map(), t = (e, t) => `${c(e)} ${t}`;
8
+ return {
9
+ reserve: (n, i) => r.sync(() => {
10
+ let r = t(n, i);
11
+ if (e.has(r)) {
12
+ let t = e.get(r);
13
+ return t ? {
14
+ won: !1,
15
+ prior: t
16
+ } : { won: !1 };
17
+ }
18
+ return e.set(r, null), { won: !0 };
19
+ }),
20
+ record: (n, i, a) => r.sync(() => void e.set(t(n, i), a))
21
+ };
22
+ }, u = () => {
23
+ let i = null;
24
+ return {
25
+ reserve: (a, o) => {
26
+ let l = i;
27
+ return l ? t(() => l.insertIgnoreWithOutcome(e, {
28
+ tenant: c(a),
29
+ key: o,
30
+ createdAt: /* @__PURE__ */ new Date()
31
+ }, { conflictColumns: ["tenant", "key"] })).pipe(r.map(({ row: e, outcome: t }) => {
32
+ if (t === "inserted") return { won: !0 };
33
+ let n = e.resultId, r = e.resultProvider;
34
+ return typeof n == "string" && typeof r == "string" ? {
35
+ won: !1,
36
+ prior: {
37
+ id: n,
38
+ provider: r
39
+ }
40
+ } : { won: !1 };
41
+ }), r.catch((e) => r.sync(() => (s.warn("idempotency reserve failed — skipping send to avoid a double", {
42
+ key: o,
43
+ message: n(e)
44
+ }), { won: !1 })))) : r.succeed({ won: !0 });
45
+ },
46
+ record: (l, u, d) => {
47
+ let f = i;
48
+ return f ? t(() => f.updateMany(e, {
49
+ resultId: d.id,
50
+ resultProvider: d.provider
51
+ }, { where: a(o("tenant", c(l)), o("key", u)) })).pipe(r.asVoid, r.catch((e) => r.sync(() => s.warn("idempotency record failed", {
52
+ key: u,
53
+ message: n(e)
54
+ })))) : r.void;
55
+ },
56
+ bind: async (e) => {
57
+ i = e;
58
+ }
59
+ };
60
+ };
61
+ //#endregion
62
+ export { l as memoryIdempotencyStore, u as postgresIdempotencyStore };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { PluginEgressDeclaration } from '@voltro/protocol';
6
6
  import { PluginHttpRouteRequest } from '@voltro/protocol';
7
7
  import { Schema } from 'effect';
8
8
  import { VoltroPlugin } from '@voltro/protocol';
9
+ import { YieldableError } from 'effect/Cause';
9
10
 
10
11
  /** Any template regardless of its props type — for registries/collections.
11
12
  * `P` is invariant across `EmailTemplate` (it appears in the `props` Schema
@@ -16,7 +17,7 @@ import { VoltroPlugin } from '@voltro/protocol';
16
17
  export declare interface AnyEmailTemplate {
17
18
  readonly name: string;
18
19
  readonly subject: (props: never) => string;
19
- readonly props: Schema.Schema.Any;
20
+ readonly props: Schema.Top;
20
21
  readonly render: (props: never) => unknown;
21
22
  readonly locale?: string | undefined;
22
23
  readonly preview?: unknown;
@@ -57,7 +58,7 @@ export declare const createOutbox: (max?: number) => Outbox;
57
58
 
58
59
  /** Define a template. Infers the props type from the `props` Schema so
59
60
  * `subject` / `render` receive the decoded shape. */
60
- export declare const defineEmail: <S extends Schema.Schema.Any>(template: {
61
+ export declare const defineEmail: <S extends Schema.Top>(template: {
61
62
  readonly name: string;
62
63
  readonly props: S;
63
64
  readonly subject: (props: Schema.Schema.Type<S>) => string;
@@ -78,7 +79,7 @@ export declare const defineEmail: <S extends Schema.Schema.Any>(template: {
78
79
  export declare interface EmailTemplate<P = unknown> {
79
80
  readonly name: string;
80
81
  readonly subject: (props: P) => string;
81
- readonly props: Schema.Schema<P, P, never> | Schema.Schema.Any;
82
+ readonly props: Schema.Codec<P, P, never, never> | Schema.Top;
82
83
  readonly render: (props: P) => unknown;
83
84
  readonly locale?: string;
84
85
  /** Realistic example props — pre-fills the dashboard preview box (and
@@ -177,14 +178,12 @@ export declare const mailEgressOf: (options: {
177
178
  export declare class MailError extends MailError_base {
178
179
  }
179
180
 
180
- declare const MailError_base: Schema.TaggedErrorClass<MailError, "MailError", {
181
- readonly _tag: Schema.tag<"MailError">;
182
- } & {
183
- provider: typeof Schema.String;
184
- message: typeof Schema.String;
185
- transient: typeof Schema.Boolean;
186
- status: Schema.optional<typeof Schema.Number>;
187
- }>;
181
+ declare const MailError_base: Schema.Class<MailError, Schema.TaggedStruct<"MailError", {
182
+ readonly provider: Schema.String;
183
+ readonly message: Schema.String;
184
+ readonly transient: Schema.Boolean;
185
+ readonly status: Schema.optional<Schema.Number>;
186
+ }>, YieldableError>;
188
187
 
189
188
  export declare interface MailEvent {
190
189
  readonly type: MailEventType;
@@ -385,7 +384,7 @@ export declare interface MailSendRecord {
385
384
  export declare class MailService extends MailService_base {
386
385
  }
387
386
 
388
- declare const MailService_base: Context.TagClass<MailService, "@voltro/plugin-mail/MailService", MailServiceShape>;
387
+ declare const MailService_base: Context.ServiceClass<MailService, "@voltro/plugin-mail/MailService", MailServiceShape>;
389
388
 
390
389
  export declare const mailServiceLayer: (options: MailServiceOptions) => Layer.Layer<MailService>;
391
390
 
@@ -465,12 +464,10 @@ export declare interface MailUnsubscribe {
465
464
  export declare class MailWebhookError extends MailWebhookError_base {
466
465
  }
467
466
 
468
- declare const MailWebhookError_base: Schema.TaggedErrorClass<MailWebhookError, "MailWebhookError", {
469
- readonly _tag: Schema.tag<"MailWebhookError">;
470
- } & {
471
- provider: typeof Schema.String;
472
- message: typeof Schema.String;
473
- }>;
467
+ declare const MailWebhookError_base: Schema.Class<MailWebhookError, Schema.TaggedStruct<"MailWebhookError", {
468
+ readonly provider: Schema.String;
469
+ readonly message: Schema.String;
470
+ }>, YieldableError>;
474
471
 
475
472
  /**
476
473
  * The provider's delivery-event webhook, mounted by the plugin. Bounces and