@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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/client/index.d.ts +174 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +151 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +84 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +40 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +110 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +7 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/crons.d.ts +16 -0
- package/dist/component/crons.d.ts.map +1 -0
- package/dist/component/crons.js +19 -0
- package/dist/component/crons.js.map +1 -0
- package/dist/component/mutations.d.ts +95 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +243 -0
- package/dist/component/mutations.js.map +1 -0
- package/dist/component/queries.d.ts +59 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +61 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +54 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +40 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/validators.d.ts +54 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +40 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/jmap/index.d.ts +22 -0
- package/dist/jmap/index.d.ts.map +1 -0
- package/dist/jmap/index.js +21 -0
- package/dist/jmap/index.js.map +1 -0
- package/dist/jmap/send.d.ts +125 -0
- package/dist/jmap/send.d.ts.map +1 -0
- package/dist/jmap/send.js +418 -0
- package/dist/jmap/send.js.map +1 -0
- package/dist/jmap/types.d.ts +107 -0
- package/dist/jmap/types.d.ts.map +1 -0
- package/dist/jmap/types.js +16 -0
- package/dist/jmap/types.js.map +1 -0
- package/dist/shared.d.ts +32 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +33 -0
- package/dist/shared.js.map +1 -0
- package/dist/smtp/index.d.ts +22 -0
- package/dist/smtp/index.d.ts.map +1 -0
- package/dist/smtp/index.js +21 -0
- package/dist/smtp/index.js.map +1 -0
- package/dist/smtp/send.d.ts +51 -0
- package/dist/smtp/send.d.ts.map +1 -0
- package/dist/smtp/send.js +124 -0
- package/dist/smtp/send.js.map +1 -0
- package/dist/smtp/transport.d.ts +43 -0
- package/dist/smtp/transport.d.ts.map +1 -0
- package/dist/smtp/transport.js +55 -0
- package/dist/smtp/transport.js.map +1 -0
- package/dist/smtp/types.d.ts +122 -0
- package/dist/smtp/types.d.ts.map +1 -0
- package/dist/smtp/types.js +9 -0
- package/dist/smtp/types.js.map +1 -0
- package/package.json +118 -0
- package/src/client/index.ts +312 -0
- package/src/client/types.ts +90 -0
- package/src/component/_generated/api.ts +56 -0
- package/src/component/_generated/component.ts +134 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +9 -0
- package/src/component/crons.ts +23 -0
- package/src/component/mutations.ts +262 -0
- package/src/component/queries.ts +70 -0
- package/src/component/schema.ts +40 -0
- package/src/component/validators.ts +47 -0
- package/src/jmap/index.ts +39 -0
- package/src/jmap/send.test.ts +565 -0
- package/src/jmap/send.ts +502 -0
- package/src/jmap/types.ts +117 -0
- package/src/shared.ts +41 -0
- package/src/smtp/index.ts +30 -0
- package/src/smtp/send.test.ts +240 -0
- package/src/smtp/send.ts +154 -0
- package/src/smtp/transport.ts +58 -0
- package/src/smtp/types.ts +124 -0
- package/src/test.ts +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/jmap/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, injectable generic-JMAP send logic. Everything here is runtime-neutral
|
|
3
|
+
* and network-free: it drives an injected {@link JmapFetch} (the host's runtime
|
|
4
|
+
* `fetch`), so a fake gives full coverage with no socket. There is no Node-only
|
|
5
|
+
* piece — JMAP is plain HTTP — so the whole adapter is covered at 100%.
|
|
6
|
+
*
|
|
7
|
+
* Sending is the JMAP two-call batch (RFC 8621): `Email/set` creates the message
|
|
8
|
+
* in a mailbox, then `EmailSubmission/set` submits it. {@link discoverJmapSession}
|
|
9
|
+
* resolves the account / identity / mailbox ids from a JMAP session so a host need
|
|
10
|
+
* not hand-wire them.
|
|
11
|
+
*/
|
|
12
|
+
import type { JmapConfig, JmapDiscoverOptions, JmapFetch, JmapMessage, JmapSendResult, JmapSender } from "./types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Validate a host-supplied {@link JmapConfig}: every id must be a non-empty
|
|
15
|
+
* string. Throws a plain `Error` on an invalid config — the host surfaces it.
|
|
16
|
+
* Pure: no I/O.
|
|
17
|
+
*
|
|
18
|
+
* @param config - The resolved JMAP connection config.
|
|
19
|
+
* @returns The same config, unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateJmapConfig(config: JmapConfig): JmapConfig;
|
|
22
|
+
/** The JMAP `Email` create object plus the submission envelope for one message. */
|
|
23
|
+
interface EmailCreate {
|
|
24
|
+
/** The JMAP `Email/set` create object. */
|
|
25
|
+
email: Record<string, unknown>;
|
|
26
|
+
/** The `EmailSubmission` envelope (`mailFrom` + `rcptTo`). */
|
|
27
|
+
envelope: {
|
|
28
|
+
mailFrom: {
|
|
29
|
+
email: string;
|
|
30
|
+
};
|
|
31
|
+
rcptTo: Array<{
|
|
32
|
+
email: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the JMAP `Email` create object and the submission envelope from a
|
|
38
|
+
* {@link JmapMessage}: resolve `from` (message → `config.from`), require one of
|
|
39
|
+
* `text`/`html`, and reject CR/LF in `to`/`from`/`replyTo`/`subject`/`headers`.
|
|
40
|
+
* Pure.
|
|
41
|
+
*
|
|
42
|
+
* @param message - The outbound message.
|
|
43
|
+
* @param config - The resolved config (for the `from` default + the mailbox).
|
|
44
|
+
* @returns The `Email` create object and the submission envelope.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildEmailCreate(message: JmapMessage, config: Pick<JmapConfig, "from" | "mailboxId">): EmailCreate;
|
|
47
|
+
/**
|
|
48
|
+
* Build the full JMAP request body for sending one {@link JmapMessage}: an
|
|
49
|
+
* `Email/set` create (creation id `draft`) followed by an `EmailSubmission/set`
|
|
50
|
+
* that back-references it (`#draft`). Pure.
|
|
51
|
+
*
|
|
52
|
+
* @param message - The message to send.
|
|
53
|
+
* @param config - The resolved JMAP config.
|
|
54
|
+
* @returns The JMAP request body to POST to the endpoint.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildSubmitRequest(message: JmapMessage, config: JmapConfig): {
|
|
57
|
+
using: string[];
|
|
58
|
+
methodCalls: unknown[];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Parse a JMAP send response into a {@link JmapSendResult}: reject method-level
|
|
62
|
+
* errors and `notCreated` entries, then read the created `Email` id and the
|
|
63
|
+
* `EmailSubmission` id. Throws on any failure (the host catches it and calls
|
|
64
|
+
* `markFailed`). Pure.
|
|
65
|
+
*
|
|
66
|
+
* @param json - The parsed JMAP response body.
|
|
67
|
+
* @returns The created email id and submission id (the latter as `messageId`).
|
|
68
|
+
*/
|
|
69
|
+
export declare function parseSubmitResponse(json: unknown): JmapSendResult;
|
|
70
|
+
/**
|
|
71
|
+
* Send one {@link JmapMessage} through an injected {@link JmapFetch} and return a
|
|
72
|
+
* normalized {@link JmapSendResult}. Validates the config, builds the two-call
|
|
73
|
+
* JMAP batch, POSTs it, and parses the result. Throws on an HTTP or JMAP error
|
|
74
|
+
* (the host catches it and calls `markFailed`). Pure: pass the host's runtime
|
|
75
|
+
* `fetch` in production, or a fake in a unit test.
|
|
76
|
+
*
|
|
77
|
+
* @param fetchFn - The injected `fetch` (the host's runtime `fetch`, or a fake).
|
|
78
|
+
* @param message - The message to send.
|
|
79
|
+
* @param config - The resolved JMAP config.
|
|
80
|
+
* @returns The normalized send result — store `messageId` as the queue `providerId`.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* // host Convex action (no "use node" — fetch runs in V8):
|
|
85
|
+
* const { messageId } = await sendViaJmap((u, i) => fetch(u, i), { to, html }, config);
|
|
86
|
+
* await email.markSent(ctx, id, { providerId: messageId });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function sendViaJmap(fetchFn: JmapFetch, message: JmapMessage, config: JmapConfig): Promise<JmapSendResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a {@link JmapConfig} from a JMAP session: fetch the session resource
|
|
92
|
+
* for the `apiUrl` + primary mail account, then `Identity/get` for the sending
|
|
93
|
+
* identity and `Mailbox/get` for the Sent (or Drafts) mailbox. Pure: drives the
|
|
94
|
+
* injected {@link JmapFetch}. The host runs this once and passes the result to
|
|
95
|
+
* {@link createJmapSender} / {@link sendViaJmap}.
|
|
96
|
+
*
|
|
97
|
+
* @param fetchFn - The injected `fetch`.
|
|
98
|
+
* @param opts - The session URL, bearer token, and optional preferred `from`.
|
|
99
|
+
* @returns A resolved {@link JmapConfig} ready to send with.
|
|
100
|
+
*/
|
|
101
|
+
export declare function discoverJmapSession(fetchFn: JmapFetch, opts: JmapDiscoverOptions): Promise<JmapConfig>;
|
|
102
|
+
/**
|
|
103
|
+
* Build a bound {@link JmapSender} over a resolved config and an injected
|
|
104
|
+
* `fetch`: a function that sends one message and returns the normalized result.
|
|
105
|
+
* Validates the config eagerly. This is the one call a host wires into its flush
|
|
106
|
+
* action (a plain Convex action — no `"use node"`).
|
|
107
|
+
*
|
|
108
|
+
* @param config - The resolved JMAP config (e.g. from {@link discoverJmapSession}).
|
|
109
|
+
* @param fetchFn - The host's runtime `fetch`.
|
|
110
|
+
* @returns A sender that dispatches one {@link JmapMessage} via the configured server.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const config = await discoverJmapSession((u, i) => fetch(u, i), {
|
|
115
|
+
* sessionUrl: "https://mail.example.com/.well-known/jmap",
|
|
116
|
+
* token: process.env.JMAP_TOKEN!,
|
|
117
|
+
* from: "no-reply@app.com",
|
|
118
|
+
* });
|
|
119
|
+
* const send = createJmapSender(config, (u, i) => fetch(u, i));
|
|
120
|
+
* const { messageId } = await send({ to, subject, html });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export declare function createJmapSender(config: JmapConfig, fetchFn: JmapFetch): JmapSender;
|
|
124
|
+
export {};
|
|
125
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/jmap/send.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,mBAAmB,EACnB,SAAS,EACT,WAAW,EACX,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC;AA8BpB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAejE;AAED,mFAAmF;AACnF,UAAU,WAAW;IACnB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,8DAA8D;IAC9D,QAAQ,EAAE;QACR,QAAQ,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5B,MAAM,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAClC,CAAC;CACH;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,CAAC,GAC7C,WAAW,CAiFb;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,GACjB;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,OAAO,EAAE,CAAA;CAAE,CAsB7C;AA4CD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAkBjE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,cAAc,CAAC,CAgBzB;AAyED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,UAAU,CAAC,CA0DrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,SAAS,GACjB,UAAU,CAGZ"}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, injectable generic-JMAP send logic. Everything here is runtime-neutral
|
|
3
|
+
* and network-free: it drives an injected {@link JmapFetch} (the host's runtime
|
|
4
|
+
* `fetch`), so a fake gives full coverage with no socket. There is no Node-only
|
|
5
|
+
* piece — JMAP is plain HTTP — so the whole adapter is covered at 100%.
|
|
6
|
+
*
|
|
7
|
+
* Sending is the JMAP two-call batch (RFC 8621): `Email/set` creates the message
|
|
8
|
+
* in a mailbox, then `EmailSubmission/set` submits it. {@link discoverJmapSession}
|
|
9
|
+
* resolves the account / identity / mailbox ids from a JMAP session so a host need
|
|
10
|
+
* not hand-wire them.
|
|
11
|
+
*/
|
|
12
|
+
/** A control character (CR/LF) that must never reach a header value. */
|
|
13
|
+
const CRLF = /[\r\n]/;
|
|
14
|
+
/** The JMAP capability URNs the adapter uses. */
|
|
15
|
+
const CAP_CORE = "urn:ietf:params:jmap:core";
|
|
16
|
+
const CAP_MAIL = "urn:ietf:params:jmap:mail";
|
|
17
|
+
const CAP_SUBMISSION = "urn:ietf:params:jmap:submission";
|
|
18
|
+
/** Narrow an unknown value to a plain object without a type assertion. */
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return typeof value === "object" && value !== null;
|
|
21
|
+
}
|
|
22
|
+
/** Guard a single header-bound value against CR/LF injection. */
|
|
23
|
+
function assertNoCrlf(label, value) {
|
|
24
|
+
if (CRLF.test(value)) {
|
|
25
|
+
throw new Error(`jmap message: \`${label}\` must not contain CR or LF`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Split a comma-separated address list into trimmed, non-empty addresses. */
|
|
29
|
+
function splitAddresses(raw) {
|
|
30
|
+
return raw
|
|
31
|
+
.split(",")
|
|
32
|
+
.map((a) => a.trim())
|
|
33
|
+
.filter((a) => a.length > 0);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate a host-supplied {@link JmapConfig}: every id must be a non-empty
|
|
37
|
+
* string. Throws a plain `Error` on an invalid config — the host surfaces it.
|
|
38
|
+
* Pure: no I/O.
|
|
39
|
+
*
|
|
40
|
+
* @param config - The resolved JMAP connection config.
|
|
41
|
+
* @returns The same config, unchanged.
|
|
42
|
+
*/
|
|
43
|
+
export function validateJmapConfig(config) {
|
|
44
|
+
const keys = [
|
|
45
|
+
"endpoint",
|
|
46
|
+
"token",
|
|
47
|
+
"accountId",
|
|
48
|
+
"identityId",
|
|
49
|
+
"mailboxId",
|
|
50
|
+
];
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
const value = config[key];
|
|
53
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
54
|
+
throw new Error(`jmap config: \`${key}\` must be a non-empty string`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build the JMAP `Email` create object and the submission envelope from a
|
|
61
|
+
* {@link JmapMessage}: resolve `from` (message → `config.from`), require one of
|
|
62
|
+
* `text`/`html`, and reject CR/LF in `to`/`from`/`replyTo`/`subject`/`headers`.
|
|
63
|
+
* Pure.
|
|
64
|
+
*
|
|
65
|
+
* @param message - The outbound message.
|
|
66
|
+
* @param config - The resolved config (for the `from` default + the mailbox).
|
|
67
|
+
* @returns The `Email` create object and the submission envelope.
|
|
68
|
+
*/
|
|
69
|
+
export function buildEmailCreate(message, config) {
|
|
70
|
+
if (typeof message.to !== "string" || message.to.trim() === "") {
|
|
71
|
+
throw new Error("jmap message: `to` must be a non-empty string");
|
|
72
|
+
}
|
|
73
|
+
const from = message.from ?? config.from;
|
|
74
|
+
if (from === undefined || from.trim() === "") {
|
|
75
|
+
throw new Error("jmap message: `from` is required (pass `message.from` or `config.from`)");
|
|
76
|
+
}
|
|
77
|
+
if (message.text === undefined && message.html === undefined) {
|
|
78
|
+
throw new Error("jmap message: one of `text` or `html` is required");
|
|
79
|
+
}
|
|
80
|
+
const recipients = splitAddresses(message.to);
|
|
81
|
+
if (recipients.length === 0) {
|
|
82
|
+
throw new Error("jmap message: `to` must contain at least one address");
|
|
83
|
+
}
|
|
84
|
+
assertNoCrlf("from", from);
|
|
85
|
+
for (const rcpt of recipients) {
|
|
86
|
+
assertNoCrlf("to", rcpt);
|
|
87
|
+
}
|
|
88
|
+
if (message.replyTo !== undefined) {
|
|
89
|
+
assertNoCrlf("replyTo", message.replyTo);
|
|
90
|
+
}
|
|
91
|
+
if (message.subject !== undefined) {
|
|
92
|
+
assertNoCrlf("subject", message.subject);
|
|
93
|
+
}
|
|
94
|
+
let body;
|
|
95
|
+
if (message.text !== undefined && message.html !== undefined) {
|
|
96
|
+
body = {
|
|
97
|
+
bodyStructure: {
|
|
98
|
+
type: "multipart/alternative",
|
|
99
|
+
subParts: [
|
|
100
|
+
{ type: "text/plain", partId: "text" },
|
|
101
|
+
{ type: "text/html", partId: "html" },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
bodyValues: {
|
|
105
|
+
text: { value: message.text },
|
|
106
|
+
html: { value: message.html },
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
else if (message.html !== undefined) {
|
|
111
|
+
body = {
|
|
112
|
+
bodyStructure: { type: "text/html", partId: "html" },
|
|
113
|
+
bodyValues: { html: { value: message.html } },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
body = {
|
|
118
|
+
bodyStructure: { type: "text/plain", partId: "text" },
|
|
119
|
+
bodyValues: { text: { value: message.text } },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const email = {
|
|
123
|
+
mailboxIds: { [config.mailboxId]: true },
|
|
124
|
+
keywords: { $seen: true },
|
|
125
|
+
from: [{ email: from }],
|
|
126
|
+
to: recipients.map((email) => ({ email })),
|
|
127
|
+
...(message.subject !== undefined ? { subject: message.subject } : {}),
|
|
128
|
+
...(message.replyTo !== undefined
|
|
129
|
+
? { replyTo: [{ email: message.replyTo }] }
|
|
130
|
+
: {}),
|
|
131
|
+
...body,
|
|
132
|
+
};
|
|
133
|
+
if (message.headers !== undefined) {
|
|
134
|
+
for (const [key, value] of Object.entries(message.headers)) {
|
|
135
|
+
assertNoCrlf(`headers.${key}`, key);
|
|
136
|
+
assertNoCrlf(`headers.${key}`, value);
|
|
137
|
+
email[`header:${key}:asText`] = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
email,
|
|
142
|
+
envelope: {
|
|
143
|
+
mailFrom: { email: from },
|
|
144
|
+
rcptTo: recipients.map((email) => ({ email })),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build the full JMAP request body for sending one {@link JmapMessage}: an
|
|
150
|
+
* `Email/set` create (creation id `draft`) followed by an `EmailSubmission/set`
|
|
151
|
+
* that back-references it (`#draft`). Pure.
|
|
152
|
+
*
|
|
153
|
+
* @param message - The message to send.
|
|
154
|
+
* @param config - The resolved JMAP config.
|
|
155
|
+
* @returns The JMAP request body to POST to the endpoint.
|
|
156
|
+
*/
|
|
157
|
+
export function buildSubmitRequest(message, config) {
|
|
158
|
+
const { email, envelope } = buildEmailCreate(message, config);
|
|
159
|
+
return {
|
|
160
|
+
using: [CAP_CORE, CAP_MAIL, CAP_SUBMISSION],
|
|
161
|
+
methodCalls: [
|
|
162
|
+
["Email/set", { accountId: config.accountId, create: { draft: email } }, "0"],
|
|
163
|
+
[
|
|
164
|
+
"EmailSubmission/set",
|
|
165
|
+
{
|
|
166
|
+
accountId: config.accountId,
|
|
167
|
+
create: {
|
|
168
|
+
sub: {
|
|
169
|
+
emailId: "#draft",
|
|
170
|
+
identityId: config.identityId,
|
|
171
|
+
envelope,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
"1",
|
|
176
|
+
],
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Find the arguments of the first method response with the given name. */
|
|
181
|
+
function findInvocation(responses, name) {
|
|
182
|
+
for (const inv of responses) {
|
|
183
|
+
if (Array.isArray(inv) && inv[0] === name && isRecord(inv[1])) {
|
|
184
|
+
return inv[1];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
/** Describe a JMAP `SetError` (its `type`, or `"unknown"`). */
|
|
190
|
+
function describeSetError(value) {
|
|
191
|
+
return isRecord(value) && typeof value.type === "string"
|
|
192
|
+
? value.type
|
|
193
|
+
: "unknown";
|
|
194
|
+
}
|
|
195
|
+
/** Read a created object's id from a `Foo/set` response, throwing on `notCreated`. */
|
|
196
|
+
function readCreatedId(args, createId, label) {
|
|
197
|
+
const notCreated = args.notCreated;
|
|
198
|
+
if (isRecord(notCreated) && createId in notCreated) {
|
|
199
|
+
throw new Error(`jmap: ${label} not created (${describeSetError(notCreated[createId])})`);
|
|
200
|
+
}
|
|
201
|
+
const created = args.created;
|
|
202
|
+
if (isRecord(created)) {
|
|
203
|
+
const entry = created[createId];
|
|
204
|
+
if (isRecord(entry) && typeof entry.id === "string") {
|
|
205
|
+
return entry.id;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw new Error(`jmap: ${label} not created (no id in response)`);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Parse a JMAP send response into a {@link JmapSendResult}: reject method-level
|
|
212
|
+
* errors and `notCreated` entries, then read the created `Email` id and the
|
|
213
|
+
* `EmailSubmission` id. Throws on any failure (the host catches it and calls
|
|
214
|
+
* `markFailed`). Pure.
|
|
215
|
+
*
|
|
216
|
+
* @param json - The parsed JMAP response body.
|
|
217
|
+
* @returns The created email id and submission id (the latter as `messageId`).
|
|
218
|
+
*/
|
|
219
|
+
export function parseSubmitResponse(json) {
|
|
220
|
+
if (!isRecord(json) || !Array.isArray(json.methodResponses)) {
|
|
221
|
+
throw new Error("jmap: malformed response (no methodResponses)");
|
|
222
|
+
}
|
|
223
|
+
const responses = json.methodResponses;
|
|
224
|
+
for (const inv of responses) {
|
|
225
|
+
if (Array.isArray(inv) && inv[0] === "error") {
|
|
226
|
+
throw new Error(`jmap: method error (${describeSetError(inv[1])})`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const emailArgs = findInvocation(responses, "Email/set");
|
|
230
|
+
const subArgs = findInvocation(responses, "EmailSubmission/set");
|
|
231
|
+
if (emailArgs === null || subArgs === null) {
|
|
232
|
+
throw new Error("jmap: response missing Email/set or EmailSubmission/set");
|
|
233
|
+
}
|
|
234
|
+
const emailId = readCreatedId(emailArgs, "draft", "Email");
|
|
235
|
+
const messageId = readCreatedId(subArgs, "sub", "EmailSubmission");
|
|
236
|
+
return { messageId, emailId };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Send one {@link JmapMessage} through an injected {@link JmapFetch} and return a
|
|
240
|
+
* normalized {@link JmapSendResult}. Validates the config, builds the two-call
|
|
241
|
+
* JMAP batch, POSTs it, and parses the result. Throws on an HTTP or JMAP error
|
|
242
|
+
* (the host catches it and calls `markFailed`). Pure: pass the host's runtime
|
|
243
|
+
* `fetch` in production, or a fake in a unit test.
|
|
244
|
+
*
|
|
245
|
+
* @param fetchFn - The injected `fetch` (the host's runtime `fetch`, or a fake).
|
|
246
|
+
* @param message - The message to send.
|
|
247
|
+
* @param config - The resolved JMAP config.
|
|
248
|
+
* @returns The normalized send result — store `messageId` as the queue `providerId`.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* // host Convex action (no "use node" — fetch runs in V8):
|
|
253
|
+
* const { messageId } = await sendViaJmap((u, i) => fetch(u, i), { to, html }, config);
|
|
254
|
+
* await email.markSent(ctx, id, { providerId: messageId });
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export async function sendViaJmap(fetchFn, message, config) {
|
|
258
|
+
validateJmapConfig(config);
|
|
259
|
+
const body = buildSubmitRequest(message, config);
|
|
260
|
+
const res = await fetchFn(config.endpoint, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
Authorization: `Bearer ${config.token}`,
|
|
264
|
+
"Content-Type": "application/json",
|
|
265
|
+
Accept: "application/json",
|
|
266
|
+
},
|
|
267
|
+
body: JSON.stringify(body),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
throw new Error(`jmap: request failed (HTTP ${res.status})`);
|
|
271
|
+
}
|
|
272
|
+
return parseSubmitResponse(await res.json());
|
|
273
|
+
}
|
|
274
|
+
/** POST a JMAP request and return its `methodResponses`, throwing on HTTP/shape errors. */
|
|
275
|
+
async function jmapCall(fetchFn, endpoint, token, request) {
|
|
276
|
+
const res = await fetchFn(endpoint, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: {
|
|
279
|
+
Authorization: `Bearer ${token}`,
|
|
280
|
+
"Content-Type": "application/json",
|
|
281
|
+
Accept: "application/json",
|
|
282
|
+
},
|
|
283
|
+
body: JSON.stringify(request),
|
|
284
|
+
});
|
|
285
|
+
if (!res.ok) {
|
|
286
|
+
throw new Error(`jmap: request failed (HTTP ${res.status})`);
|
|
287
|
+
}
|
|
288
|
+
const json = await res.json();
|
|
289
|
+
if (!isRecord(json) || !Array.isArray(json.methodResponses)) {
|
|
290
|
+
throw new Error("jmap: malformed response (no methodResponses)");
|
|
291
|
+
}
|
|
292
|
+
return json.methodResponses;
|
|
293
|
+
}
|
|
294
|
+
/** Pick the sending identity (by `from`, else the first) from an `Identity/get` list. */
|
|
295
|
+
function pickIdentity(list, from) {
|
|
296
|
+
const valid = [];
|
|
297
|
+
for (const item of list) {
|
|
298
|
+
if (isRecord(item) &&
|
|
299
|
+
typeof item.id === "string" &&
|
|
300
|
+
typeof item.email === "string") {
|
|
301
|
+
valid.push({ id: item.id, email: item.email });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const lower = from?.toLowerCase();
|
|
305
|
+
const matched = lower !== undefined
|
|
306
|
+
? valid.find((i) => i.email.toLowerCase() === lower)
|
|
307
|
+
: undefined;
|
|
308
|
+
return matched ?? valid[0] ?? null;
|
|
309
|
+
}
|
|
310
|
+
/** Pick the mailbox to file the sent copy in: role `sent`, else `drafts`, else first. */
|
|
311
|
+
function pickMailboxId(list) {
|
|
312
|
+
const valid = [];
|
|
313
|
+
for (const item of list) {
|
|
314
|
+
if (isRecord(item) && typeof item.id === "string") {
|
|
315
|
+
valid.push({
|
|
316
|
+
id: item.id,
|
|
317
|
+
role: typeof item.role === "string" ? item.role : null,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const sent = valid.find((m) => m.role === "sent");
|
|
322
|
+
if (sent !== undefined) {
|
|
323
|
+
return sent.id;
|
|
324
|
+
}
|
|
325
|
+
const drafts = valid.find((m) => m.role === "drafts");
|
|
326
|
+
if (drafts !== undefined) {
|
|
327
|
+
return drafts.id;
|
|
328
|
+
}
|
|
329
|
+
return valid[0]?.id ?? null;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Resolve a {@link JmapConfig} from a JMAP session: fetch the session resource
|
|
333
|
+
* for the `apiUrl` + primary mail account, then `Identity/get` for the sending
|
|
334
|
+
* identity and `Mailbox/get` for the Sent (or Drafts) mailbox. Pure: drives the
|
|
335
|
+
* injected {@link JmapFetch}. The host runs this once and passes the result to
|
|
336
|
+
* {@link createJmapSender} / {@link sendViaJmap}.
|
|
337
|
+
*
|
|
338
|
+
* @param fetchFn - The injected `fetch`.
|
|
339
|
+
* @param opts - The session URL, bearer token, and optional preferred `from`.
|
|
340
|
+
* @returns A resolved {@link JmapConfig} ready to send with.
|
|
341
|
+
*/
|
|
342
|
+
export async function discoverJmapSession(fetchFn, opts) {
|
|
343
|
+
if (typeof opts.sessionUrl !== "string" || opts.sessionUrl.trim() === "") {
|
|
344
|
+
throw new Error("jmap discover: `sessionUrl` must be a non-empty string");
|
|
345
|
+
}
|
|
346
|
+
if (typeof opts.token !== "string" || opts.token.trim() === "") {
|
|
347
|
+
throw new Error("jmap discover: `token` must be a non-empty string");
|
|
348
|
+
}
|
|
349
|
+
const sres = await fetchFn(opts.sessionUrl, {
|
|
350
|
+
method: "GET",
|
|
351
|
+
headers: { Authorization: `Bearer ${opts.token}`, Accept: "application/json" },
|
|
352
|
+
});
|
|
353
|
+
if (!sres.ok) {
|
|
354
|
+
throw new Error(`jmap discover: session request failed (HTTP ${sres.status})`);
|
|
355
|
+
}
|
|
356
|
+
const session = await sres.json();
|
|
357
|
+
if (!isRecord(session) || typeof session.apiUrl !== "string") {
|
|
358
|
+
throw new Error("jmap discover: session has no apiUrl");
|
|
359
|
+
}
|
|
360
|
+
const accounts = session.primaryAccounts;
|
|
361
|
+
const accountId = isRecord(accounts) ? accounts[CAP_MAIL] : undefined;
|
|
362
|
+
if (typeof accountId !== "string") {
|
|
363
|
+
throw new Error("jmap discover: no primary mail account");
|
|
364
|
+
}
|
|
365
|
+
const endpoint = session.apiUrl;
|
|
366
|
+
const idResponses = await jmapCall(fetchFn, endpoint, opts.token, {
|
|
367
|
+
using: [CAP_CORE, CAP_SUBMISSION],
|
|
368
|
+
methodCalls: [["Identity/get", { accountId, ids: null }, "0"]],
|
|
369
|
+
});
|
|
370
|
+
const idArgs = findInvocation(idResponses, "Identity/get");
|
|
371
|
+
const identity = pickIdentity(idArgs !== null && Array.isArray(idArgs.list) ? idArgs.list : [], opts.from);
|
|
372
|
+
if (identity === null) {
|
|
373
|
+
throw new Error("jmap discover: no sending identity found");
|
|
374
|
+
}
|
|
375
|
+
const mbResponses = await jmapCall(fetchFn, endpoint, opts.token, {
|
|
376
|
+
using: [CAP_CORE, CAP_MAIL],
|
|
377
|
+
methodCalls: [["Mailbox/get", { accountId, ids: null }, "0"]],
|
|
378
|
+
});
|
|
379
|
+
const mbArgs = findInvocation(mbResponses, "Mailbox/get");
|
|
380
|
+
const mailboxId = pickMailboxId(mbArgs !== null && Array.isArray(mbArgs.list) ? mbArgs.list : []);
|
|
381
|
+
if (mailboxId === null) {
|
|
382
|
+
throw new Error("jmap discover: no sent or drafts mailbox found");
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
endpoint,
|
|
386
|
+
token: opts.token,
|
|
387
|
+
accountId,
|
|
388
|
+
identityId: identity.id,
|
|
389
|
+
mailboxId,
|
|
390
|
+
from: opts.from ?? identity.email,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Build a bound {@link JmapSender} over a resolved config and an injected
|
|
395
|
+
* `fetch`: a function that sends one message and returns the normalized result.
|
|
396
|
+
* Validates the config eagerly. This is the one call a host wires into its flush
|
|
397
|
+
* action (a plain Convex action — no `"use node"`).
|
|
398
|
+
*
|
|
399
|
+
* @param config - The resolved JMAP config (e.g. from {@link discoverJmapSession}).
|
|
400
|
+
* @param fetchFn - The host's runtime `fetch`.
|
|
401
|
+
* @returns A sender that dispatches one {@link JmapMessage} via the configured server.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* const config = await discoverJmapSession((u, i) => fetch(u, i), {
|
|
406
|
+
* sessionUrl: "https://mail.example.com/.well-known/jmap",
|
|
407
|
+
* token: process.env.JMAP_TOKEN!,
|
|
408
|
+
* from: "no-reply@app.com",
|
|
409
|
+
* });
|
|
410
|
+
* const send = createJmapSender(config, (u, i) => fetch(u, i));
|
|
411
|
+
* const { messageId } = await send({ to, subject, html });
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
export function createJmapSender(config, fetchFn) {
|
|
415
|
+
validateJmapConfig(config);
|
|
416
|
+
return (message) => sendViaJmap(fetchFn, message, config);
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/jmap/send.ts"],"names":[],"mappings":"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"}
|