sently 0.2.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/LICENSE +21 -0
- package/README.md +367 -0
- package/dist/chunk-794hc3m4.js +105 -0
- package/dist/chunk-794hc3m4.js.map +10 -0
- package/dist/chunk-hdqpvsm8.js +116 -0
- package/dist/chunk-hdqpvsm8.js.map +11 -0
- package/dist/chunk-tch6s785.js +943 -0
- package/dist/chunk-tch6s785.js.map +14 -0
- package/dist/chunk-v0bahtg2.js +6 -0
- package/dist/chunk-v0bahtg2.js.map +9 -0
- package/dist/chunk-vm70w4e3.js +68 -0
- package/dist/chunk-vm70w4e3.js.map +10 -0
- package/dist/src/adapters/bun.js +175 -0
- package/dist/src/adapters/bun.js.map +10 -0
- package/dist/src/adapters/cf.js +78 -0
- package/dist/src/adapters/cf.js.map +10 -0
- package/dist/src/adapters/deno.js +73 -0
- package/dist/src/adapters/deno.js.map +10 -0
- package/dist/src/adapters/node.js +172 -0
- package/dist/src/adapters/node.js.map +10 -0
- package/dist/src/auth/oauth2.js +14 -0
- package/dist/src/auth/oauth2.js.map +9 -0
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +9 -0
- package/dist/src/pool/pool.js +263 -0
- package/dist/src/pool/pool.js.map +11 -0
- package/dist/src/transports/postmark.js +85 -0
- package/dist/src/transports/postmark.js.map +10 -0
- package/dist/src/transports/resend.js +86 -0
- package/dist/src/transports/resend.js.map +10 -0
- package/dist/src/transports/sendgrid.js +104 -0
- package/dist/src/transports/sendgrid.js.map +10 -0
- package/dist/src/transports/smtp.js +24 -0
- package/dist/src/transports/smtp.js.map +9 -0
- package/package.json +105 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sendx contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# sently
|
|
2
|
+
|
|
3
|
+
**Runtime-agnostic email library for Node.js, Bun, Deno, and Cloudflare Workers.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/sently)
|
|
6
|
+
[](https://jsr.io/@sently/sently)
|
|
7
|
+
[](https://bundlephobia.com/package/sently)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](#)
|
|
10
|
+
[](https://github.com/alialnaghmoush/sently)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why sently
|
|
15
|
+
|
|
16
|
+
- **Works everywhere** — Node.js, Bun, Deno, Cloudflare Workers, and any environment with Web APIs
|
|
17
|
+
- **True tree-shaking** — import only what you need; unused adapters and transports stay out of your bundle
|
|
18
|
+
- **Zero dependencies in core** — MIME, SMTP protocol, and encoding use pure Web APIs only
|
|
19
|
+
- **DKIM signing** — RSA-SHA256 and Ed25519-SHA256 via Web Crypto
|
|
20
|
+
- **OAuth2 / XOAUTH2** — Gmail and Microsoft 365 SMTP auth with automatic token refresh
|
|
21
|
+
- **Connection pooling** — reuse SMTP sessions with optional rate limiting
|
|
22
|
+
- **TypeScript-first** — strict types, subpath exports, and full IDE support
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
**npm** ([sently](https://www.npmjs.com/package/sently)):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bun add sently
|
|
32
|
+
npm install sently
|
|
33
|
+
pnpm add sently
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**JSR** ([@sently/sently](https://jsr.io/@sently/sently)) — Deno, Bun, and other JSR-aware runtimes:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
deno add jsr:@sently/sently
|
|
40
|
+
bunx jsr add @sently/sently
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createMailer } from "sently";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### SMTP with auto-detected adapter
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { createMailer } from "sently";
|
|
55
|
+
|
|
56
|
+
const mailer = await createMailer({
|
|
57
|
+
host: "smtp.example.com",
|
|
58
|
+
port: 587,
|
|
59
|
+
auth: { user: "you@example.com", pass: "secret" },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await mailer.send({
|
|
63
|
+
from: "you@example.com",
|
|
64
|
+
to: "recipient@example.com",
|
|
65
|
+
subject: "Hello from sently",
|
|
66
|
+
text: "Plain text body",
|
|
67
|
+
html: "<p>HTML body</p>",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await mailer.close();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Resend HTTP transport (Vercel Edge compatible)
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { createMailer } from "sently";
|
|
77
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
78
|
+
|
|
79
|
+
const mailer = await createMailer({
|
|
80
|
+
transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await mailer.send({
|
|
84
|
+
from: "onboarding@yourdomain.com",
|
|
85
|
+
to: "recipient@example.com",
|
|
86
|
+
subject: "Hello from the edge",
|
|
87
|
+
html: "<p>Sent via Resend + sently</p>",
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Cloudflare Worker
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { createMailer } from "sently";
|
|
95
|
+
import { CloudflareAdapter } from "sently/adapters/cf";
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
async fetch() {
|
|
99
|
+
const mailer = await createMailer({
|
|
100
|
+
host: "smtp.example.com",
|
|
101
|
+
port: 587,
|
|
102
|
+
auth: { user: "relay@example.com", pass: "secret" },
|
|
103
|
+
adapter: new CloudflareAdapter(),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await mailer.send({
|
|
107
|
+
from: "relay@example.com",
|
|
108
|
+
to: "user@example.com",
|
|
109
|
+
subject: "From a Worker",
|
|
110
|
+
text: "Hello from Cloudflare Workers",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return new Response("Sent");
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Adapters
|
|
121
|
+
|
|
122
|
+
| Runtime | Import | Notes |
|
|
123
|
+
|---------|--------|-------|
|
|
124
|
+
| Node.js (auto) | `createMailer(config)` | Auto-detected |
|
|
125
|
+
| Node.js (explicit) | `sently/adapters/node` → `NodeAdapter` | Reference implementation |
|
|
126
|
+
| Bun (auto) | `createMailer(config)` | Auto-detected |
|
|
127
|
+
| Bun (explicit) | `sently/adapters/bun` → `BunAdapter` | Node compat layer |
|
|
128
|
+
| Deno | `sently/adapters/deno` → `DenoAdapter` | Native `Deno.startTls` |
|
|
129
|
+
| Cloudflare Workers | `sently/adapters/cf` → `CloudflareAdapter` | `cloudflare:sockets` |
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { NodeAdapter } from "sently/adapters/node";
|
|
133
|
+
|
|
134
|
+
const mailer = await createMailer({
|
|
135
|
+
host: "smtp.example.com",
|
|
136
|
+
adapter: new NodeAdapter({ secure: false }),
|
|
137
|
+
auth: { user: "you@example.com", pass: "secret" },
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Transports
|
|
144
|
+
|
|
145
|
+
### SMTP
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { createMailer } from "sently";
|
|
149
|
+
import { SMTPTransport } from "sently/transports/smtp";
|
|
150
|
+
import { NodeAdapter } from "sently/adapters/node";
|
|
151
|
+
|
|
152
|
+
const transport = new SMTPTransport({
|
|
153
|
+
host: "smtp.example.com",
|
|
154
|
+
port: 587,
|
|
155
|
+
auth: { user: "you@example.com", pass: "secret" },
|
|
156
|
+
adapter: new NodeAdapter(),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const mailer = await createMailer({ transport });
|
|
160
|
+
await mailer.verify(); // test connection + auth
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**AUTH methods:** XOAUTH2, CRAM-MD5, LOGIN, and PLAIN (auto-negotiated from EHLO unless `auth.type` is set).
|
|
164
|
+
|
|
165
|
+
#### DKIM signing
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const mailer = await createMailer({
|
|
169
|
+
host: "smtp.example.com",
|
|
170
|
+
auth: { user: "you@example.com", pass: "secret" },
|
|
171
|
+
dkim: {
|
|
172
|
+
domainName: "example.com",
|
|
173
|
+
keySelector: "2024",
|
|
174
|
+
privateKey: await Bun.file("dkim-private.pem").text(),
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Gmail OAuth2 (XOAUTH2)
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { OAuth2Client } from "sently/auth/oauth2";
|
|
183
|
+
|
|
184
|
+
const mailer = await createMailer({
|
|
185
|
+
host: "smtp.gmail.com",
|
|
186
|
+
port: 465,
|
|
187
|
+
secure: true,
|
|
188
|
+
auth: {
|
|
189
|
+
type: "OAUTH2",
|
|
190
|
+
user: "me@gmail.com",
|
|
191
|
+
oauth2: {
|
|
192
|
+
user: "me@gmail.com",
|
|
193
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
194
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
195
|
+
refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Connection pooling
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const mailer = await createMailer({
|
|
205
|
+
host: "smtp.example.com",
|
|
206
|
+
pool: true,
|
|
207
|
+
maxConnections: 5,
|
|
208
|
+
maxMessages: 100,
|
|
209
|
+
rateDelta: 10,
|
|
210
|
+
rateLimit: 1000,
|
|
211
|
+
auth: { user: "you@example.com", pass: "secret" },
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Or use `SMTPPool` directly:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { SMTPPool } from "sently/pool";
|
|
219
|
+
|
|
220
|
+
const pool = new SMTPPool({
|
|
221
|
+
host: "smtp.example.com",
|
|
222
|
+
adapter: new NodeAdapter(),
|
|
223
|
+
auth: { user: "you@example.com", pass: "secret" },
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### HTTP APIs
|
|
228
|
+
|
|
229
|
+
#### Resend
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
233
|
+
|
|
234
|
+
const transport = new ResendTransport({ apiKey: "re_..." });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### SendGrid
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { SendGridTransport } from "sently/transports/sendgrid";
|
|
241
|
+
|
|
242
|
+
const transport = new SendGridTransport({ apiKey: "SG...." });
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Postmark
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { PostmarkTransport } from "sently/transports/postmark";
|
|
249
|
+
|
|
250
|
+
const transport = new PostmarkTransport({ serverToken: "..." });
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## MailOptions Reference
|
|
256
|
+
|
|
257
|
+
| Field | Type | Default | Description |
|
|
258
|
+
|-------|------|---------|-------------|
|
|
259
|
+
| `from` | `AddressInput` | *required* | Sender address |
|
|
260
|
+
| `to` | `AddressInput` | *required* | Recipients |
|
|
261
|
+
| `cc` | `AddressInput` | — | CC recipients (visible in headers) |
|
|
262
|
+
| `bcc` | `AddressInput` | — | BCC recipients (envelope only, not in headers) |
|
|
263
|
+
| `replyTo` | `AddressInput` | — | Reply-To header |
|
|
264
|
+
| `subject` | `string` | *required* | Email subject (RFC 2047 for non-ASCII) |
|
|
265
|
+
| `text` | `string` | — | Plain text body |
|
|
266
|
+
| `html` | `string` | — | HTML body |
|
|
267
|
+
| `attachments` | `Attachment[]` | — | File attachments |
|
|
268
|
+
| `headers` | `Record<string, string>` | — | Custom headers |
|
|
269
|
+
| `messageId` | `string` | auto | Message-ID header |
|
|
270
|
+
| `date` | `Date` | now | Date header |
|
|
271
|
+
| `priority` | `'high' \| 'normal' \| 'low'` | — | X-Priority / Importance |
|
|
272
|
+
| `encoding` | `'utf-8' \| 'ascii'` | `'utf-8'` | Character encoding hint |
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Attachments
|
|
277
|
+
|
|
278
|
+
### In-memory (all runtimes)
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
await mailer.send({
|
|
282
|
+
from: "you@example.com",
|
|
283
|
+
to: "user@example.com",
|
|
284
|
+
subject: "With attachment",
|
|
285
|
+
text: "See attached",
|
|
286
|
+
attachments: [
|
|
287
|
+
{
|
|
288
|
+
filename: "report.pdf",
|
|
289
|
+
content: pdfBytes, // Uint8Array
|
|
290
|
+
contentType: "application/pdf",
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### File path (Node.js / Bun / Deno only)
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
attachments: [
|
|
300
|
+
{
|
|
301
|
+
filename: "report.pdf",
|
|
302
|
+
path: "/path/to/report.pdf",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
On Cloudflare Workers and browsers, use `content: Uint8Array` — `attachment.path` is not supported.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Tree-Shaking
|
|
312
|
+
|
|
313
|
+
Each import path is a separate build entry point:
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
import { createMailer } from "sently"
|
|
317
|
+
+ import { ResendTransport } from "sently/transports/resend"
|
|
318
|
+
→ Bundle: core/mime (~8KB) + core/address (~2KB) + transports/resend (~2KB) ≈ ~12KB gzip
|
|
319
|
+
|
|
320
|
+
vs. full Nodemailer: ~220KB
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Only code you import is bundled. Adapters and transports you never import are never included.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Migrating from Nodemailer
|
|
328
|
+
|
|
329
|
+
| Nodemailer | sently |
|
|
330
|
+
|------------|-------|
|
|
331
|
+
| `nodemailer.createTransport({...})` | `await createMailer({...})` |
|
|
332
|
+
| `transporter.sendMail(options)` | `mailer.send(options)` |
|
|
333
|
+
| `transporter.verify()` | `mailer.verify()` |
|
|
334
|
+
| `options.attachments[].path` | Same (Node/Bun/Deno); use `content` on edge |
|
|
335
|
+
| `import nodemailer from 'nodemailer'` | `import { createMailer } from 'sently'` |
|
|
336
|
+
| CommonJS | ESM only |
|
|
337
|
+
| Node.js only | Node, Bun, Deno, CF Workers |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Bundle Size
|
|
342
|
+
|
|
343
|
+
Approximate gzip sizes per subpath export:
|
|
344
|
+
|
|
345
|
+
| Export | ~gzip |
|
|
346
|
+
|--------|-------|
|
|
347
|
+
| `sently` | ~6 KB |
|
|
348
|
+
| `sently/transports/smtp` | ~10 KB |
|
|
349
|
+
| `sently/transports/resend` | ~2 KB |
|
|
350
|
+
| `sently/transports/sendgrid` | ~2 KB |
|
|
351
|
+
| `sently/transports/postmark` | ~2 KB |
|
|
352
|
+
| `sently/adapters/node` | ~3 KB |
|
|
353
|
+
| `sently/adapters/bun` | ~3 KB |
|
|
354
|
+
| `sently/adapters/deno` | ~2 KB |
|
|
355
|
+
| `sently/adapters/cf` | ~2 KB |
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Links
|
|
360
|
+
|
|
361
|
+
- **Source & issues:** [github.com/alialnaghmoush/sently](https://github.com/alialnaghmoush/sently)
|
|
362
|
+
- **npm:** [npmjs.com/package/sently](https://www.npmjs.com/package/sently)
|
|
363
|
+
- **JSR:** [jsr.io/@sently/sently](https://jsr.io/@sently/sently)
|
|
364
|
+
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
MIT
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// src/core/base64.ts
|
|
2
|
+
var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
+
var BASE64_LINE_LENGTH = 76;
|
|
4
|
+
var encoder = new TextEncoder;
|
|
5
|
+
var decoder = new TextDecoder;
|
|
6
|
+
function encodeBase64(data) {
|
|
7
|
+
const bytes = typeof data === "string" ? encoder.encode(data) : data;
|
|
8
|
+
let result = "";
|
|
9
|
+
let i = 0;
|
|
10
|
+
while (i < bytes.length) {
|
|
11
|
+
const b0 = bytes[i] ?? 0;
|
|
12
|
+
const b1 = bytes[i + 1];
|
|
13
|
+
const b2 = bytes[i + 2];
|
|
14
|
+
if (b1 === undefined) {
|
|
15
|
+
result += BASE64_CHARS[b0 >> 2];
|
|
16
|
+
result += BASE64_CHARS[(b0 & 3) << 4];
|
|
17
|
+
result += "==";
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
if (b2 === undefined) {
|
|
21
|
+
result += BASE64_CHARS[b0 >> 2];
|
|
22
|
+
result += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
23
|
+
result += BASE64_CHARS[(b1 & 15) << 2];
|
|
24
|
+
result += "=";
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
result += BASE64_CHARS[b0 >> 2];
|
|
28
|
+
result += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
29
|
+
result += BASE64_CHARS[(b1 & 15) << 2 | b2 >> 6];
|
|
30
|
+
result += BASE64_CHARS[b2 & 63];
|
|
31
|
+
i += 3;
|
|
32
|
+
}
|
|
33
|
+
return wrapBase64Lines(result);
|
|
34
|
+
}
|
|
35
|
+
function decodeBase64(data) {
|
|
36
|
+
const cleaned = data.replace(/\s/g, "");
|
|
37
|
+
const len = cleaned.length;
|
|
38
|
+
if (len === 0) {
|
|
39
|
+
return new Uint8Array(0);
|
|
40
|
+
}
|
|
41
|
+
if (len % 4 !== 0) {
|
|
42
|
+
throw new Error("Invalid base64 string length");
|
|
43
|
+
}
|
|
44
|
+
const padding = cleaned.endsWith("==") ? 2 : cleaned.endsWith("=") ? 1 : 0;
|
|
45
|
+
const outputLen = len * 3 / 4 - padding;
|
|
46
|
+
const output = new Uint8Array(outputLen);
|
|
47
|
+
let outIndex = 0;
|
|
48
|
+
for (let i = 0;i < len; i += 4) {
|
|
49
|
+
const c0 = base64CharToValue(cleaned[i] ?? "=");
|
|
50
|
+
const c1 = base64CharToValue(cleaned[i + 1] ?? "=");
|
|
51
|
+
const c2 = base64CharToValue(cleaned[i + 2] ?? "=");
|
|
52
|
+
const c3 = base64CharToValue(cleaned[i + 3] ?? "=");
|
|
53
|
+
const triple = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
54
|
+
if (outIndex < outputLen) {
|
|
55
|
+
output[outIndex++] = triple >> 16 & 255;
|
|
56
|
+
}
|
|
57
|
+
if (outIndex < outputLen) {
|
|
58
|
+
output[outIndex++] = triple >> 8 & 255;
|
|
59
|
+
}
|
|
60
|
+
if (outIndex < outputLen) {
|
|
61
|
+
output[outIndex++] = triple & 255;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
function encodeHeader(value) {
|
|
67
|
+
if (!needsEncoding(value)) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
const encoded = encodeBase64(value).replace(/\r\n/g, "");
|
|
71
|
+
return `=?UTF-8?B?${encoded}?=`;
|
|
72
|
+
}
|
|
73
|
+
function needsEncoding(text) {
|
|
74
|
+
for (let i = 0;i < text.length; i++) {
|
|
75
|
+
if (text.charCodeAt(i) > 127) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function base64CharToValue(char) {
|
|
82
|
+
if (char === "=") {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
const index = BASE64_CHARS.indexOf(char);
|
|
86
|
+
if (index === -1) {
|
|
87
|
+
throw new Error(`Invalid base64 character: ${char}`);
|
|
88
|
+
}
|
|
89
|
+
return index;
|
|
90
|
+
}
|
|
91
|
+
function wrapBase64Lines(base64) {
|
|
92
|
+
const lines = [];
|
|
93
|
+
for (let i = 0;i < base64.length; i += BASE64_LINE_LENGTH) {
|
|
94
|
+
lines.push(base64.slice(i, i + BASE64_LINE_LENGTH));
|
|
95
|
+
}
|
|
96
|
+
return lines.join(`\r
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
function encodeUtf8(text) {
|
|
100
|
+
return encoder.encode(text);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { encodeBase64, decodeBase64, encodeHeader, encodeUtf8 };
|
|
104
|
+
|
|
105
|
+
//# debugId=93C9FABDDF825C4864756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/base64.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// src/core/base64.ts\n\nconst BASE64_CHARS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\nconst BASE64_LINE_LENGTH = 76;\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Encode a Uint8Array or string to Base64.\n * Uses TextEncoder + manual base64 to support binary data correctly.\n */\nexport function encodeBase64(data: Uint8Array | string): string {\n const bytes = typeof data === \"string\" ? encoder.encode(data) : data;\n let result = \"\";\n let i = 0;\n\n while (i < bytes.length) {\n const b0 = bytes[i] ?? 0;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n\n if (b1 === undefined) {\n result += BASE64_CHARS[b0 >> 2];\n result += BASE64_CHARS[(b0 & 0x03) << 4];\n result += \"==\";\n break;\n }\n\n if (b2 === undefined) {\n result += BASE64_CHARS[b0 >> 2];\n result += BASE64_CHARS[((b0 & 0x03) << 4) | (b1 >> 4)];\n result += BASE64_CHARS[(b1 & 0x0f) << 2];\n result += \"=\";\n break;\n }\n\n result += BASE64_CHARS[b0 >> 2];\n result += BASE64_CHARS[((b0 & 0x03) << 4) | (b1 >> 4)];\n result += BASE64_CHARS[((b1 & 0x0f) << 2) | (b2 >> 6)];\n result += BASE64_CHARS[b2 & 0x3f];\n i += 3;\n }\n\n return wrapBase64Lines(result);\n}\n\n/**\n * Decode a Base64 string to Uint8Array.\n */\nexport function decodeBase64(data: string): Uint8Array {\n const cleaned = data.replace(/\\s/g, \"\");\n const len = cleaned.length;\n\n if (len === 0) {\n return new Uint8Array(0);\n }\n\n if (len % 4 !== 0) {\n throw new Error(\"Invalid base64 string length\");\n }\n\n const padding = cleaned.endsWith(\"==\") ? 2 : cleaned.endsWith(\"=\") ? 1 : 0;\n const outputLen = (len * 3) / 4 - padding;\n const output = new Uint8Array(outputLen);\n\n let outIndex = 0;\n for (let i = 0; i < len; i += 4) {\n const c0 = base64CharToValue(cleaned[i] ?? \"=\");\n const c1 = base64CharToValue(cleaned[i + 1] ?? \"=\");\n const c2 = base64CharToValue(cleaned[i + 2] ?? \"=\");\n const c3 = base64CharToValue(cleaned[i + 3] ?? \"=\");\n const triple = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;\n\n if (outIndex < outputLen) {\n output[outIndex++] = (triple >> 16) & 0xff;\n }\n if (outIndex < outputLen) {\n output[outIndex++] = (triple >> 8) & 0xff;\n }\n if (outIndex < outputLen) {\n output[outIndex++] = triple & 0xff;\n }\n }\n\n return output;\n}\n\n/**\n * Encode text using Quoted-Printable (RFC 2045).\n */\nexport function encodeQP(text: string): string {\n const bytes = encoder.encode(text);\n const lines: string[] = [];\n let line = \"\";\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i] ?? 0;\n\n if (byte === 0x0a) {\n lines.push(line);\n line = \"\";\n continue;\n }\n\n if (byte === 0x0d) {\n continue;\n }\n\n let encoded: string;\n if (\n (byte >= 33 && byte <= 60) ||\n (byte >= 62 && byte <= 126) ||\n byte === 0x09 ||\n byte === 0x20\n ) {\n encoded = String.fromCharCode(byte);\n } else {\n encoded = `=${byte.toString(16).toUpperCase().padStart(2, \"0\")}`;\n }\n\n if (line.length + encoded.length > 75) {\n lines.push(`${line}=`);\n line = encoded;\n } else {\n line += encoded;\n }\n }\n\n if (line.length > 0) {\n lines.push(line);\n }\n\n return lines.join(\"\\r\\n\");\n}\n\n/**\n * Encode an email header value per RFC 2047.\n * Non-ASCII values become: =?UTF-8?B?<base64>?=\n */\nexport function encodeHeader(value: string): string {\n if (!needsEncoding(value)) {\n return value;\n }\n\n const encoded = encodeBase64(value).replace(/\\r\\n/g, \"\");\n return `=?UTF-8?B?${encoded}?=`;\n}\n\n/**\n * Returns true if the string contains non-ASCII characters\n * and therefore requires RFC 2047 encoding in headers.\n */\nexport function needsEncoding(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) > 127) {\n return true;\n }\n }\n return false;\n}\n\nfunction base64CharToValue(char: string): number {\n if (char === \"=\") {\n return 0;\n }\n const index = BASE64_CHARS.indexOf(char);\n if (index === -1) {\n throw new Error(`Invalid base64 character: ${char}`);\n }\n return index;\n}\n\nfunction wrapBase64Lines(base64: string): string {\n const lines: string[] = [];\n for (let i = 0; i < base64.length; i += BASE64_LINE_LENGTH) {\n lines.push(base64.slice(i, i + BASE64_LINE_LENGTH));\n }\n return lines.join(\"\\r\\n\");\n}\n\n/** Decode bytes to UTF-8 string. */\nexport function decodeUtf8(bytes: Uint8Array): string {\n return decoder.decode(bytes);\n}\n\n/** Encode string to UTF-8 bytes. */\nexport function encodeUtf8(text: string): Uint8Array {\n return encoder.encode(text);\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "93C9FABDDF825C4864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeHeader
|
|
3
|
+
} from "./chunk-794hc3m4.js";
|
|
4
|
+
import {
|
|
5
|
+
__require
|
|
6
|
+
} from "./chunk-v0bahtg2.js";
|
|
7
|
+
|
|
8
|
+
// src/core/address.ts
|
|
9
|
+
function parseAddresses(input) {
|
|
10
|
+
if (Array.isArray(input)) {
|
|
11
|
+
return input.flatMap((item) => parseAddresses(item));
|
|
12
|
+
}
|
|
13
|
+
if (typeof input === "object") {
|
|
14
|
+
return [{ ...input }];
|
|
15
|
+
}
|
|
16
|
+
const trimmed = input.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
return splitAddressList(trimmed).map(parseSingleAddress);
|
|
21
|
+
}
|
|
22
|
+
function toMIMEHeader(address) {
|
|
23
|
+
if (address.name) {
|
|
24
|
+
const name = encodeHeader(address.name);
|
|
25
|
+
return `${name} <${address.address}>`;
|
|
26
|
+
}
|
|
27
|
+
return address.address;
|
|
28
|
+
}
|
|
29
|
+
function extractEmails(input) {
|
|
30
|
+
return parseAddresses(input).map((addr) => addr.address);
|
|
31
|
+
}
|
|
32
|
+
function splitAddressList(input) {
|
|
33
|
+
const parts = [];
|
|
34
|
+
let current = "";
|
|
35
|
+
let inQuotes = false;
|
|
36
|
+
let inAngle = false;
|
|
37
|
+
for (let i = 0;i < input.length; i++) {
|
|
38
|
+
const char = input[i] ?? "";
|
|
39
|
+
if (char === '"' && input[i - 1] !== "\\") {
|
|
40
|
+
inQuotes = !inQuotes;
|
|
41
|
+
current += char;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (char === "<" && !inQuotes) {
|
|
45
|
+
inAngle = true;
|
|
46
|
+
current += char;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (char === ">" && !inQuotes) {
|
|
50
|
+
inAngle = false;
|
|
51
|
+
current += char;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (char === "," && !inQuotes && !inAngle) {
|
|
55
|
+
if (current.trim()) {
|
|
56
|
+
parts.push(current.trim());
|
|
57
|
+
}
|
|
58
|
+
current = "";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
current += char;
|
|
62
|
+
}
|
|
63
|
+
if (current.trim()) {
|
|
64
|
+
parts.push(current.trim());
|
|
65
|
+
}
|
|
66
|
+
return parts;
|
|
67
|
+
}
|
|
68
|
+
function parseSingleAddress(input) {
|
|
69
|
+
const trimmed = input.trim();
|
|
70
|
+
const angleMatch = trimmed.match(/^(?:"([^"]*)"|([^<]*?))\s*<([^>]+)>$/);
|
|
71
|
+
if (angleMatch) {
|
|
72
|
+
const name = (angleMatch[1] ?? angleMatch[2] ?? "").trim();
|
|
73
|
+
const address = (angleMatch[3] ?? "").trim();
|
|
74
|
+
if (name) {
|
|
75
|
+
return { name, address };
|
|
76
|
+
}
|
|
77
|
+
return { address };
|
|
78
|
+
}
|
|
79
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
80
|
+
return { address: trimmed.slice(1, -1) };
|
|
81
|
+
}
|
|
82
|
+
return { address: trimmed };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/transports/resolve-attachments.ts
|
|
86
|
+
async function resolveAttachments(attachments = []) {
|
|
87
|
+
const resolved = [];
|
|
88
|
+
for (const attachment of attachments) {
|
|
89
|
+
if (attachment.content instanceof Uint8Array) {
|
|
90
|
+
resolved.push(attachment);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (attachment.path) {
|
|
94
|
+
let fs;
|
|
95
|
+
try {
|
|
96
|
+
fs = await import("node:fs/promises");
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error("attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead");
|
|
99
|
+
}
|
|
100
|
+
const data = await fs.readFile(attachment.path);
|
|
101
|
+
const { path: _path, ...rest } = attachment;
|
|
102
|
+
resolved.push({ ...rest, content: new Uint8Array(data) });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (typeof attachment.content === "string") {
|
|
106
|
+
resolved.push(attachment);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
resolved.push(attachment);
|
|
110
|
+
}
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { parseAddresses, toMIMEHeader, extractEmails, resolveAttachments };
|
|
115
|
+
|
|
116
|
+
//# debugId=85211AC1FC8A34D664756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/address.ts", "../src/transports/resolve-attachments.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// src/core/address.ts\nimport { encodeHeader } from \"./base64.js\";\nimport type { Address, AddressInput } from \"./types.js\";\n\n/**\n * Normalize any AddressInput form into Address[].\n */\nexport function parseAddresses(input: AddressInput): Address[] {\n if (Array.isArray(input)) {\n return input.flatMap((item) => parseAddresses(item));\n }\n\n if (typeof input === \"object\") {\n return [{ ...input }];\n }\n\n const trimmed = input.trim();\n if (!trimmed) {\n return [];\n }\n\n return splitAddressList(trimmed).map(parseSingleAddress);\n}\n\n/**\n * Format an Address for SMTP envelope commands (MAIL FROM / RCPT TO).\n */\nexport function toEnvelope(address: Address): string {\n return address.address;\n}\n\n/**\n * Format an Address for use in a MIME header (From, To, CC, etc.).\n */\nexport function toMIMEHeader(address: Address): string {\n if (address.name) {\n const name = encodeHeader(address.name);\n return `${name} <${address.address}>`;\n }\n return address.address;\n}\n\n/**\n * Extract plain email strings from any AddressInput.\n */\nexport function extractEmails(input: AddressInput): string[] {\n return parseAddresses(input).map((addr) => addr.address);\n}\n\n/**\n * Basic email format validation (format only, no DNS lookup).\n */\nexport function isValidEmail(email: string): boolean {\n return /^[^\\s@<>]+@[^\\s@<>]+\\.[^\\s@<>]+$/.test(email);\n}\n\nfunction splitAddressList(input: string): string[] {\n const parts: string[] = [];\n let current = \"\";\n let inQuotes = false;\n let inAngle = false;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i] ?? \"\";\n if (char === '\"' && input[i - 1] !== \"\\\\\") {\n inQuotes = !inQuotes;\n current += char;\n continue;\n }\n if (char === \"<\" && !inQuotes) {\n inAngle = true;\n current += char;\n continue;\n }\n if (char === \">\" && !inQuotes) {\n inAngle = false;\n current += char;\n continue;\n }\n if (char === \",\" && !inQuotes && !inAngle) {\n if (current.trim()) {\n parts.push(current.trim());\n }\n current = \"\";\n continue;\n }\n current += char;\n }\n\n if (current.trim()) {\n parts.push(current.trim());\n }\n\n return parts;\n}\n\nfunction parseSingleAddress(input: string): Address {\n const trimmed = input.trim();\n\n const angleMatch = trimmed.match(/^(?:\"([^\"]*)\"|([^<]*?))\\s*<([^>]+)>$/);\n if (angleMatch) {\n const name = (angleMatch[1] ?? angleMatch[2] ?? \"\").trim();\n const address = (angleMatch[3] ?? \"\").trim();\n if (name) {\n return { name, address };\n }\n return { address };\n }\n\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n return { address: trimmed.slice(1, -1) };\n }\n\n return { address: trimmed };\n}\n",
|
|
6
|
+
"import type { Attachment } from \"../core/types.js\";\n\n/**\n * Resolve attachment.path to in-memory Uint8Array content.\n * @throws When attachment.path is used on runtimes without node:fs/promises\n */\nexport async function resolveAttachments(attachments: Attachment[] = []): Promise<Attachment[]> {\n const resolved: Attachment[] = [];\n\n for (const attachment of attachments) {\n if (attachment.content instanceof Uint8Array) {\n resolved.push(attachment);\n continue;\n }\n\n if (attachment.path) {\n let fs: typeof import(\"node:fs/promises\");\n try {\n fs = await import(\"node:fs/promises\");\n } catch {\n throw new Error(\n \"attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead\",\n );\n }\n\n const data = await fs.readFile(attachment.path);\n const { path: _path, ...rest } = attachment;\n resolved.push({ ...rest, content: new Uint8Array(data) });\n continue;\n }\n\n if (typeof attachment.content === \"string\") {\n resolved.push(attachment);\n continue;\n }\n\n resolved.push(attachment);\n }\n\n return resolved;\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "85211AC1FC8A34D664756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|