@vllnt/convex-email 0.1.0-canary.63aca6b

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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/dist/client/index.d.ts +174 -0
  4. package/dist/client/index.d.ts.map +1 -0
  5. package/dist/client/index.js +151 -0
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/client/types.d.ts +84 -0
  8. package/dist/client/types.d.ts.map +1 -0
  9. package/dist/client/types.js +3 -0
  10. package/dist/client/types.js.map +1 -0
  11. package/dist/component/_generated/api.d.ts +40 -0
  12. package/dist/component/_generated/api.d.ts.map +1 -0
  13. package/dist/component/_generated/api.js +31 -0
  14. package/dist/component/_generated/api.js.map +1 -0
  15. package/dist/component/_generated/component.d.ts +110 -0
  16. package/dist/component/_generated/component.d.ts.map +1 -0
  17. package/dist/component/_generated/component.js +11 -0
  18. package/dist/component/_generated/component.js.map +1 -0
  19. package/dist/component/_generated/dataModel.d.ts +46 -0
  20. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  21. package/dist/component/_generated/dataModel.js +11 -0
  22. package/dist/component/_generated/dataModel.js.map +1 -0
  23. package/dist/component/_generated/server.d.ts +121 -0
  24. package/dist/component/_generated/server.d.ts.map +1 -0
  25. package/dist/component/_generated/server.js +78 -0
  26. package/dist/component/_generated/server.js.map +1 -0
  27. package/dist/component/convex.config.d.ts +3 -0
  28. package/dist/component/convex.config.d.ts.map +1 -0
  29. package/dist/component/convex.config.js +7 -0
  30. package/dist/component/convex.config.js.map +1 -0
  31. package/dist/component/crons.d.ts +16 -0
  32. package/dist/component/crons.d.ts.map +1 -0
  33. package/dist/component/crons.js +19 -0
  34. package/dist/component/crons.js.map +1 -0
  35. package/dist/component/mutations.d.ts +95 -0
  36. package/dist/component/mutations.d.ts.map +1 -0
  37. package/dist/component/mutations.js +243 -0
  38. package/dist/component/mutations.js.map +1 -0
  39. package/dist/component/queries.d.ts +59 -0
  40. package/dist/component/queries.d.ts.map +1 -0
  41. package/dist/component/queries.js +61 -0
  42. package/dist/component/queries.js.map +1 -0
  43. package/dist/component/schema.d.ts +54 -0
  44. package/dist/component/schema.d.ts.map +1 -0
  45. package/dist/component/schema.js +40 -0
  46. package/dist/component/schema.js.map +1 -0
  47. package/dist/component/validators.d.ts +54 -0
  48. package/dist/component/validators.d.ts.map +1 -0
  49. package/dist/component/validators.js +40 -0
  50. package/dist/component/validators.js.map +1 -0
  51. package/dist/jmap/index.d.ts +22 -0
  52. package/dist/jmap/index.d.ts.map +1 -0
  53. package/dist/jmap/index.js +21 -0
  54. package/dist/jmap/index.js.map +1 -0
  55. package/dist/jmap/send.d.ts +125 -0
  56. package/dist/jmap/send.d.ts.map +1 -0
  57. package/dist/jmap/send.js +418 -0
  58. package/dist/jmap/send.js.map +1 -0
  59. package/dist/jmap/types.d.ts +107 -0
  60. package/dist/jmap/types.d.ts.map +1 -0
  61. package/dist/jmap/types.js +16 -0
  62. package/dist/jmap/types.js.map +1 -0
  63. package/dist/shared.d.ts +32 -0
  64. package/dist/shared.d.ts.map +1 -0
  65. package/dist/shared.js +33 -0
  66. package/dist/shared.js.map +1 -0
  67. package/dist/smtp/index.d.ts +22 -0
  68. package/dist/smtp/index.d.ts.map +1 -0
  69. package/dist/smtp/index.js +21 -0
  70. package/dist/smtp/index.js.map +1 -0
  71. package/dist/smtp/send.d.ts +51 -0
  72. package/dist/smtp/send.d.ts.map +1 -0
  73. package/dist/smtp/send.js +124 -0
  74. package/dist/smtp/send.js.map +1 -0
  75. package/dist/smtp/transport.d.ts +43 -0
  76. package/dist/smtp/transport.d.ts.map +1 -0
  77. package/dist/smtp/transport.js +55 -0
  78. package/dist/smtp/transport.js.map +1 -0
  79. package/dist/smtp/types.d.ts +122 -0
  80. package/dist/smtp/types.d.ts.map +1 -0
  81. package/dist/smtp/types.js +9 -0
  82. package/dist/smtp/types.js.map +1 -0
  83. package/package.json +118 -0
  84. package/src/client/index.ts +312 -0
  85. package/src/client/types.ts +90 -0
  86. package/src/component/_generated/api.ts +56 -0
  87. package/src/component/_generated/component.ts +134 -0
  88. package/src/component/_generated/dataModel.ts +60 -0
  89. package/src/component/_generated/server.ts +156 -0
  90. package/src/component/convex.config.ts +9 -0
  91. package/src/component/crons.ts +23 -0
  92. package/src/component/mutations.ts +262 -0
  93. package/src/component/queries.ts +70 -0
  94. package/src/component/schema.ts +40 -0
  95. package/src/component/validators.ts +47 -0
  96. package/src/jmap/index.ts +39 -0
  97. package/src/jmap/send.test.ts +565 -0
  98. package/src/jmap/send.ts +502 -0
  99. package/src/jmap/types.ts +117 -0
  100. package/src/shared.ts +41 -0
  101. package/src/smtp/index.ts +30 -0
  102. package/src/smtp/send.test.ts +240 -0
  103. package/src/smtp/send.ts +154 -0
  104. package/src/smtp/transport.ts +58 -0
  105. package/src/smtp/types.ts +124 -0
  106. package/src/test.ts +12 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Public TypeScript surface for the optional generic-JMAP transport adapter.
3
+ *
4
+ * Import this from your own Convex action to send queued messages over any JMAP
5
+ * server's HTTP API — Stalwart, Fastmail, Cyrus, Apache James. JMAP is an open
6
+ * protocol (RFC 8620 core + 8621 mail/submission), so this adapter is generic
7
+ * over any JMAP server: the server is host config, never baked in. The component
8
+ * itself never sends — it records the message and its status.
9
+ *
10
+ * Unlike the SMTP adapter, this layer is **pure**: it drives an injected
11
+ * {@link JmapFetch} (the host's runtime `fetch`), so it needs no Node runtime,
12
+ * no `"use node"` action, and no third-party dependency, and is 100%-coverable
13
+ * with a fake `fetch`.
14
+ */
15
+ /** The minimal `Response` subset {@link JmapFetch} resolves with. */
16
+ export interface JmapResponse {
17
+ /** Whether the HTTP status is in the 2xx range. */
18
+ ok: boolean;
19
+ /** The HTTP status code. */
20
+ status: number;
21
+ /** Parse the response body as JSON. */
22
+ json(): Promise<unknown>;
23
+ }
24
+ /** The minimal `RequestInit` subset the adapter passes to {@link JmapFetch}. */
25
+ export interface JmapRequestInit {
26
+ /** HTTP method (`"GET"` for session discovery, `"POST"` for the JMAP API). */
27
+ method: string;
28
+ /** Request headers — always carries `Authorization` and (for POST) `Content-Type`. */
29
+ headers: Record<string, string>;
30
+ /** The JSON request body (POST only). */
31
+ body?: string;
32
+ }
33
+ /**
34
+ * The injected `fetch` seam — the minimal surface the adapter drives. The host
35
+ * passes its runtime `fetch` (`(url, init) => fetch(url, init)` in a Convex
36
+ * action); a fake one satisfies the same shape in tests, so the whole adapter is
37
+ * 100%-coverable with no network.
38
+ */
39
+ export type JmapFetch = (url: string, init: JmapRequestInit) => Promise<JmapResponse>;
40
+ /**
41
+ * Resolved JMAP connection config — everything {@link sendViaJmap} needs to send.
42
+ * Host-supplied, or produced by {@link discoverJmapSession}. Generic over any
43
+ * JMAP server: Stalwart is one configured endpoint, never baked in.
44
+ */
45
+ export interface JmapConfig {
46
+ /** The JMAP API endpoint the method calls POST to (the session's `apiUrl`, e.g. `https://mail.example.com/jmap`). */
47
+ endpoint: string;
48
+ /** The bearer access token (a secret — keep it server-side; never ships to a client). */
49
+ token: string;
50
+ /** The JMAP account id that owns the mailbox/identity (the `urn:ietf:params:jmap:mail` primary account). */
51
+ accountId: string;
52
+ /** The sending identity id (an `Identity` whose address matches `from`). */
53
+ identityId: string;
54
+ /** The mailbox the sent copy is filed in (typically the Sent mailbox; a JMAP `Email` must belong to a mailbox). */
55
+ mailboxId: string;
56
+ /** Default `From` address used when a {@link JmapMessage} omits `from`. */
57
+ from?: string;
58
+ }
59
+ /**
60
+ * A single outbound message handed to the JMAP transport. Mirrors the queue's
61
+ * stored fields (`to`/`from` plus the rendered body) without coupling to the
62
+ * component's storage shape — the host maps a stored payload onto it. Identical
63
+ * shape to the SMTP adapter's message so a host can target either transport.
64
+ */
65
+ export interface JmapMessage {
66
+ /** The recipient address (one address or a comma-separated list). */
67
+ to: string;
68
+ /** The sender address; falls back to {@link JmapConfig.from} when omitted. */
69
+ from?: string;
70
+ /** The message subject line. */
71
+ subject?: string;
72
+ /** The plain-text body. At least one of `text`/`html` should be set. */
73
+ text?: string;
74
+ /** The HTML body. At least one of `text`/`html` should be set. */
75
+ html?: string;
76
+ /** Optional `Reply-To` address. */
77
+ replyTo?: string;
78
+ /** Optional extra headers (host-supplied, set as JMAP `header:Name:asText` properties). */
79
+ headers?: Record<string, string>;
80
+ }
81
+ /**
82
+ * The normalized result of {@link sendViaJmap}. `messageId` is the JMAP
83
+ * `EmailSubmission` id (store it as the queue's `providerId` on `markSent`);
84
+ * `emailId` is the created `Email` object id.
85
+ */
86
+ export interface JmapSendResult {
87
+ /** The `EmailSubmission` id (or the `Email` id if the server returned no submission id) — store as the queue `providerId`. */
88
+ messageId: string;
89
+ /** The created `Email` object id. */
90
+ emailId: string;
91
+ }
92
+ /**
93
+ * A bound sender: a function that sends one {@link JmapMessage} through a
94
+ * preconfigured endpoint + `fetch`. {@link createJmapSender} returns one; the
95
+ * host calls it inside its own Convex action.
96
+ */
97
+ export type JmapSender = (message: JmapMessage) => Promise<JmapSendResult>;
98
+ /** Options for {@link discoverJmapSession} — resolve a {@link JmapConfig} from a JMAP session. */
99
+ export interface JmapDiscoverOptions {
100
+ /** The JMAP session resource URL (e.g. `https://mail.example.com/.well-known/jmap`). */
101
+ sessionUrl: string;
102
+ /** The bearer access token. */
103
+ token: string;
104
+ /** Pick the sending identity whose address matches this `from`; otherwise the first identity is used (and becomes the config default `from`). */
105
+ from?: string;
106
+ }
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jmap/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,EAAE,EAAE,OAAO,CAAC;IACZ,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,eAAe,KAClB,OAAO,CAAC,YAAY,CAAC,CAAC;AAE3B;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,qHAAqH;IACrH,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAC;IACd,4GAA4G;IAC5G,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,mHAAmH;IACnH,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,8HAA8H;IAC9H,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAE3E,kGAAkG;AAClG,MAAM,WAAW,mBAAmB;IAClC,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,iJAAiJ;IACjJ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Public TypeScript surface for the optional generic-JMAP transport adapter.
3
+ *
4
+ * Import this from your own Convex action to send queued messages over any JMAP
5
+ * server's HTTP API — Stalwart, Fastmail, Cyrus, Apache James. JMAP is an open
6
+ * protocol (RFC 8620 core + 8621 mail/submission), so this adapter is generic
7
+ * over any JMAP server: the server is host config, never baked in. The component
8
+ * itself never sends — it records the message and its status.
9
+ *
10
+ * Unlike the SMTP adapter, this layer is **pure**: it drives an injected
11
+ * {@link JmapFetch} (the host's runtime `fetch`), so it needs no Node runtime,
12
+ * no `"use node"` action, and no third-party dependency, and is 100%-coverable
13
+ * with a fake `fetch`.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/jmap/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,32 @@
1
+ /** Shared constants used by both `client/` and `component/`. */
2
+ export declare const COMPONENT_NAME = "email";
3
+ /**
4
+ * The standard message lifecycle states. `queued` is the freshly-enqueued state;
5
+ * the host's transport sender claims it (`sending`), then records a terminal
6
+ * `sent` or `failed`. A `sending` message can return to `queued` for a retry
7
+ * while attempts remain. Terminal states are final — once `sent`, or `failed`
8
+ * with attempts exhausted, the message never transitions again.
9
+ */
10
+ export declare const MESSAGE_STATUSES: readonly ["queued", "sending", "sent", "failed"];
11
+ /** A single message lifecycle status. */
12
+ export type MessageStatus = (typeof MESSAGE_STATUSES)[number];
13
+ /** The two terminal states — once reached, a message never transitions again. */
14
+ export declare const TERMINAL_STATUSES: ReadonlySet<MessageStatus>;
15
+ /**
16
+ * Default maximum send attempts before a `markFailed` is permanent (the message
17
+ * lands in terminal `failed` instead of returning to `queued` for another
18
+ * attempt). The host owns the actual transport send and reports each outcome; the
19
+ * component only counts attempts and decides retry-vs-terminal. A per-message
20
+ * override is accepted at `enqueue`; this is the client-level default.
21
+ */
22
+ export declare const DEFAULT_MAX_ATTEMPTS = 5;
23
+ /**
24
+ * Default retention (ms) for terminal messages before the prune cron sweeps them:
25
+ * 30 days. Bounds unbounded growth of the `messages` table while leaving a
26
+ * generous audit window for delivery records. A host that wants a different
27
+ * window drives `prune` from its own scheduler with an explicit `before` cutoff.
28
+ */
29
+ export declare const DEFAULT_RETENTION_MS = 2592000000;
30
+ /** Default page size for a `prune` pass before the sweep self-reschedules. */
31
+ export declare const DEFAULT_PRUNE_BATCH = 200;
32
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAEhE,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,kDAAmD,CAAC;AAEjF,yCAAyC;AACzC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,aAAa,CAGvD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,aAAgB,CAAC;AAElD,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,MAAM,CAAC"}
package/dist/shared.js ADDED
@@ -0,0 +1,33 @@
1
+ /** Shared constants used by both `client/` and `component/`. */
2
+ export const COMPONENT_NAME = "email";
3
+ /**
4
+ * The standard message lifecycle states. `queued` is the freshly-enqueued state;
5
+ * the host's transport sender claims it (`sending`), then records a terminal
6
+ * `sent` or `failed`. A `sending` message can return to `queued` for a retry
7
+ * while attempts remain. Terminal states are final — once `sent`, or `failed`
8
+ * with attempts exhausted, the message never transitions again.
9
+ */
10
+ export const MESSAGE_STATUSES = ["queued", "sending", "sent", "failed"];
11
+ /** The two terminal states — once reached, a message never transitions again. */
12
+ export const TERMINAL_STATUSES = new Set([
13
+ "sent",
14
+ "failed",
15
+ ]);
16
+ /**
17
+ * Default maximum send attempts before a `markFailed` is permanent (the message
18
+ * lands in terminal `failed` instead of returning to `queued` for another
19
+ * attempt). The host owns the actual transport send and reports each outcome; the
20
+ * component only counts attempts and decides retry-vs-terminal. A per-message
21
+ * override is accepted at `enqueue`; this is the client-level default.
22
+ */
23
+ export const DEFAULT_MAX_ATTEMPTS = 5;
24
+ /**
25
+ * Default retention (ms) for terminal messages before the prune cron sweeps them:
26
+ * 30 days. Bounds unbounded growth of the `messages` table while leaving a
27
+ * generous audit window for delivery records. A host that wants a different
28
+ * window drives `prune` from its own scheduler with an explicit `before` cutoff.
29
+ */
30
+ export const DEFAULT_RETENTION_MS = 2_592_000_000;
31
+ /** Default page size for a `prune` pass before the sweep self-reschedules. */
32
+ export const DEFAULT_PRUNE_BATCH = 200;
33
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAEhE,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAKjF,iFAAiF;AACjF,MAAM,CAAC,MAAM,iBAAiB,GAA+B,IAAI,GAAG,CAAC;IACnE,MAAM;IACN,QAAQ;CACT,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAElD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The optional generic-SMTP transport adapter — a host-side `./smtp` entry the
3
+ * host imports into its OWN `"use node"` action to actually send queued messages
4
+ * over SMTP (Stalwart, Postfix, any server). It is NOT part of the sandboxed
5
+ * component: a Convex component runs in V8 and cannot open a raw SMTP socket or
6
+ * ship a `"use node"` action, so the real send is host-side glue.
7
+ *
8
+ * Tree-shake boundary: a backend-only consumer importing `@vllnt/convex-email`
9
+ * (the `.` entry) pulls ZERO SMTP code and no `nodemailer`. Importing this `./smtp`
10
+ * entry is the explicit opt-in; `nodemailer` is an optional peer dep loaded only
11
+ * by {@link createSmtpTransport} / {@link createSmtpSender} here.
12
+ *
13
+ * Two layers:
14
+ * - pure + 100%-covered: {@link sendViaSmtp}, {@link validateSmtpConfig},
15
+ * {@link toMailOptions} — driven by an injected transport, unit-tested with a fake.
16
+ * - thin Node wrapper (coverage-excluded): {@link createSmtpTransport},
17
+ * {@link createSmtpSender} — the real `nodemailer.createTransport`, consumer-E2E verified.
18
+ */
19
+ export { sendViaSmtp, validateSmtpConfig, toMailOptions } from "./send.js";
20
+ export { createSmtpTransport, createSmtpSender } from "./transport.js";
21
+ export type { SmtpConfig, SmtpMessage, SmtpMailOptions, SmtpSendInfo, SmtpSendResult, SmtpSender, SmtpTransport, } from "./types.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/smtp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,cAAc,EACd,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The optional generic-SMTP transport adapter — a host-side `./smtp` entry the
3
+ * host imports into its OWN `"use node"` action to actually send queued messages
4
+ * over SMTP (Stalwart, Postfix, any server). It is NOT part of the sandboxed
5
+ * component: a Convex component runs in V8 and cannot open a raw SMTP socket or
6
+ * ship a `"use node"` action, so the real send is host-side glue.
7
+ *
8
+ * Tree-shake boundary: a backend-only consumer importing `@vllnt/convex-email`
9
+ * (the `.` entry) pulls ZERO SMTP code and no `nodemailer`. Importing this `./smtp`
10
+ * entry is the explicit opt-in; `nodemailer` is an optional peer dep loaded only
11
+ * by {@link createSmtpTransport} / {@link createSmtpSender} here.
12
+ *
13
+ * Two layers:
14
+ * - pure + 100%-covered: {@link sendViaSmtp}, {@link validateSmtpConfig},
15
+ * {@link toMailOptions} — driven by an injected transport, unit-tested with a fake.
16
+ * - thin Node wrapper (coverage-excluded): {@link createSmtpTransport},
17
+ * {@link createSmtpSender} — the real `nodemailer.createTransport`, consumer-E2E verified.
18
+ */
19
+ export { sendViaSmtp, validateSmtpConfig, toMailOptions } from "./send.js";
20
+ export { createSmtpTransport, createSmtpSender } from "./transport.js";
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/smtp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pure, injectable generic-SMTP send logic. Everything here is runtime-neutral
3
+ * and network-free: {@link sendViaSmtp} drives an injected {@link SmtpTransport},
4
+ * so a fake transport gives full coverage with no socket. The only piece that
5
+ * needs Node is the thin real-`nodemailer` wrapper in `./transport` (excluded
6
+ * from coverage, consumer-E2E verified).
7
+ */
8
+ import type { SmtpConfig, SmtpMailOptions, SmtpMessage, SmtpSendResult, SmtpTransport } from "./types.js";
9
+ /**
10
+ * Validate and normalize a host-supplied {@link SmtpConfig}, returning a config
11
+ * with `secure` resolved (defaults to `true` for port 465, else `false`). Throws
12
+ * a plain `Error` on an invalid config — the host surfaces it. Pure: no I/O.
13
+ *
14
+ * @param config - The raw host SMTP config.
15
+ * @returns The same config with `secure` resolved to a concrete boolean.
16
+ */
17
+ export declare function validateSmtpConfig(config: SmtpConfig): SmtpConfig & {
18
+ secure: boolean;
19
+ };
20
+ /**
21
+ * Build the transport `sendMail` options from a {@link SmtpMessage} and the
22
+ * resolved config, resolving `from` (message → config default) and guarding the
23
+ * address/subject/header fields against SMTP header (CRLF) injection. Pure.
24
+ *
25
+ * @param message - The outbound message.
26
+ * @param config - The resolved SMTP config (for the `from` default).
27
+ * @returns The mail options to hand to {@link SmtpTransport.sendMail}.
28
+ */
29
+ export declare function toMailOptions(message: SmtpMessage, config: Pick<SmtpConfig, "from">): SmtpMailOptions;
30
+ /**
31
+ * Send one {@link SmtpMessage} through an injected {@link SmtpTransport} and
32
+ * return a normalized {@link SmtpSendResult}. This is the pure, testable core:
33
+ * pass a real `nodemailer` transport in the host's `"use node"` action, or a fake
34
+ * one in a unit test. Throws when the transport throws (the host catches it and
35
+ * calls `markFailed`).
36
+ *
37
+ * @param transport - The injected transport (real nodemailer or a fake).
38
+ * @param message - The message to send.
39
+ * @param config - The resolved config, for the `from` default.
40
+ * @returns The normalized send result — store `messageId` as the queue `providerId`.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // host "use node" action:
45
+ * const transport = createSmtpTransport(config); // real nodemailer
46
+ * const { messageId } = await sendViaSmtp(transport, { to, html }, config);
47
+ * await email.markSent(ctx, id, { providerId: messageId });
48
+ * ```
49
+ */
50
+ export declare function sendViaSmtp(transport: SmtpTransport, message: SmtpMessage, config?: Pick<SmtpConfig, "from">): Promise<SmtpSendResult>;
51
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/smtp/send.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,WAAW,EAEX,cAAc,EACd,aAAa,EACd,MAAM,YAAY,CAAC;AAKpB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,GACjB,UAAU,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAsBlC;AASD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAC/B,eAAe,CAoCjB;AAgBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,EACpB,MAAM,GAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAM,GACpC,OAAO,CAAC,cAAc,CAAC,CAIzB"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Pure, injectable generic-SMTP send logic. Everything here is runtime-neutral
3
+ * and network-free: {@link sendViaSmtp} drives an injected {@link SmtpTransport},
4
+ * so a fake transport gives full coverage with no socket. The only piece that
5
+ * needs Node is the thin real-`nodemailer` wrapper in `./transport` (excluded
6
+ * from coverage, consumer-E2E verified).
7
+ */
8
+ /** A control character (CR/LF) that must never reach a raw SMTP header. */
9
+ const CRLF = /[\r\n]/;
10
+ /**
11
+ * Validate and normalize a host-supplied {@link SmtpConfig}, returning a config
12
+ * with `secure` resolved (defaults to `true` for port 465, else `false`). Throws
13
+ * a plain `Error` on an invalid config — the host surfaces it. Pure: no I/O.
14
+ *
15
+ * @param config - The raw host SMTP config.
16
+ * @returns The same config with `secure` resolved to a concrete boolean.
17
+ */
18
+ export function validateSmtpConfig(config) {
19
+ if (typeof config.host !== "string" || config.host.trim() === "") {
20
+ throw new Error("smtp config: `host` must be a non-empty string");
21
+ }
22
+ if (typeof config.port !== "number" ||
23
+ !Number.isInteger(config.port) ||
24
+ config.port <= 0 ||
25
+ config.port > 65535) {
26
+ throw new Error("smtp config: `port` must be an integer in 1..65535");
27
+ }
28
+ if (config.auth !== undefined) {
29
+ if (typeof config.auth.user !== "string" ||
30
+ typeof config.auth.pass !== "string") {
31
+ throw new Error("smtp config: `auth` requires string `user` and `pass`");
32
+ }
33
+ }
34
+ const secure = config.secure ?? config.port === 465;
35
+ return { ...config, secure };
36
+ }
37
+ /** Guard a single address-like header value against CRLF injection. */
38
+ function assertNoCrlf(label, value) {
39
+ if (CRLF.test(value)) {
40
+ throw new Error(`smtp message: \`${label}\` must not contain CR or LF`);
41
+ }
42
+ }
43
+ /**
44
+ * Build the transport `sendMail` options from a {@link SmtpMessage} and the
45
+ * resolved config, resolving `from` (message → config default) and guarding the
46
+ * address/subject/header fields against SMTP header (CRLF) injection. Pure.
47
+ *
48
+ * @param message - The outbound message.
49
+ * @param config - The resolved SMTP config (for the `from` default).
50
+ * @returns The mail options to hand to {@link SmtpTransport.sendMail}.
51
+ */
52
+ export function toMailOptions(message, config) {
53
+ if (typeof message.to !== "string" || message.to.trim() === "") {
54
+ throw new Error("smtp message: `to` must be a non-empty string");
55
+ }
56
+ const from = message.from ?? config.from;
57
+ if (from === undefined || from.trim() === "") {
58
+ throw new Error("smtp message: `from` is required (pass `message.from` or `config.from`)");
59
+ }
60
+ if (message.text === undefined && message.html === undefined) {
61
+ throw new Error("smtp message: one of `text` or `html` is required");
62
+ }
63
+ assertNoCrlf("to", message.to);
64
+ assertNoCrlf("from", from);
65
+ if (message.replyTo !== undefined) {
66
+ assertNoCrlf("replyTo", message.replyTo);
67
+ }
68
+ if (message.subject !== undefined) {
69
+ assertNoCrlf("subject", message.subject);
70
+ }
71
+ if (message.headers !== undefined) {
72
+ for (const [key, value] of Object.entries(message.headers)) {
73
+ assertNoCrlf(`headers.${key}`, key);
74
+ assertNoCrlf(`headers.${key}`, value);
75
+ }
76
+ }
77
+ return {
78
+ to: message.to,
79
+ from,
80
+ subject: message.subject,
81
+ text: message.text,
82
+ html: message.html,
83
+ replyTo: message.replyTo,
84
+ headers: message.headers,
85
+ };
86
+ }
87
+ /** Flatten a nodemailer address entry (string or `{ address }`) to a string. */
88
+ function addressOf(entry) {
89
+ return typeof entry === "string" ? entry : entry.address;
90
+ }
91
+ /** Normalize a transport's raw send info into the public {@link SmtpSendResult}. */
92
+ function toSendResult(info) {
93
+ return {
94
+ messageId: info.messageId ?? "",
95
+ accepted: (info.accepted ?? []).map(addressOf),
96
+ rejected: (info.rejected ?? []).map(addressOf),
97
+ };
98
+ }
99
+ /**
100
+ * Send one {@link SmtpMessage} through an injected {@link SmtpTransport} and
101
+ * return a normalized {@link SmtpSendResult}. This is the pure, testable core:
102
+ * pass a real `nodemailer` transport in the host's `"use node"` action, or a fake
103
+ * one in a unit test. Throws when the transport throws (the host catches it and
104
+ * calls `markFailed`).
105
+ *
106
+ * @param transport - The injected transport (real nodemailer or a fake).
107
+ * @param message - The message to send.
108
+ * @param config - The resolved config, for the `from` default.
109
+ * @returns The normalized send result — store `messageId` as the queue `providerId`.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * // host "use node" action:
114
+ * const transport = createSmtpTransport(config); // real nodemailer
115
+ * const { messageId } = await sendViaSmtp(transport, { to, html }, config);
116
+ * await email.markSent(ctx, id, { providerId: messageId });
117
+ * ```
118
+ */
119
+ export async function sendViaSmtp(transport, message, config = {}) {
120
+ const options = toMailOptions(message, config);
121
+ const info = await transport.sendMail(options);
122
+ return toSendResult(info);
123
+ }
124
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/smtp/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The thin real-`nodemailer` wrapper — the ONLY piece that needs Node (raw SMTP
3
+ * sockets). It builds a real transport from a host {@link SmtpConfig} and binds it
4
+ * to {@link sendViaSmtp}. The host imports this from its OWN `"use node"` action;
5
+ * a Convex component runs in V8 and cannot host a Node action, so this never runs
6
+ * inside the sandboxed component.
7
+ *
8
+ * `nodemailer` is an **optional peer dependency** — the queue core installs and
9
+ * runs with zero third-party runtime deps. This module is import-tested only when
10
+ * the host opts into SMTP. It is **excluded from `coverage.include`**: it is a
11
+ * trivial pass-through to the real library, consumer-E2E verified (exactly as the
12
+ * `./react` live-backend integration is the consuming app's E2E). The pure
13
+ * {@link sendViaSmtp} + config validation it delegates to ARE covered at 100%.
14
+ */
15
+ import type { SmtpConfig, SmtpSender, SmtpTransport } from "./types.js";
16
+ /**
17
+ * Build a real `nodemailer` SMTP transport from a host {@link SmtpConfig}. The
18
+ * config is validated first (throws on an invalid one). Generic over any SMTP
19
+ * server — Stalwart, Postfix, anything — the host supplies the connection.
20
+ *
21
+ * @param config - The host SMTP connection config.
22
+ * @returns A {@link SmtpTransport} backed by `nodemailer.createTransport`.
23
+ */
24
+ export declare function createSmtpTransport(config: SmtpConfig): SmtpTransport;
25
+ /**
26
+ * Build a bound {@link SmtpSender} over a real `nodemailer` transport: a function
27
+ * that sends one message and returns the normalized result. This is the one call
28
+ * a host wires into its `"use node"` flush action.
29
+ *
30
+ * @param config - The host SMTP connection config.
31
+ * @returns A sender that dispatches one {@link SmtpMessage} via the configured server.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * "use node";
36
+ * import { createSmtpSender } from "@vllnt/convex-email/smtp";
37
+ * const send = createSmtpSender({ host: process.env.SMTP_HOST!, port: 465, secure: true,
38
+ * auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! } });
39
+ * const { messageId } = await send({ to, from, subject, html });
40
+ * ```
41
+ */
42
+ export declare function createSmtpSender(config: SmtpConfig): SmtpSender;
43
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/smtp/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CAQrE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAG/D"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The thin real-`nodemailer` wrapper — the ONLY piece that needs Node (raw SMTP
3
+ * sockets). It builds a real transport from a host {@link SmtpConfig} and binds it
4
+ * to {@link sendViaSmtp}. The host imports this from its OWN `"use node"` action;
5
+ * a Convex component runs in V8 and cannot host a Node action, so this never runs
6
+ * inside the sandboxed component.
7
+ *
8
+ * `nodemailer` is an **optional peer dependency** — the queue core installs and
9
+ * runs with zero third-party runtime deps. This module is import-tested only when
10
+ * the host opts into SMTP. It is **excluded from `coverage.include`**: it is a
11
+ * trivial pass-through to the real library, consumer-E2E verified (exactly as the
12
+ * `./react` live-backend integration is the consuming app's E2E). The pure
13
+ * {@link sendViaSmtp} + config validation it delegates to ARE covered at 100%.
14
+ */
15
+ import nodemailer from "nodemailer";
16
+ import { sendViaSmtp, validateSmtpConfig } from "./send.js";
17
+ /**
18
+ * Build a real `nodemailer` SMTP transport from a host {@link SmtpConfig}. The
19
+ * config is validated first (throws on an invalid one). Generic over any SMTP
20
+ * server — Stalwart, Postfix, anything — the host supplies the connection.
21
+ *
22
+ * @param config - The host SMTP connection config.
23
+ * @returns A {@link SmtpTransport} backed by `nodemailer.createTransport`.
24
+ */
25
+ export function createSmtpTransport(config) {
26
+ const resolved = validateSmtpConfig(config);
27
+ return nodemailer.createTransport({
28
+ host: resolved.host,
29
+ port: resolved.port,
30
+ secure: resolved.secure,
31
+ auth: resolved.auth,
32
+ });
33
+ }
34
+ /**
35
+ * Build a bound {@link SmtpSender} over a real `nodemailer` transport: a function
36
+ * that sends one message and returns the normalized result. This is the one call
37
+ * a host wires into its `"use node"` flush action.
38
+ *
39
+ * @param config - The host SMTP connection config.
40
+ * @returns A sender that dispatches one {@link SmtpMessage} via the configured server.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * "use node";
45
+ * import { createSmtpSender } from "@vllnt/convex-email/smtp";
46
+ * const send = createSmtpSender({ host: process.env.SMTP_HOST!, port: 465, secure: true,
47
+ * auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! } });
48
+ * const { messageId } = await send({ to, from, subject, html });
49
+ * ```
50
+ */
51
+ export function createSmtpSender(config) {
52
+ const transport = createSmtpTransport(config);
53
+ return (message) => sendViaSmtp(transport, message, config);
54
+ }
55
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/smtp/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAG5D;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IACpD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO,UAAU,CAAC,eAAe,CAAC;QAChC,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,IAAI,EAAE,QAAQ,CAAC,IAAI;KACpB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC"}