@sdxc/mail 0.0.0-pre.1
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.md +21 -0
- package/README.md +544 -0
- package/dist/cloudflare.d.ts +82 -0
- package/dist/cloudflare.js +71 -0
- package/dist/components.d.ts +346 -0
- package/dist/components.js +293 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +24 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +22 -0
- package/dist/lib/address.d.ts +37 -0
- package/dist/lib/address.js +56 -0
- package/dist/lib/html-to-text.d.ts +19 -0
- package/dist/lib/html-to-text.js +149 -0
- package/dist/mailer.d.ts +82 -0
- package/dist/mailer.js +188 -0
- package/dist/markdown.d.ts +46 -0
- package/dist/markdown.js +182 -0
- package/dist/memory.d.ts +81 -0
- package/dist/memory.js +84 -0
- package/dist/middleware.d.ts +53 -0
- package/dist/middleware.js +50 -0
- package/dist/mime.d.ts +21 -0
- package/dist/mime.js +374 -0
- package/dist/render.d.ts +27 -0
- package/dist/render.js +33 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.js +8 -0
- package/package.json +30 -0
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an email body tree into the two parts a message carries. One authored
|
|
3
|
+
* tree produces both the HTML part and the plain-text alternative derived from
|
|
4
|
+
* it, so a text part is never a separate piece of copy that can drift.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import type { RemixElement } from "remix/ui";
|
|
10
|
+
/** The two body parts produced from a single email tree. */
|
|
11
|
+
export interface RenderedEmail {
|
|
12
|
+
/** Serialized HTML of the tree. */
|
|
13
|
+
html: string;
|
|
14
|
+
/** Plain-text alternative derived from that same HTML. */
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Renders an email body tree to both body parts.
|
|
19
|
+
*
|
|
20
|
+
* The text part is parsed from the rendered HTML, keeping link targets and
|
|
21
|
+
* structure in sync; set `text` on the message to override the heuristic.
|
|
22
|
+
*
|
|
23
|
+
* @param element - The body tree to render.
|
|
24
|
+
* @returns The HTML part and the plain-text alternative derived from it.
|
|
25
|
+
* @example let { html, text } = await render(<InviteBody url={url} />);
|
|
26
|
+
*/
|
|
27
|
+
export declare function render(element: RemixElement): Promise<RenderedEmail>;
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an email body tree into the two parts a message carries. One authored
|
|
3
|
+
* tree produces both the HTML part and the plain-text alternative derived from
|
|
4
|
+
* it, so a text part is never a separate piece of copy that can drift.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import { renderToString } from "remix/ui/server";
|
|
10
|
+
import { htmlToText } from "./lib/html-to-text.js";
|
|
11
|
+
/**
|
|
12
|
+
* The document type every mail client is calibrated against.
|
|
13
|
+
*
|
|
14
|
+
* XHTML 1.0 Transitional, because Outlook renders through Word, which needs
|
|
15
|
+
* this declaration to avoid a quirks mode that collapses table cell heights.
|
|
16
|
+
*/
|
|
17
|
+
const DOCTYPE = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
|
|
18
|
+
/**
|
|
19
|
+
* Renders an email body tree to both body parts.
|
|
20
|
+
*
|
|
21
|
+
* The text part is parsed from the rendered HTML, keeping link targets and
|
|
22
|
+
* structure in sync; set `text` on the message to override the heuristic.
|
|
23
|
+
*
|
|
24
|
+
* @param element - The body tree to render.
|
|
25
|
+
* @returns The HTML part and the plain-text alternative derived from it.
|
|
26
|
+
* @example let { html, text } = await render(<InviteBody url={url} />);
|
|
27
|
+
*/
|
|
28
|
+
export async function render(element) {
|
|
29
|
+
let html = await renderToString(element);
|
|
30
|
+
if (html.startsWith("<html"))
|
|
31
|
+
html = `${DOCTYPE}${html}`;
|
|
32
|
+
return { html, text: htmlToText(html) };
|
|
33
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery contracts shared by the mailer and every transport: the message a
|
|
3
|
+
* caller writes, the normalized message a transport receives, the authoring
|
|
4
|
+
* interface an email class implements, and the transport adapter itself.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import type { Result } from "@sdxc/result";
|
|
10
|
+
import type { RemixElement } from "remix/ui";
|
|
11
|
+
import type { MailError } from "./errors.js";
|
|
12
|
+
/** A single mailbox, optionally with the display name clients show instead of it. */
|
|
13
|
+
export interface Address {
|
|
14
|
+
/** Addr-spec of the mailbox, e.g. `no-reply@example.com`. */
|
|
15
|
+
email: string;
|
|
16
|
+
/** Display name shown by mail clients in place of the bare address. */
|
|
17
|
+
name?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A message as callers write it. Everything the mailer can supply a default for
|
|
21
|
+
* is optional, so a caller only states what is specific to this message.
|
|
22
|
+
*/
|
|
23
|
+
export interface Message {
|
|
24
|
+
/** Sender mailbox; omitted means the mailer's configured sender. */
|
|
25
|
+
from?: Address;
|
|
26
|
+
/** Primary recipients; at least one is required. */
|
|
27
|
+
to: Address | Address[];
|
|
28
|
+
/** Carbon-copy recipients. */
|
|
29
|
+
cc?: Address | Address[];
|
|
30
|
+
/** Blind carbon-copy recipients, kept hidden from the other recipients. */
|
|
31
|
+
bcc?: Address | Address[];
|
|
32
|
+
/** Where replies go; omitted means the mailer's configured reply-to. */
|
|
33
|
+
replyTo?: Address | Address[];
|
|
34
|
+
/** Subject line, already in the recipient's language. */
|
|
35
|
+
subject: string;
|
|
36
|
+
/** HTML body; when it is the only body part the mailer derives the text one. */
|
|
37
|
+
html?: string;
|
|
38
|
+
/** Plain-text body; supply it to override the mailer's derived version. */
|
|
39
|
+
text?: string;
|
|
40
|
+
/** Extra headers merged over the mailer's configured headers. */
|
|
41
|
+
headers?: Record<string, string>;
|
|
42
|
+
/** Explicit `Date` header value; omitted means "now". Set it to keep tests deterministic. */
|
|
43
|
+
date?: Date;
|
|
44
|
+
/** Explicit `Message-ID` value; omitted means generated. Set it to keep tests deterministic. */
|
|
45
|
+
messageId?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A message after normalization: defaults applied, every address list coerced to
|
|
49
|
+
* an array, and a plain-text part derived when only HTML was authored. Transports
|
|
50
|
+
* receive this shape fully resolved, with defaults applied and validation done.
|
|
51
|
+
*/
|
|
52
|
+
export interface NormalizedMessage {
|
|
53
|
+
/** Sender mailbox, resolved from the message or the mailer configuration. */
|
|
54
|
+
from: Address;
|
|
55
|
+
/** Primary recipients, guaranteed to hold at least one address. */
|
|
56
|
+
to: Address[];
|
|
57
|
+
/** Carbon-copy recipients; empty when none were given. */
|
|
58
|
+
cc: Address[];
|
|
59
|
+
/** Blind carbon-copy recipients; empty when none were given. */
|
|
60
|
+
bcc: Address[];
|
|
61
|
+
/** Reply-to mailboxes; empty when neither the message nor the mailer set one. */
|
|
62
|
+
replyTo: Address[];
|
|
63
|
+
/** Subject line, already in the recipient's language. */
|
|
64
|
+
subject: string;
|
|
65
|
+
/** HTML body; at least one of `html` and `text` is a non-empty string. */
|
|
66
|
+
html?: string;
|
|
67
|
+
/** Plain-text body; derived from `html` unless the caller supplied one. */
|
|
68
|
+
text?: string;
|
|
69
|
+
/** Headers to add, with per-message values winning over configured ones. */
|
|
70
|
+
headers: Record<string, string>;
|
|
71
|
+
/** Value for the `Date` header, defaulted to the moment of normalization. */
|
|
72
|
+
date: Date;
|
|
73
|
+
/** Value for the `Message-ID` header, generated when the caller omitted it. */
|
|
74
|
+
messageId: string;
|
|
75
|
+
/**
|
|
76
|
+
* The email object this message was produced from, when it came from one.
|
|
77
|
+
* It exists so tests can identify a sent message by its type.
|
|
78
|
+
*/
|
|
79
|
+
email?: Email;
|
|
80
|
+
}
|
|
81
|
+
/** Identifier a provider assigns to an accepted message. */
|
|
82
|
+
export interface SentMessage {
|
|
83
|
+
/** Provider identifier when it returns one, otherwise the message's `Message-ID`. */
|
|
84
|
+
messageId: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Adapter that puts a normalized message on the wire for one provider. Delivery
|
|
88
|
+
* outcome is a value: a transport reports provider rejections through the
|
|
89
|
+
* returned `Result`.
|
|
90
|
+
*/
|
|
91
|
+
export interface Transport {
|
|
92
|
+
/**
|
|
93
|
+
* Delivers an already normalized message.
|
|
94
|
+
*
|
|
95
|
+
* @param message - The message to deliver, with defaults applied.
|
|
96
|
+
* @returns The provider's identifier on success, a `MailError` on rejection.
|
|
97
|
+
*/
|
|
98
|
+
send(message: NormalizedMessage): Promise<Result<SentMessage, MailError>>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* An email authored as a class: who it goes to, what it says, and what it looks
|
|
102
|
+
* like. Sender identity lives on the mailer, and a subject arrives already
|
|
103
|
+
* translated from the caller's locale handling.
|
|
104
|
+
*/
|
|
105
|
+
export interface Email {
|
|
106
|
+
/** Recipient this email is addressed to, derived from the data it was constructed with. */
|
|
107
|
+
readonly to: Address | Address[];
|
|
108
|
+
/** Subject line, already translated. */
|
|
109
|
+
readonly subject: string;
|
|
110
|
+
/** Body tree, rendered by the mailer into HTML and plain text. */
|
|
111
|
+
body(): RemixElement;
|
|
112
|
+
/** Per-email reply-to override; the mailer's configuration supplies the default. */
|
|
113
|
+
readonly replyTo?: Address | Address[];
|
|
114
|
+
/** Per-email headers, merged over the mailer's configured headers. */
|
|
115
|
+
readonly headers?: Record<string, string>;
|
|
116
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery contracts shared by the mailer and every transport: the message a
|
|
3
|
+
* caller writes, the normalized message a transport receives, the authoring
|
|
4
|
+
* interface an email class implements, and the transport adapter itself.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sdxc/mail",
|
|
3
|
+
"version": "0.0.0-pre.1",
|
|
4
|
+
"description": "Transport-agnostic transactional email with pluggable transports and remix/ui rendering",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./markdown": "./dist/markdown.js",
|
|
10
|
+
"./memory": "./dist/memory.js",
|
|
11
|
+
"./middleware": "./dist/middleware.js",
|
|
12
|
+
"./cloudflare": "./dist/cloudflare.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@sdxc/highlight": "0.0.0-pre.1",
|
|
16
|
+
"@sdxc/logger": "2026.9.11",
|
|
17
|
+
"@sdxc/markdown": "0.0.0-pre.1",
|
|
18
|
+
"@sdxc/result": "2026.9.11",
|
|
19
|
+
"remix": "3.0.0-rc.2"
|
|
20
|
+
},
|
|
21
|
+
"gitHead": "e96a064f9208eb667923f57374e2cdaac73c72fb",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
28
|
+
"directory": "packages/mail"
|
|
29
|
+
}
|
|
30
|
+
}
|