@schemavaults/send-email 0.3.0 → 0.4.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/README.md +94 -1
- package/bun.lock +4 -4
- package/dist/cli.js +14377 -3777
- package/dist/cli.js.map +1 -1
- package/dist/create-email-attachment-from-file.d.ts +9 -0
- package/dist/create-email-attachment-from-file.js +27 -0
- package/dist/create-email-attachment-from-file.js.map +1 -0
- package/dist/create-email-attachment.d.ts +43 -0
- package/dist/create-email-attachment.js +58 -0
- package/dist/create-email-attachment.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/send-email-request-body-schema.d.ts +38 -113
- package/dist/send-email-request-body-schema.js +9 -0
- package/dist/send-email-request-body-schema.js.map +1 -1
- package/dist/send-email.d.ts +9 -0
- package/dist/send-email.js +8 -1
- package/dist/send-email.js.map +1 -1
- package/dist/validators/email-attachment-schema.d.ts +64 -0
- package/dist/validators/email-attachment-schema.js +120 -0
- package/dist/validators/email-attachment-schema.js.map +1 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @schemavaults/send-email
|
|
2
2
|
|
|
3
|
-
TypeScript client for sending emails via the `@schemavaults/mail-server` API. Provides request body validation (via Zod), environment-aware server URL resolution, and helpers for sending to individual recipients, mailing lists, and listing available email templates.
|
|
3
|
+
TypeScript client for sending emails via the `@schemavaults/mail-server` API. Provides request body validation (via Zod), environment-aware server URL resolution, and helpers for sending to individual recipients, mailing lists, attaching files, and listing available email templates.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -81,6 +81,59 @@ bunx schemavaults-send-email --transport smtp-primary send \
|
|
|
81
81
|
|
|
82
82
|
Transport names are defined by whoever configures the mail-server; this package only validates that the value looks like an identifier. An unknown transport name is rejected by the mail-server, not by the client.
|
|
83
83
|
|
|
84
|
+
### Attach files
|
|
85
|
+
|
|
86
|
+
Request bodies are JSON, so each attachment's bytes travel as a base64 string under `body.attachments`. You rarely need to encode anything yourself: the `attachments` option on `sendEmail()` (and `sendEmailToMailingList()`) accepts raw bytes, plain text, or already-encoded attachments and base64-encodes them for you before appending them to `body.attachments`.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { sendEmail } from "@schemavaults/send-email";
|
|
90
|
+
// Node/Bun only -- reads from disk, so it lives on its own subpath
|
|
91
|
+
import { createEmailAttachmentFromFile } from "@schemavaults/send-email/create-email-attachment-from-file";
|
|
92
|
+
|
|
93
|
+
await sendEmail({
|
|
94
|
+
body: {
|
|
95
|
+
to: "user@example.com",
|
|
96
|
+
subject: "Your invoice",
|
|
97
|
+
message: { text: "Invoice attached.", html: "<p>Invoice attached.</p>" },
|
|
98
|
+
},
|
|
99
|
+
attachments: [
|
|
100
|
+
// Binary: any Uint8Array / Buffer / ArrayBuffer
|
|
101
|
+
{ filename: "invoice.pdf", content: pdfBytes, contentType: "application/pdf" },
|
|
102
|
+
// Plain text: say so, otherwise a string is assumed to be base64 already
|
|
103
|
+
{ filename: "summary.txt", content: "Thanks for your order!", encoding: "utf8" },
|
|
104
|
+
// A file on disk; the basename becomes the attachment filename
|
|
105
|
+
await createEmailAttachmentFromFile("./reports/q3.csv"),
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To build the wire shape yourself -- for example to validate it up front or to put it in `body.attachments` directly -- use `createEmailAttachment()`, which takes the same input and returns a validated `EmailAttachment`:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { createEmailAttachment } from "@schemavaults/send-email";
|
|
114
|
+
|
|
115
|
+
const attachment = createEmailAttachment({
|
|
116
|
+
filename: "logo.png",
|
|
117
|
+
content: logoBytes,
|
|
118
|
+
contentType: "image/png",
|
|
119
|
+
contentId: "logo", // inline image: reference it as <img src="cid:logo"> in the HTML body
|
|
120
|
+
});
|
|
121
|
+
// => { filename: "logo.png", content: "iVBORw0KGgo...", contentType: "image/png", contentId: "logo" }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Setting `contentId` marks the attachment as inline so the HTML body can embed it via a `cid:` URL; pass the bare identifier without angle brackets. `contentType` is optional -- when omitted, the mail-server's transport derives it from the filename's extension.
|
|
125
|
+
|
|
126
|
+
Limits enforced client-side (and exported as `MAX_ATTACHMENTS_PER_EMAIL` / `MAX_TOTAL_ATTACHMENT_BYTES`): at most 20 attachments per email, totalling at most 25 MiB once decoded. Filenames may not contain path separators or control characters. The mail-server, its hosting platform, and the receiving mail systems may enforce lower limits of their own.
|
|
127
|
+
|
|
128
|
+
From the CLI, `--attach <path>` is repeatable on both `send` and `send-to-mailing-list`, and may be combined with `--body-file` (the files are appended to any `attachments` already in the JSON):
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
bunx schemavaults-send-email send \
|
|
132
|
+
--to user@example.com --subject "Your invoice" \
|
|
133
|
+
--text "Invoice attached." --html "<p>Invoice attached.</p>" \
|
|
134
|
+
--attach ./invoice.pdf --attach ./reports/q3.csv
|
|
135
|
+
```
|
|
136
|
+
|
|
84
137
|
### Send to a mailing list
|
|
85
138
|
|
|
86
139
|
```ts
|
|
@@ -141,6 +194,7 @@ interface ISendEmailOpts {
|
|
|
141
194
|
environment?: "production" | "development" | "staging";
|
|
142
195
|
dryRun?: boolean; // convenience; sets body.dryRun
|
|
143
196
|
transport?: string; // convenience; sets body.transport
|
|
197
|
+
attachments?: EmailAttachmentInput[]; // convenience; encoded + appended to body.attachments
|
|
144
198
|
}
|
|
145
199
|
```
|
|
146
200
|
|
|
@@ -157,9 +211,30 @@ interface ISendEmailToMailingListOpts {
|
|
|
157
211
|
environment?: "production" | "development" | "staging";
|
|
158
212
|
dryRun?: boolean; // convenience; sets body.dryRun
|
|
159
213
|
transport?: string; // convenience; sets body.transport
|
|
214
|
+
attachments?: EmailAttachmentInput[]; // convenience; encoded + appended to body.attachments
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `createEmailAttachment(input)`
|
|
219
|
+
|
|
220
|
+
Builds a validated `EmailAttachment` (the wire shape used in `body.attachments`), base64-encoding the content for you. Throws a `TypeError` describing the problem when the result is invalid.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
interface EmailAttachmentInput {
|
|
224
|
+
filename: string; // shown to the recipient; no path separators
|
|
225
|
+
content: string | Uint8Array | ArrayBuffer; // bytes, or a string (see `encoding`)
|
|
226
|
+
encoding?: "base64" | "utf8"; // how to read a string `content`; default "base64"
|
|
227
|
+
contentType?: string; // MIME type; derived from the filename when omitted
|
|
228
|
+
contentId?: string; // set to embed inline via cid: in the HTML body
|
|
160
229
|
}
|
|
161
230
|
```
|
|
162
231
|
|
|
232
|
+
An `EmailAttachment` is itself valid `EmailAttachmentInput` (its `content` is base64), so pre-built attachments and raw inputs can be mixed freely.
|
|
233
|
+
|
|
234
|
+
### `createEmailAttachmentFromFile(path, opts?)`
|
|
235
|
+
|
|
236
|
+
Node/Bun only. Reads a file from disk and returns a validated `EmailAttachment`; the file's basename is the attachment filename unless `opts.filename` overrides it. `opts` may also set `contentType` and `contentId`. Imported from the `@schemavaults/send-email/create-email-attachment-from-file` subpath so the package's main entry stays free of filesystem imports.
|
|
237
|
+
|
|
163
238
|
### `listEmailTemplates(opts?)`
|
|
164
239
|
|
|
165
240
|
Returns the list of registered email templates from the mail-server catalog.
|
|
@@ -197,6 +272,10 @@ Zod schema for validating template IDs: lowercase alphanumeric with hyphens/unde
|
|
|
197
272
|
|
|
198
273
|
Zod schema for validating transport names: lowercase alphanumeric with hyphens/underscores, 1-64 characters, must start with a lowercase letter and end with a lowercase alphanumeric character. Whether a well-formed name corresponds to a transport the mail-server actually has configured is decided server-side.
|
|
199
274
|
|
|
275
|
+
### `emailAttachmentSchema` / `emailAttachmentsSchema`
|
|
276
|
+
|
|
277
|
+
Zod schemas for a single attachment and for the `attachments` array respectively. The array schema enforces the count (`MAX_ATTACHMENTS_PER_EMAIL`, 20) and total decoded size (`MAX_TOTAL_ATTACHMENT_BYTES`, 25 MiB) limits; both constants are exported.
|
|
278
|
+
|
|
200
279
|
## Request body shape
|
|
201
280
|
|
|
202
281
|
The `message` field accepts either a template reference or raw content:
|
|
@@ -206,6 +285,13 @@ type Message =
|
|
|
206
285
|
| { template_id: string; template_props?: unknown }
|
|
207
286
|
| { text: string; html: string }; // both required
|
|
208
287
|
|
|
288
|
+
type EmailAttachment = {
|
|
289
|
+
filename: string; // 1-255 chars; no path separators or control characters
|
|
290
|
+
content: string; // base64-encoded bytes (standard alphabet, padded)
|
|
291
|
+
contentType?: string; // MIME type; derived from the filename when omitted
|
|
292
|
+
contentId?: string; // marks the attachment inline; reference as cid:<contentId>
|
|
293
|
+
};
|
|
294
|
+
|
|
209
295
|
type SendEmailRequestBody = {
|
|
210
296
|
to: string | string[]; // email address(es) or mailing list UUID
|
|
211
297
|
subject: string;
|
|
@@ -216,6 +302,7 @@ type SendEmailRequestBody = {
|
|
|
216
302
|
bcc?: string | string[];
|
|
217
303
|
dryRun?: boolean; // server validates without dispatching
|
|
218
304
|
transport?: string; // which mail-server transport delivers this email
|
|
305
|
+
attachments?: EmailAttachment[]; // 1-20 files, at most 25 MiB in total once decoded
|
|
219
306
|
};
|
|
220
307
|
```
|
|
221
308
|
|
|
@@ -228,3 +315,9 @@ bun test
|
|
|
228
315
|
bun run typecheck
|
|
229
316
|
bun run lint
|
|
230
317
|
```
|
|
318
|
+
|
|
319
|
+
### Keeping the package root browser-safe
|
|
320
|
+
|
|
321
|
+
Consumers bundle the package root for the browser (the mail-server's admin UI imports `sendEmail` from a client component), so nothing reachable from `src/index.ts` may need a Node/Bun runtime. `src/browser-compatibility.test.ts` enforces this on every `bun test`: it bundles the root with esbuild for the browser platform, asserts no Node built-in is imported anywhere in the graph, and evaluates the bundle in a `node:vm` context with no `process` / `Buffer` / `require` before exercising the public API.
|
|
322
|
+
|
|
323
|
+
Anything that does need Node or Bun -- filesystem access, for example -- goes in its own module exposed through a package subpath (as `create-email-attachment-from-file` is) and is added to that test's `NODE_ONLY_MODULES` list, which both checks the root never imports it and confirms it really is not browser-safe.
|
package/bun.lock
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "@schemavaults/send-email",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"commander": "
|
|
9
|
-
"zod": "
|
|
8
|
+
"commander": "14.0.1",
|
|
9
|
+
"zod": "4.4.3",
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@eslint/js": "9.39.1",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
|
|
169
169
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
|
170
170
|
|
|
171
|
-
"commander": ["commander@14.0.
|
|
171
|
+
"commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="],
|
|
172
172
|
|
|
173
173
|
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
|
174
174
|
|
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
|
|
353
353
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
|
354
354
|
|
|
355
|
-
"zod": ["zod@
|
|
355
|
+
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
|
356
356
|
|
|
357
357
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
|
358
358
|
|