@yozz.app/smtp 0.1.1 → 0.1.3
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/README.md +3 -2
- package/dist/index.d.mts +30 -4
- package/dist/index.mjs +15 -1
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -8,12 +8,13 @@ pnpm add @yozz.app/smtp
|
|
|
8
8
|
|
|
9
9
|
## The seam
|
|
10
10
|
|
|
11
|
-
`@yozz.app/smtp` speaks SMTP over any `ByteDuplex`
|
|
11
|
+
`@yozz.app/smtp` speaks SMTP over any `ByteDuplex` (`{ read, write }`, a type it declares itself; a
|
|
12
|
+
`@yozz.app/tls` connection satisfies it). No runtime dependencies. It knows replies, EHLO
|
|
12
13
|
keywords, AUTH PLAIN / LOGIN, the MAIL / RCPT / DATA sequence and dot-stuffing. It **never knows**
|
|
13
14
|
TLS, certificates or the vault. STARTTLS is not spoken: the transport is already TLS (465).
|
|
14
15
|
|
|
15
16
|
`buildMessage` turns composer fields into 7-bit bytes: RFC 2047 headers, 7bit or quoted-printable bodies,
|
|
16
|
-
`multipart/alternative` when an HTML rendering is given, `In-Reply-To` + `References` for replies.
|
|
17
|
+
`multipart/alternative` when an HTML rendering is given, `In-Reply-To` + the folded `References` chain for replies.
|
|
17
18
|
|
|
18
19
|
## Tests
|
|
19
20
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/transport.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What this client needs from a transport: bytes in, bytes out. Declared here rather than
|
|
4
|
+
* imported from `@yozz.app/tls` so the package has no runtime dependency at all; it is the same
|
|
5
|
+
* two-method shape, so a `TlsConnection` satisfies it structurally.
|
|
6
|
+
*/
|
|
7
|
+
type ByteDuplex = {
|
|
8
|
+
readonly read: () => Promise<Uint8Array | null>;
|
|
9
|
+
readonly write: (bytes: Uint8Array) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
3
12
|
//#region src/reply.d.ts
|
|
4
13
|
type SmtpReply = {
|
|
5
14
|
readonly code: number; /** One entry per line, the `NNN-` / `NNN ` prefix removed. */
|
|
@@ -68,8 +77,25 @@ type MessageInput = {
|
|
|
68
77
|
readonly messageId: string;
|
|
69
78
|
readonly text: string; /** When present the message is `multipart/alternative`, text first. */
|
|
70
79
|
readonly html?: string;
|
|
71
|
-
readonly inReplyTo?: string;
|
|
80
|
+
readonly inReplyTo?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The parent's `References` followed by its Message-ID, oldest first — the chain RFC 5322 §3.6.4
|
|
83
|
+
* asks a reply to carry. Without it a client that threads on References alone (and the base
|
|
84
|
+
* subject is a guess, not a rule) sees a reply to a reply as a new conversation. Defaults to
|
|
85
|
+
* `[inReplyTo]`, which is what a first reply's chain is anyway.
|
|
86
|
+
*/
|
|
87
|
+
readonly references?: readonly string[]; /** When present the whole message becomes `multipart/mixed`: the body first, then each file. */
|
|
72
88
|
readonly attachments?: readonly MessageAttachment[];
|
|
89
|
+
/**
|
|
90
|
+
* Headers this client owns, appended after the standard block.
|
|
91
|
+
*
|
|
92
|
+
* YOZZ no longer uses this. It stamped `X-Yozz-Draft` here to find its own copies again without
|
|
93
|
+
* trusting a Message-ID a provider may rewrite, and that failed on a more basic point: a server
|
|
94
|
+
* need only index the headers IMAP names, so `SEARCH HEADER` on a private one can answer the
|
|
95
|
+
* empty list for a message that carries it (docs/knowledge/forwardemail-api.md). The field stays
|
|
96
|
+
* because a message builder should be able to set a header; do not build a LOOKUP on one.
|
|
97
|
+
*/
|
|
98
|
+
readonly extraHeaders?: readonly (readonly [string, string])[];
|
|
73
99
|
};
|
|
74
100
|
type MessageAttachment = {
|
|
75
101
|
readonly filename: string;
|
|
@@ -86,4 +112,4 @@ declare const formatMailbox: ({
|
|
|
86
112
|
declare const formatDate: (date: Date) => string;
|
|
87
113
|
declare const buildMessage: (input: MessageInput) => Uint8Array;
|
|
88
114
|
//#endregion
|
|
89
|
-
export { type MessageAttachment, type MessageInput, type SmtpCapabilities, type SmtpClient, type SmtpEnvelope, type SmtpFailure, type SmtpReply, type SmtpResult, buildMessage, createSmtpClient, dotStuff, encodeHeaderText, formatDate, formatMailbox };
|
|
115
|
+
export { type ByteDuplex, type MessageAttachment, type MessageInput, type SmtpCapabilities, type SmtpClient, type SmtpEnvelope, type SmtpFailure, type SmtpReply, type SmtpResult, buildMessage, createSmtpClient, dotStuff, encodeHeaderText, formatDate, formatMailbox };
|
package/dist/index.mjs
CHANGED
|
@@ -94,6 +94,11 @@ const readReply = async (reader) => {
|
|
|
94
94
|
};
|
|
95
95
|
//#endregion
|
|
96
96
|
//#region src/client.ts
|
|
97
|
+
/**
|
|
98
|
+
* SMTP client over a `ByteDuplex` (RFC 5321 + AUTH, RFC 4954). Implicit TLS is the transport's
|
|
99
|
+
* business; this never sees a certificate or a password store. One command in flight at a time,
|
|
100
|
+
* which is all SMTP allows without PIPELINING.
|
|
101
|
+
*/
|
|
97
102
|
const encoder$1 = new TextEncoder();
|
|
98
103
|
const base64$1 = (bytes) => {
|
|
99
104
|
let binary = "";
|
|
@@ -430,7 +435,16 @@ const buildMessage = (input) => {
|
|
|
430
435
|
const cc = input.cc ?? [];
|
|
431
436
|
if (cc.length > 0) headers.push(["Cc", cc.join(", ")]);
|
|
432
437
|
headers.push(["Subject", encodeHeaderText(input.subject)], ["Date", formatDate(input.date)], ["Message-ID", input.messageId], ["MIME-Version", "1.0"]);
|
|
433
|
-
if (input.inReplyTo !== void 0) headers.push(["In-Reply-To", input.inReplyTo]
|
|
438
|
+
if (input.inReplyTo !== void 0) headers.push(["In-Reply-To", input.inReplyTo]);
|
|
439
|
+
const references = input.references ?? (input.inReplyTo === void 0 ? [] : [input.inReplyTo]);
|
|
440
|
+
if (references.length > 0) {
|
|
441
|
+
for (const id of references) assertNoLineBreak("References", id);
|
|
442
|
+
headers.push(["References", references.join("\r\n ")]);
|
|
443
|
+
}
|
|
444
|
+
for (const [name, value] of input.extraHeaders ?? []) {
|
|
445
|
+
assertNoLineBreak(name, value);
|
|
446
|
+
headers.push([name, value]);
|
|
447
|
+
}
|
|
434
448
|
for (const { filename, mimeType } of input.attachments ?? []) {
|
|
435
449
|
assertNoLineBreak("filename", filename);
|
|
436
450
|
assertNoLineBreak("Content-Type", mimeType);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yozz.app/smtp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Transport-agnostic SMTP client core plus an RFC 5322 message builder.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/fishballapp/yozz",
|
|
@@ -28,13 +28,11 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@yozz.app/tls": "0.1.1"
|
|
33
|
-
},
|
|
34
31
|
"devDependencies": {
|
|
35
32
|
"@types/node": "^24.0.0",
|
|
36
33
|
"tsdown": "^0.22.3",
|
|
37
|
-
"@yozz.app/
|
|
34
|
+
"@yozz.app/tls": "0.1.3",
|
|
35
|
+
"@yozz.app/x509": "0.1.3"
|
|
38
36
|
},
|
|
39
37
|
"scripts": {
|
|
40
38
|
"build": "tsdown src/index.ts --format esm --dts",
|