@voltro/plugin-mail 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1215 -0
- package/THIRD-PARTY-NOTICES.md +108 -736
- package/dist/egress.js +34 -0
- package/dist/events.js +46 -0
- package/dist/idempotency.js +62 -0
- package/dist/index.d.ts +15 -18
- package/dist/index.js +97 -1026
- package/dist/outbox.js +15 -0
- package/dist/providers.js +377 -0
- package/dist/registry.js +13 -0
- package/dist/render.js +44 -0
- package/dist/scheduler.js +36 -0
- package/dist/service.js +279 -0
- package/dist/skeleton.js +24 -0
- package/dist/storeTables.js +28 -0
- package/dist/suppression.js +46 -0
- package/dist/types.js +20 -0
- package/package.json +6 -8
package/dist/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.
|
|
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.
|
|
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.
|
|
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.
|
|
181
|
-
readonly
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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.
|
|
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.
|
|
469
|
-
readonly
|
|
470
|
-
|
|
471
|
-
|
|
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
|