sently 0.7.2 → 0.8.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/CHANGELOG.md +16 -0
- package/README.md +192 -8
- package/dist/adapters/bun.js +1 -1
- package/dist/adapters/cf.js +2 -2
- package/dist/adapters/cf.js.map +1 -1
- package/dist/adapters/deno.js +1 -1
- package/dist/adapters/node.js +1 -1
- package/dist/auth/oauth2.js +2 -2
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/{chunk-d5rwm3br.js → chunk-5scdgffb.js} +2 -2
- package/dist/{chunk-d5rwm3br.js.map → chunk-5scdgffb.js.map} +1 -1
- package/dist/{chunk-a1c87hn1.js → chunk-6bkj4y4r.js} +2 -2
- package/dist/{chunk-a1c87hn1.js.map → chunk-6bkj4y4r.js.map} +1 -1
- package/dist/{chunk-cvrbfke9.js → chunk-8phpsns1.js} +3 -3
- package/dist/{chunk-cvrbfke9.js.map → chunk-8phpsns1.js.map} +1 -1
- package/dist/chunk-9zkv5azm.js +4 -0
- package/dist/chunk-9zkv5azm.js.map +10 -0
- package/dist/{chunk-qp4y0kp2.js → chunk-f3dz43cy.js} +2 -2
- package/dist/{chunk-qp4y0kp2.js.map → chunk-f3dz43cy.js.map} +1 -1
- package/dist/chunk-m07smapm.js +3 -0
- package/dist/chunk-m07smapm.js.map +10 -0
- package/dist/chunk-n5dan5bz.js +4 -0
- package/dist/chunk-n5dan5bz.js.map +10 -0
- package/dist/{chunk-keksa6fx.js → chunk-nbmq8ejv.js} +2 -2
- package/dist/{chunk-keksa6fx.js.map → chunk-nbmq8ejv.js.map} +1 -1
- package/dist/chunk-qh90yr6d.js +4 -0
- package/dist/{chunk-gh5n759p.js.map → chunk-qh90yr6d.js.map} +2 -2
- package/dist/{chunk-r7ee9tk7.js → chunk-vhxzhx8b.js} +3 -3
- package/dist/{chunk-r7ee9tk7.js.map → chunk-vhxzhx8b.js.map} +1 -1
- package/dist/{chunk-rm78s1ek.js → chunk-wbvhaqcp.js} +3 -3
- package/dist/{chunk-rm78s1ek.js.map → chunk-wbvhaqcp.js.map} +1 -1
- package/dist/{chunk-74rha3ac.js → chunk-wsymfbkn.js} +3 -3
- package/dist/{chunk-74rha3ac.js.map → chunk-wsymfbkn.js.map} +1 -1
- package/dist/chunk-yr56b0ts.js +4 -0
- package/dist/chunk-yr56b0ts.js.map +11 -0
- package/dist/{chunk-kebhy657.js → chunk-z62q7kqr.js} +2 -2
- package/dist/{chunk-kebhy657.js.map → chunk-z62q7kqr.js.map} +1 -1
- package/dist/{chunk-gkkv7yqm.js → chunk-zst8an2p.js} +3 -3
- package/dist/{chunk-gkkv7yqm.js.map → chunk-zst8an2p.js.map} +1 -1
- package/dist/core/errors.js +2 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/provider-label.d.ts +6 -0
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +27 -4
- package/dist/detect.js +2 -2
- package/dist/detect.js.map +1 -1
- package/dist/dkim.js +2 -2
- package/dist/dkim.js.map +1 -1
- package/dist/idempotency.d.ts +1 -0
- package/dist/idempotency.js +2 -2
- package/dist/idempotency.js.map +3 -3
- package/dist/index.d.ts +49 -0
- package/dist/index.js +58 -0
- package/dist/mailer.js +2 -2
- package/dist/mailer.js.map +1 -1
- package/dist/observability/console.d.ts +12 -0
- package/dist/observability/console.js +3 -0
- package/dist/observability/console.js.map +10 -0
- package/dist/plugins/react.js +2 -2
- package/dist/plugins/react.js.map +1 -1
- package/dist/plugins/template.js +1 -1
- package/dist/pool/pool.js +2 -2
- package/dist/pool/pool.js.map +1 -1
- package/dist/smtp-mailer.js +2 -2
- package/dist/smtp-mailer.js.map +2 -2
- package/dist/transports/brevo.d.ts +1 -0
- package/dist/transports/brevo.js +2 -2
- package/dist/transports/brevo.js.map +3 -3
- package/dist/transports/cloudflare-email.d.ts +58 -0
- package/dist/transports/cloudflare-email.js +3 -0
- package/dist/transports/cloudflare-email.js.map +10 -0
- package/dist/transports/fallback.d.ts +100 -0
- package/dist/transports/fallback.js +3 -0
- package/dist/transports/fallback.js.map +9 -0
- package/dist/transports/loops.d.ts +32 -0
- package/dist/transports/loops.js +3 -0
- package/dist/transports/loops.js.map +10 -0
- package/dist/transports/mailersend.d.ts +27 -0
- package/dist/transports/mailersend.js +3 -0
- package/dist/transports/mailersend.js.map +10 -0
- package/dist/transports/mailgun.d.ts +1 -0
- package/dist/transports/mailgun.js +2 -2
- package/dist/transports/mailgun.js.map +3 -3
- package/dist/transports/mailtrap.d.ts +36 -0
- package/dist/transports/mailtrap.js +3 -0
- package/dist/transports/mailtrap.js.map +10 -0
- package/dist/transports/plunk.d.ts +29 -0
- package/dist/transports/plunk.js +3 -0
- package/dist/transports/plunk.js.map +10 -0
- package/dist/transports/postmark.d.ts +1 -0
- package/dist/transports/postmark.js +2 -2
- package/dist/transports/postmark.js.map +3 -3
- package/dist/transports/preview.d.ts +1 -0
- package/dist/transports/preview.js +2 -2
- package/dist/transports/preview.js.map +3 -3
- package/dist/transports/resend.d.ts +1 -0
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +3 -3
- package/dist/transports/retry.d.ts +1 -0
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +3 -3
- package/dist/transports/sendgrid.d.ts +1 -0
- package/dist/transports/sendgrid.js +2 -2
- package/dist/transports/sendgrid.js.map +3 -3
- package/dist/transports/ses.d.ts +1 -0
- package/dist/transports/ses.js +3 -3
- package/dist/transports/ses.js.map +3 -3
- package/dist/transports/smtp.d.ts +1 -0
- package/dist/transports/smtp.js +2 -2
- package/dist/transports/smtp.js.map +1 -1
- package/dist/transports/sparkpost.d.ts +30 -0
- package/dist/transports/sparkpost.js +3 -0
- package/dist/transports/sparkpost.js.map +10 -0
- package/dist/transports/weighted-fallback.d.ts +65 -0
- package/dist/transports/weighted-fallback.js +3 -0
- package/dist/transports/weighted-fallback.js.map +10 -0
- package/dist/webhooks/brevo.js +1 -1
- package/dist/webhooks/mailgun.js +1 -1
- package/dist/webhooks/postmark.js +1 -1
- package/dist/webhooks/resend.js +1 -1
- package/dist/webhooks/sendgrid.js +1 -1
- package/dist/webhooks/ses.js +1 -1
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/package.json +37 -1
- package/dist/chunk-4mvp3b85.js +0 -3
- package/dist/chunk-4mvp3b85.js.map +0 -10
- package/dist/chunk-gh5n759p.js +0 -4
- package/dist/chunk-ka6t8g77.js +0 -4
- package/dist/chunk-ka6t8g77.js.map +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.8.0] — 2026-06-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`FallbackTransport`** (`sently/transports/fallback`) — provider failover through an ordered transport list; composes with `RetryTransport`; `FallbackError.attempts` records `{ provider, error }` per failed attempt
|
|
10
|
+
- **Five HTTP providers:** MailerSend, Plunk, SparkPost, Mailtrap, Loops (`sently/transports/mailersend`, `plunk`, `sparkpost`, `mailtrap`, `loops`)
|
|
11
|
+
- **`SendResult.provider`** and **`SendResult.providerIndex`** — set by `FallbackTransport` to identify which provider handled the send
|
|
12
|
+
- **`durationMs`** optional third argument on `onSuccess` / `onError` mailer hooks (backward compatible)
|
|
13
|
+
- **`consoleObserver`** (`sently/observability`) — ready-made console logging hooks; also exported from main `"sently"` barrel
|
|
14
|
+
- **Mailer `onFallback` hook** — `onFallback(ctx, failedProvider, nextProvider, error)` on `MailerHooks`; wired automatically when using `FallbackTransport` or `WeightedFallbackTransport`
|
|
15
|
+
- **Provider cooldowns** — `FallbackTransport(transports, { cooldownMs })` skips unhealthy providers until cooldown expires; shared across `WeightedFallbackTransport` sends
|
|
16
|
+
- **`verifyAll()`** on `FallbackTransport` / `WeightedFallbackTransport` — per-provider `{ ok, providers: [{ provider, ok, message? }] }` result; `verify()` still returns the first healthy provider
|
|
17
|
+
- **`WeightedFallbackTransport`** (`sently/transports/weighted-fallback`) — weighted-random primary provider with failover through remaining providers
|
|
18
|
+
- **`Transport.provider`** — stable provider identifier on every transport class; `getProviderLabel()` prefers it over `constructor.name` inference
|
|
19
|
+
- **`CloudflareEmailTransport`** (`sently/transports/cloudflare-email`) — Workers `send_email` binding transport (not fetch HTTP)
|
|
20
|
+
|
|
5
21
|
## [0.7.2] — 2026-05-31
|
|
6
22
|
|
|
7
23
|
### Fixed
|
package/README.md
CHANGED
|
@@ -14,7 +14,49 @@ bun add sently
|
|
|
14
14
|
[](#)
|
|
15
15
|
[](https://github.com/alialnaghmoush/sently)
|
|
16
16
|
|
|
17
|
-
> **Pre-1.0 — API may change.** sently is pre-1.0 and the public API is still being refined ahead of a stable v1.0.0. Breaking changes can land in any 0.x release; review the [CHANGELOG](CHANGELOG.md) before upgrading. Pin an exact version (e.g. `"sently": "0.
|
|
17
|
+
> **Pre-1.0 — API may change.** sently is pre-1.0 and the public API is still being refined ahead of a stable v1.0.0. Breaking changes can land in any 0.x release; review the [CHANGELOG](CHANGELOG.md) before upgrading. Pin an exact version (e.g. `"sently": "0.8.0"`) for production until v1.0.0.
|
|
18
|
+
|
|
19
|
+
## Index
|
|
20
|
+
|
|
21
|
+
**Getting started**
|
|
22
|
+
|
|
23
|
+
- [Why not Nodemailer?](#why-not-nodemailer)
|
|
24
|
+
- [The 30-second tour](#the-30-second-tour)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
27
|
+
- [SMTP with auto-detected adapter](#smtp-with-auto-detected-adapter)
|
|
28
|
+
- [Resend HTTP transport](#resend-http-transport-vercel-edge-compatible)
|
|
29
|
+
- [Cloudflare Worker](#cloudflare-worker)
|
|
30
|
+
- [Choosing an entrypoint](#choosing-an-entrypoint)
|
|
31
|
+
- [Migrating from Nodemailer](#migrating-from-nodemailer)
|
|
32
|
+
|
|
33
|
+
**Sending mail**
|
|
34
|
+
|
|
35
|
+
- [Adapters](#adapters)
|
|
36
|
+
- [Transports](#transports)
|
|
37
|
+
- [SMTP](#smtp)
|
|
38
|
+
- [HTTP APIs](#http-apis)
|
|
39
|
+
- [FallbackTransport](#fallbacktransport)
|
|
40
|
+
- [PreviewTransport](#previewtransport)
|
|
41
|
+
- [RetryTransport](#retrytransport)
|
|
42
|
+
- [sendBulk()](#sendbulk)
|
|
43
|
+
- [Mailer lifecycle hooks](#mailer-lifecycle-hooks)
|
|
44
|
+
- [IdempotencyTransport](#idempotencytransport)
|
|
45
|
+
- [Plugin system](#plugin-system)
|
|
46
|
+
- [TemplatePlugin](#templateplugin)
|
|
47
|
+
- [React Email plugin](#react-email-plugin)
|
|
48
|
+
- [Webhook parsing](#webhook-parsing)
|
|
49
|
+
|
|
50
|
+
**Reference**
|
|
51
|
+
|
|
52
|
+
- [MailOptions Reference](#mailoptions-reference)
|
|
53
|
+
- [Attachments](#attachments)
|
|
54
|
+
- [Error Handling](#error-handling)
|
|
55
|
+
- [Security](#security)
|
|
56
|
+
- [Bundle size](#bundle-size)
|
|
57
|
+
- [TypeScript](#typescript)
|
|
58
|
+
- [Links](#links)
|
|
59
|
+
- [License](#license)
|
|
18
60
|
|
|
19
61
|
---
|
|
20
62
|
|
|
@@ -29,7 +71,8 @@ bun add sently
|
|
|
29
71
|
| DKIM signing | ✓ via `nodemailer-dkim` | ✓ built-in (Web Crypto) |
|
|
30
72
|
| OAuth2 / XOAUTH2 | ✓ via plugin | ✓ built-in |
|
|
31
73
|
| Connection pooling | ✓ | ✓ |
|
|
32
|
-
| HTTP transports | ✓ via plugins | ✓ built-in (
|
|
74
|
+
| HTTP transports | ✓ via plugins | ✓ built-in (11 HTTP APIs + CF Email binding) |
|
|
75
|
+
| Provider failover | ✗ | ✓ `FallbackTransport` + weighted routing |
|
|
33
76
|
| Retry transport | ✗ | ✓ |
|
|
34
77
|
| Preview transport | ✗ | ✓ |
|
|
35
78
|
| Template engine | ✗ | ✓ |
|
|
@@ -150,6 +193,8 @@ await mailer.send({
|
|
|
150
193
|
|
|
151
194
|
### Cloudflare Worker
|
|
152
195
|
|
|
196
|
+
**SMTP relay** (outbound TCP via `cloudflare:sockets`):
|
|
197
|
+
|
|
153
198
|
```typescript
|
|
154
199
|
import { createSMTPMailer } from "sently/smtp";
|
|
155
200
|
import { CloudflareAdapter } from "sently/adapters/cf";
|
|
@@ -175,6 +220,30 @@ export default {
|
|
|
175
220
|
};
|
|
176
221
|
```
|
|
177
222
|
|
|
223
|
+
**Workers Email binding** (`[[send_email]]` in `wrangler.toml` — no fetch HTTP API):
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { createMailer } from "sently/mailer";
|
|
227
|
+
import { CloudflareEmailTransport } from "sently/transports/cloudflare-email";
|
|
228
|
+
|
|
229
|
+
export default {
|
|
230
|
+
async fetch(_request, env) {
|
|
231
|
+
const mailer = await createMailer({
|
|
232
|
+
transport: new CloudflareEmailTransport({ sendEmail: env.SEND_EMAIL }),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await mailer.send({
|
|
236
|
+
from: "noreply@yourdomain.com",
|
|
237
|
+
to: "user@example.com",
|
|
238
|
+
subject: "From a Worker",
|
|
239
|
+
text: "Sent via send_email binding",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return new Response("Sent");
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
178
247
|
---
|
|
179
248
|
|
|
180
249
|
## Adapters
|
|
@@ -329,10 +398,107 @@ const pool = new SMTPPool({
|
|
|
329
398
|
| Mailgun | `sently/transports/mailgun` | `apiKey`, `domain` |
|
|
330
399
|
| AWS SES | `sently/transports/ses` | `accessKeyId`, `secretAccessKey`, `region` |
|
|
331
400
|
| Brevo | `sently/transports/brevo` | `apiKey` |
|
|
401
|
+
| MailerSend | `sently/transports/mailersend` | `apiToken` |
|
|
402
|
+
| Plunk | `sently/transports/plunk` | `apiKey` |
|
|
403
|
+
| SparkPost | `sently/transports/sparkpost` | `apiKey`, `euRegion?` |
|
|
404
|
+
| Mailtrap | `sently/transports/mailtrap` | `apiToken`, `sandbox?`, `inboxId?` |
|
|
405
|
+
| Loops | `sently/transports/loops` | `apiKey`, `defaultTransactionalId?` |
|
|
406
|
+
| Cloudflare Email | `sently/transports/cloudflare-email` | `sendEmail` binding (`env.SEND_EMAIL`) |
|
|
332
407
|
|
|
333
408
|
All transports implement the same interface — swap without changing your send code.
|
|
334
409
|
|
|
335
|
-
|
|
410
|
+
**Routing decorators** (compose with any transport above):
|
|
411
|
+
|
|
412
|
+
| Transport | Import path | Purpose |
|
|
413
|
+
|-----------|-------------|---------|
|
|
414
|
+
| Fallback | `sently/transports/fallback` | Ordered provider failover |
|
|
415
|
+
| Weighted fallback | `sently/transports/weighted-fallback` | Weighted-random primary + failover |
|
|
416
|
+
| Retry | `sently/transports/retry` | Per-provider retries before failing over |
|
|
417
|
+
| Idempotency | `sently/idempotency` | Dedupe sends on retry/replay |
|
|
418
|
+
|
|
419
|
+
**Loops** is template-first: `subject`/`html`/`text` are ignored. Set `options.headers['x-loops-transactional-id']` (or `defaultTransactionalId` on the transport) and pass template variables via `options.data`.
|
|
420
|
+
|
|
421
|
+
**Plunk** sends one HTTP request per `to` address and aggregates results when multiple recipients are provided.
|
|
422
|
+
|
|
423
|
+
### FallbackTransport
|
|
424
|
+
|
|
425
|
+
Route through an ordered list of providers — if your primary has an outage, the next takes over. Compose with `RetryTransport` to retry within a provider before failing over:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
import { FallbackTransport } from "sently/transports/fallback";
|
|
429
|
+
import { RetryTransport } from "sently/transports/retry";
|
|
430
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
431
|
+
import { SESTransport } from "sently/transports/ses";
|
|
432
|
+
import { createMailer } from "sently/mailer";
|
|
433
|
+
|
|
434
|
+
const transport = new FallbackTransport([
|
|
435
|
+
new RetryTransport(new ResendTransport({ apiKey: process.env.RESEND_API_KEY! })),
|
|
436
|
+
new RetryTransport(
|
|
437
|
+
new SESTransport({
|
|
438
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
439
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
440
|
+
}),
|
|
441
|
+
),
|
|
442
|
+
]);
|
|
443
|
+
|
|
444
|
+
const mailer = await createMailer({ transport });
|
|
445
|
+
|
|
446
|
+
const result = await mailer.send({ from: "...", to: "...", subject: "...", html: "..." });
|
|
447
|
+
// result.provider === "ses", result.providerIndex === 1 → primary failed, secondary won
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Permanent client errors (HTTP 400/401/403, SMTP 535) are **not** retried on the next provider — they would fail identically everywhere. When all providers fail, `FallbackError.attempts` lists each `{ provider, error }` in order for debugging.
|
|
451
|
+
|
|
452
|
+
**Cooldown** — skip providers that recently failed until a cooldown expires:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
const transport = new FallbackTransport(transports, { cooldownMs: 300_000 });
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Full-chain verify** — `verify()` returns the first healthy provider; use `verifyAll()` for per-provider visibility:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
const { ok, providers } = await transport.verifyAll();
|
|
462
|
+
// providers: [{ provider: "resend", ok: true }, { provider: "ses", ok: false, message: "..." }]
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Mailer `onFallback` hook** — observability when failover happens (requires `FallbackTransport` in the stack):
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
const mailer = await createMailer({
|
|
469
|
+
transport,
|
|
470
|
+
hooks: {
|
|
471
|
+
onFallback: (_ctx, failedProvider, nextProvider, error) => {
|
|
472
|
+
console.log(`failover ${failedProvider} → ${nextProvider}`, error);
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Weighted routing** — shift traffic gradually between providers:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { WeightedFallbackTransport } from "sently/transports/weighted-fallback";
|
|
482
|
+
|
|
483
|
+
const transport = new WeightedFallbackTransport([
|
|
484
|
+
{ transport: new ResendTransport({ apiKey }), weight: 80 },
|
|
485
|
+
{ transport: new SESTransport({ accessKeyId, secretAccessKey }), weight: 20 },
|
|
486
|
+
]);
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Every transport exposes a stable **`Transport.provider`** string (e.g. `"resend"`, `"ses"`) for hooks, logs, and `SendResult.provider`.
|
|
490
|
+
|
|
491
|
+
**Cloudflare Workers Email** — use the `send_email` binding (not fetch HTTP). Configure `[[send_email]]` in `wrangler.toml`, then pass `env.SEND_EMAIL`:
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
import { CloudflareEmailTransport } from "sently/transports/cloudflare-email";
|
|
495
|
+
|
|
496
|
+
const mailer = await createMailer({
|
|
497
|
+
transport: new CloudflareEmailTransport({ sendEmail: env.SEND_EMAIL }),
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Attachments are base64-encoded in the binding payload; use `content: Uint8Array` on Workers (no `attachment.path`).
|
|
336
502
|
|
|
337
503
|
### PreviewTransport
|
|
338
504
|
|
|
@@ -400,21 +566,32 @@ Resend batches up to `RESEND_BATCH_MAX` (100) messages per request — export fr
|
|
|
400
566
|
|
|
401
567
|
### Mailer lifecycle hooks
|
|
402
568
|
|
|
403
|
-
Optional observability hooks on `createMailer` fire for every `send()` and `sendBulk()` message (batch paths invoke hooks once per message, without double-firing). Hook context carries `{ messageId?, to, subject, provider }` — no body fields, to avoid leaking PII into logs.
|
|
569
|
+
Optional observability hooks on `createMailer` fire for every `send()` and `sendBulk()` message (batch paths invoke hooks once per message, without double-firing). Hook context carries `{ messageId?, to, subject, provider }` — no body fields, to avoid leaking PII into logs. `onSuccess` and `onError` accept an optional third argument `durationMs` (elapsed milliseconds).
|
|
404
570
|
|
|
405
571
|
```typescript
|
|
572
|
+
import { consoleObserver } from "sently/observability";
|
|
573
|
+
|
|
406
574
|
const mailer = await createMailer({
|
|
407
575
|
transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
408
576
|
hooks: {
|
|
409
577
|
onSend: (ctx) => metrics.increment("email.send", { provider: ctx.provider }),
|
|
410
|
-
onSuccess: (ctx, result) =>
|
|
411
|
-
|
|
578
|
+
onSuccess: (ctx, result, durationMs) =>
|
|
579
|
+
metrics.histogram("email.duration", durationMs ?? 0),
|
|
580
|
+
onError: (ctx, err, durationMs) => metrics.increment("email.error"),
|
|
412
581
|
onRetry: (ctx, attempt, err) => metrics.increment("email.retry", { attempt }),
|
|
582
|
+
onFallback: (ctx, failed, next, err) =>
|
|
583
|
+
metrics.increment("email.fallback", { from: failed, to: next }),
|
|
413
584
|
},
|
|
414
585
|
});
|
|
586
|
+
|
|
587
|
+
// Quick start — log lifecycle events to the console
|
|
588
|
+
const devMailer = await createMailer({
|
|
589
|
+
transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
590
|
+
hooks: consoleObserver("[myapp]"),
|
|
591
|
+
});
|
|
415
592
|
```
|
|
416
593
|
|
|
417
|
-
Hooks are fully optional and zero-cost when unset. A throwing hook does **not** break the send — in non-production environments the error is logged with `console.warn` and the send continues. Pair `onRetry` with `RetryTransport` for per-attempt retry metrics.
|
|
594
|
+
Hooks are fully optional and zero-cost when unset. A throwing hook does **not** break the send — in non-production environments the error is logged with `console.warn` and the send continues. Pair `onRetry` with `RetryTransport` for per-attempt retry metrics; pair `onFallback` with `FallbackTransport` or `WeightedFallbackTransport` for failover observability.
|
|
418
595
|
|
|
419
596
|
Works with both `sently/mailer` (`{ transport, hooks }`) and SMTP config via `createSMTPMailer` (`{ host, auth, hooks }`).
|
|
420
597
|
|
|
@@ -632,6 +809,8 @@ import { ResendError } from "sently/transports/resend";
|
|
|
632
809
|
// MailgunError → sently/transports/mailgun
|
|
633
810
|
// SESError → sently/transports/ses
|
|
634
811
|
// BrevoError → sently/transports/brevo
|
|
812
|
+
// CloudflareEmailError → sently/transports/cloudflare-email
|
|
813
|
+
// FallbackError → sently/transports/fallback (all providers failed; see .attempts)
|
|
635
814
|
|
|
636
815
|
try {
|
|
637
816
|
await mailer.send({ ... });
|
|
@@ -739,7 +918,7 @@ import { ResendTransport } from "sently/transports/resend";
|
|
|
739
918
|
import { createSMTPMailer } from "sently/smtp";
|
|
740
919
|
```
|
|
741
920
|
|
|
742
|
-
Main `"sently"` exports shared types, `createMailer
|
|
921
|
+
Main `"sently"` exports shared types, `createMailer`, `createSMTPMailer`, `detectRuntime`, OAuth2, `SentlyError`, `consoleObserver`, and the v0.8 HTTP providers (`LoopsTransport`, `MailerSendTransport`, …), plus `FallbackTransport`, `WeightedFallbackTransport`, and `CloudflareEmailTransport`. Webhooks, idempotency, DKIM, SMTP-only transports, and plugins remain separate subpaths for smallest bundles.
|
|
743
922
|
|
|
744
923
|
---
|
|
745
924
|
|
|
@@ -757,6 +936,11 @@ How do you send mail?
|
|
|
757
936
|
│ import { createSMTPMailer } from "sently/smtp"
|
|
758
937
|
│ createSMTPMailer({ host, port, auth })
|
|
759
938
|
│
|
|
939
|
+
├─ Provider failover / weighted routing
|
|
940
|
+
│ import { FallbackTransport } from "sently/transports/fallback"
|
|
941
|
+
│ import { WeightedFallbackTransport } from "sently/transports/weighted-fallback"
|
|
942
|
+
│ createMailer({ transport: new FallbackTransport([primary, backup]) })
|
|
943
|
+
│
|
|
760
944
|
└─ Custom / decorated transport (Retry, Idempotency, Preview)
|
|
761
945
|
import { createMailer } from "sently/mailer"
|
|
762
946
|
createMailer({ transport: new RetryTransport(inner) })
|
package/dist/adapters/bun.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-nbmq8ejv.js";import W from"node:net";import Q from"node:tls";function V(f){if(f.rejectUnauthorized===!1)console.warn("[sently] TLS certificate verification is disabled. Never use rejectUnauthorized: false in production.")}class X{socket=null;_secure;_connected=!1;connectionTimeout;tlsOptions;constructor(f={}){if(typeof Bun>"u")throw Error("BunAdapter requires the Bun runtime");this._secure=f.secure??!1,this.connectionTimeout=f.connectionTimeout??30000,this.tlsOptions=f.tls??{}}get secure(){return this._secure}get connected(){return this._connected}async connect(f,B){if(this._secure)await this.connectTls(f,B);else await this.connectPlain(f,B);this._connected=!0}async startTLS(f){if(!this.socket||this._secure)throw Error("Cannot STARTTLS: no plain socket available");let B=this.socket,y={...this.tlsOptions,...f};V(y),await new Promise((F,A)=>{let G=Q.connect({socket:B,servername:y.servername,rejectUnauthorized:y.rejectUnauthorized??!0,minVersion:y.minVersion});G.once("secureConnect",()=>{this.socket=G,this._secure=!0,F()}),G.once("error",A)})}async write(f){if(!this.socket)throw Error("Socket not connected");await new Promise((B,y)=>{this.socket?.write(Buffer.from(f),(F)=>{if(F)y(F);else B()})})}async*read(){if(!this.socket)throw Error("Socket not connected");let f=this.socket,B=[],y=null,F=!1,A=null,G=(H)=>{let I=new Uint8Array(H);if(y)y({value:I,done:!1}),y=null;else B.push(I)},J=(H)=>{if(A=H,F=!0,y)y({value:void 0,done:!0}),y=null},K=()=>{if(F=!0,y)y({value:void 0,done:!0}),y=null};f.on("data",G),f.on("error",J),f.on("close",K);try{while(!F||B.length>0){if(A)throw A;if(B.length>0){yield B.shift();continue}if(F)break;let H=await new Promise((I)=>{y=I});if(H.done)break;yield H.value}}finally{f.off("data",G),f.off("error",J),f.off("close",K)}}async close(){if(!this.socket)return;await new Promise((f)=>{this.socket?.end(()=>f())}),this.socket=null,this._connected=!1}connectPlain(f,B){return new Promise((y,F)=>{let A=W.connect({host:f,port:B},()=>y());A.setTimeout(this.connectionTimeout),A.once("timeout",()=>{A.destroy(),F(Error("Connection timeout"))}),A.once("error",F),this.socket=A})}connectTls(f,B){return V(this.tlsOptions),new Promise((y,F)=>{let A=Q.connect({host:f,port:B,servername:this.tlsOptions.servername??f,rejectUnauthorized:this.tlsOptions.rejectUnauthorized??!0,minVersion:this.tlsOptions.minVersion},()=>y());A.setTimeout(this.connectionTimeout),A.once("timeout",()=>{A.destroy(),F(Error("Connection timeout"))}),A.once("error",F),this.socket=A})}}export{X as BunAdapter};
|
|
2
2
|
|
|
3
3
|
//# debugId=94AAFD676203266764756E2164756E21
|
package/dist/adapters/cf.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{_ as q}from"../chunk-nbmq8ejv.js";class w{socket=null;writer=null;_secure;_connected=!1;directTls;starttls;constructor(b={}){this._secure=b.secure??!1,this.directTls=b.secure??!1,this.starttls=b.starttls??!this.directTls}get secure(){return this._secure}get connected(){return this._connected}async connect(b,g){let{connect:j}=await import("cloudflare:sockets"),m=this.directTls?"on":this.starttls?"starttls":"off";this.socket=j({hostname:b,port:g},{secureTransport:m}),this.writer=this.socket.writable.getWriter(),this._connected=!0,this._secure=m==="on"}async startTLS(b){if(!this.socket||this._secure)throw Error("Cannot STARTTLS: no plain socket available");await this.writer?.close(),this.socket=this.socket.startTls(),this.writer=this.socket.writable.getWriter(),this._secure=!0}async write(b){if(!this.writer)throw Error("Socket not connected");await this.writer.write(b)}async*read(){if(!this.socket)throw Error("Socket not connected");let b=this.socket.readable.getReader();try{while(!0){let{value:g,done:j}=await b.read();if(j)break;if(g)yield g}}finally{b.releaseLock()}}async close(){await this.writer?.close(),await this.socket?.close(),this.writer=null,this.socket=null,this._connected=!1}}export{w as CloudflareAdapter};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=885E3D60168B238964756E2164756E21
|
package/dist/adapters/cf.js.map
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * @module\n * Cloudflare Workers socket adapter for SMTP via cloudflare:sockets.\n *\n * @example\n * ```ts\n * import { CloudflareAdapter } from \"sently/adapters/cf\";\n * import { createSMTPMailer } from \"sently/smtp\";\n *\n * const mailer = await createSMTPMailer({\n * host: \"smtp.example.com\",\n * adapter: new CloudflareAdapter(),\n * auth: { user: \"relay@example.com\", pass: \"secret\" },\n * });\n * ```\n */\nimport type { SocketAdapter, TLSOptions } from \"../core/types.js\";\n\ninterface CFSocket {\n readonly readable: ReadableStream<Uint8Array>;\n readonly writable: WritableStream<Uint8Array>;\n readonly closed: Promise<void>;\n close(): Promise<void>;\n startTls(): CFSocket;\n}\n\ntype CFConnect = (\n address: { hostname: string; port: number },\n options?: { secureTransport?: \"off\" | \"on\" | \"starttls\"; allowHalfOpen?: boolean },\n) => CFSocket;\n\n/** Configuration options for {@link CloudflareAdapter}. */\nexport interface CloudflareAdapterOptions {\n /** Use implicit TLS on connect (secureTransport: \"on\"). Default: false. */\n secure?: boolean;\n /** Enable STARTTLS upgrade after plain connect. Default: true when not secure. */\n starttls?: boolean;\n /** Reserved for future TLS tuning (Workers sockets API). */\n tls?: TLSOptions;\n}\n\n/**\n * Cloudflare Workers socket adapter via cloudflare:sockets.\n *\n * Limitations:\n * - No connection pooling (isolate lifecycle)\n * - No file system access for attachment.path\n * - No DNS MX lookup — explicit SMTP relay host required\n */\nexport class CloudflareAdapter implements SocketAdapter {\n /** Underlying Cloudflare Workers socket. */\n private socket: CFSocket | null = null;\n /** Writable stream writer for outbound bytes. */\n private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;\n /** Whether the connection is currently encrypted. */\n private _secure: boolean;\n /** Whether the socket is connected. */\n private _connected = false;\n /** Whether implicit TLS is used on connect (port 465 style). */\n private readonly directTls: boolean;\n /** Whether STARTTLS upgrade is enabled after plain connect. */\n private readonly starttls: boolean;\n\n /** Creates a Cloudflare Workers socket adapter. */\n constructor(options: CloudflareAdapterOptions = {}) {\n this._secure = options.secure ?? false;\n this.directTls = options.secure ?? false;\n this.starttls = options.starttls ?? !this.directTls;\n }\n\n /** Whether the connection uses TLS. */\n get secure(): boolean {\n return this._secure;\n }\n\n /** Whether the socket is currently connected. */\n get connected(): boolean {\n return this._connected;\n }\n\n /** Opens a TCP or TLS connection to the given host and port. */\n async connect(host: string, port: number): Promise<void> {\n const { connect } = (await import(\"cloudflare:sockets\")) as { connect: CFConnect };\n\n const secureTransport = this.directTls ? \"on\" : this.starttls ? \"starttls\" : \"off\";\n this.socket = connect({ hostname: host, port }, { secureTransport });\n this.writer = this.socket.writable.getWriter();\n this._connected = true;\n this._secure = secureTransport === \"on\";\n }\n\n /** Upgrades a plain connection to TLS via STARTTLS. */\n async startTLS(_options?: TLSOptions): Promise<void> {\n if (!this.socket || this._secure) {\n throw new Error(\"Cannot STARTTLS: no plain socket available\");\n }\n\n await this.writer?.close();\n this.socket = this.socket.startTls();\n this.writer = this.socket.writable.getWriter();\n this._secure = true;\n }\n\n /** Writes raw bytes to the socket. */\n async write(data: Uint8Array): Promise<void> {\n if (!this.writer) {\n throw new Error(\"Socket not connected\");\n }\n await this.writer.write(data);\n }\n\n /** Reads incoming socket data as an async iterable of byte chunks. */\n async *read(): AsyncGenerator<Uint8Array, void, unknown> {\n if (!this.socket) {\n throw new Error(\"Socket not connected\");\n }\n\n const reader = this.socket.readable.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (value) {\n yield value;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n /** Closes the socket connection. */\n async close(): Promise<void> {\n await this.writer?.close();\n await this.socket?.close();\n this.writer = null;\n this.socket = null;\n this._connected = false;\n }\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "yCAiDO,MAAM,CAA2C,CAE9C,OAA0B,KAE1B,OAAyD,KAEzD,QAEA,WAAa,GAEJ,UAEA,SAGjB,WAAW,CAAC,EAAoC,CAAC,EAAG,CAClD,KAAK,QAAU,EAAQ,QAAU,GACjC,KAAK,UAAY,EAAQ,QAAU,GACnC,KAAK,SAAW,EAAQ,UAAY,CAAC,KAAK,aAIxC,OAAM,EAAY,CACpB,OAAO,KAAK,WAIV,UAAS,EAAY,CACvB,OAAO,KAAK,gBAIR,QAAO,CAAC,EAAc,EAA6B,CACvD,IAAQ,WAAa,KAAa,8BAE5B,EAAkB,KAAK,UAAY,KAAO,KAAK,SAAW,WAAa,MAC7E,KAAK,OAAS,EAAQ,CAAE,SAAU,EAAM,MAAK,EAAG,CAAE,iBAAgB,CAAC,EACnE,KAAK,OAAS,KAAK,OAAO,SAAS,UAAU,EAC7C,KAAK,WAAa,GAClB,KAAK,QAAU,IAAoB,UAI/B,SAAQ,CAAC,EAAsC,CACnD,GAAI,CAAC,KAAK,QAAU,KAAK,QACvB,MAAU,MAAM,4CAA4C,EAG9D,MAAM,KAAK,QAAQ,MAAM,EACzB,KAAK,OAAS,KAAK,OAAO,SAAS,EACnC,KAAK,OAAS,KAAK,OAAO,SAAS,UAAU,EAC7C,KAAK,QAAU,QAIX,MAAK,CAAC,EAAiC,CAC3C,GAAI,CAAC,KAAK,OACR,MAAU,MAAM,sBAAsB,EAExC,MAAM,KAAK,OAAO,MAAM,CAAI,QAIvB,IAAI,EAA8C,CACvD,GAAI,CAAC,KAAK,OACR,MAAU,MAAM,sBAAsB,EAGxC,IAAM,EAAS,KAAK,OAAO,SAAS,UAAU,EAC9C,GAAI,CACF,MAAO,GAAM,CACX,IAAQ,QAAO,QAAS,MAAM,EAAO,KAAK,EAC1C,GAAI,EACF,MAEF,GAAI,EACF,MAAM,UAGV,CACA,EAAO,YAAY,QAKjB,MAAK,EAAkB,CAC3B,MAAM,KAAK,QAAQ,MAAM,EACzB,MAAM,KAAK,QAAQ,MAAM,EACzB,KAAK,OAAS,KACd,KAAK,OAAS,KACd,KAAK,WAAa,GAEtB",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "885E3D60168B238964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/adapters/deno.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-nbmq8ejv.js";class v{conn=null;_secure;_connected=!1;tlsOptions;constructor(j={}){if(typeof Deno>"u")throw Error("DenoAdapter requires the Deno runtime");this._secure=j.secure??!1,this.tlsOptions=j.tls??{}}get secure(){return this._secure}get connected(){return this._connected}async connect(j,q){if(this._secure)this.conn=await Deno.connectTls({hostname:j,port:q,...this.tlsOptions.servername?{servername:this.tlsOptions.servername}:{}});else this.conn=await Deno.connect({hostname:j,port:q});this._connected=!0}async startTLS(j){if(!this.conn||this._secure)throw Error("Cannot STARTTLS: no plain connection available");let q={...this.tlsOptions,...j};this.conn=await Deno.startTls(this.conn,{...q.servername?{hostname:q.servername}:{}}),this._secure=!0}async write(j){if(!this.conn)throw Error("Socket not connected");await this.conn.write(j)}async*read(){if(!this.conn)throw Error("Socket not connected");let j=new Uint8Array(8192);while(!0){let q=await this.conn.read(j);if(q===null)break;yield j.slice(0,q)}}async close(){this.conn?.close(),this.conn=null,this._connected=!1}}export{v as DenoAdapter};
|
|
2
2
|
|
|
3
3
|
//# debugId=433F0358DDA6910C64756E2164756E21
|
package/dist/adapters/node.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-nbmq8ejv.js";import W from"node:net";import Q from"node:tls";function V(f){if(f.rejectUnauthorized===!1)console.warn("[sently] TLS certificate verification is disabled. Never use rejectUnauthorized: false in production.")}class X{socket=null;_secure;_connected=!1;connectionTimeout;tlsOptions;constructor(f={}){this._secure=f.secure??!1,this.connectionTimeout=f.connectionTimeout??30000,this.tlsOptions=f.tls??{}}get secure(){return this._secure}get connected(){return this._connected}async connect(f,B){if(this._secure)await this.connectTls(f,B);else await this.connectPlain(f,B);this._connected=!0}async startTLS(f){if(!this.socket||this._secure)throw Error("Cannot STARTTLS: no plain socket available");let B=this.socket,y={...this.tlsOptions,...f};V(y),await new Promise((F,A)=>{let G=Q.connect({socket:B,servername:y.servername,rejectUnauthorized:y.rejectUnauthorized??!0,minVersion:y.minVersion});G.once("secureConnect",()=>{this.socket=G,this._secure=!0,F()}),G.once("error",A)})}async write(f){if(!this.socket)throw Error("Socket not connected");await new Promise((B,y)=>{this.socket?.write(Buffer.from(f),(F)=>{if(F)y(F);else B()})})}async*read(){if(!this.socket)throw Error("Socket not connected");let f=this.socket,B=[],y=null,F=!1,A=null,G=(H)=>{let I=new Uint8Array(H);if(y)y({value:I,done:!1}),y=null;else B.push(I)},J=(H)=>{if(A=H,y)y({value:void 0,done:!0}),y=null;F=!0},K=()=>{if(F=!0,y)y({value:void 0,done:!0}),y=null};f.on("data",G),f.on("error",J),f.on("close",K);try{while(!F||B.length>0){if(A)throw A;if(B.length>0){yield B.shift();continue}if(F)break;let H=await new Promise((I)=>{y=I});if(H.done)break;yield H.value}}finally{f.off("data",G),f.off("error",J),f.off("close",K)}}async close(){if(!this.socket)return;await new Promise((f)=>{this.socket?.end(()=>f())}),this.socket=null,this._connected=!1}connectPlain(f,B){return new Promise((y,F)=>{let A=W.connect({host:f,port:B},()=>y());A.setTimeout(this.connectionTimeout),A.once("timeout",()=>{A.destroy(),F(Error("Connection timeout"))}),A.once("error",F),this.socket=A})}connectTls(f,B){return V(this.tlsOptions),new Promise((y,F)=>{let A=Q.connect({host:f,port:B,servername:this.tlsOptions.servername??f,rejectUnauthorized:this.tlsOptions.rejectUnauthorized??!0,minVersion:this.tlsOptions.minVersion},()=>y());A.setTimeout(this.connectionTimeout),A.once("timeout",()=>{A.destroy(),F(Error("Connection timeout"))}),A.once("error",F),this.socket=A})}}export{X as NodeAdapter};
|
|
2
2
|
|
|
3
3
|
//# debugId=81CB75805F76C9AC64756E2164756E21
|
package/dist/auth/oauth2.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{q as a,r as b,s as c}from"../chunk-wbvhaqcp.js";import"../chunk-5scdgffb.js";import"../chunk-nbmq8ejv.js";export{c as OAuth2Client,b as MICROSOFT_TOKEN_URL,a as GOOGLE_TOKEN_URL};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=37A9E134436A38BE64756E2164756E21
|
package/dist/auth/oauth2.js.map
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var J=new TextEncoder;function P(k){let f=typeof k==="string"?J.encode(k):k,j="",A=0;while(A<f.length){let m=f[A]??0,q=f[A+1],z=f[A+2];if(q===void 0){j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4],j+="==";break}if(z===void 0){j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4|q>>4],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(q&15)<<2],j+="=";break}j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4|q>>4],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(q&15)<<2|z>>6],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[z&63],A+=3}return R(j)}function S(k){let f=k.replace(/\s/g,""),j=f.length;if(j===0)return new Uint8Array(0);if(j%4!==0)throw Error("Invalid base64 string length");let A=f.endsWith("==")?2:f.endsWith("=")?1:0,m=j*3/4-A,q=new Uint8Array(m),z=0;for(let D=0;D<j;D+=4){let K=G(f[D]??"="),M=G(f[D+1]??"="),N=G(f[D+2]??"="),O=G(f[D+3]??"="),H=K<<18|M<<12|N<<6|O;if(z<m)q[z++]=H>>16&255;if(z<m)q[z++]=H>>8&255;if(z<m)q[z++]=H&255}return q}function W(k){if(!Q(k))return k;return`=?UTF-8?B?${P(k).replace(/\r\n/g,"")}?=`}function Q(k){for(let f=0;f<k.length;f++)if(k.charCodeAt(f)>127)return!0;return!1}function G(k){if(k==="=")return 0;let f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(k);if(f===-1)throw Error(`Invalid base64 character: ${k}`);return f}function R(k){let f=[];for(let j=0;j<k.length;j+=76)f.push(k.slice(j,j+76));return f.join(`\r
|
|
2
2
|
`)}function X(k){return J.encode(k)}
|
|
3
|
-
export{P,S as
|
|
3
|
+
export{P as T,S as U,W as V,X as W};
|
|
4
4
|
|
|
5
|
-
//# debugId=
|
|
5
|
+
//# debugId=4A06DB0A7917D84164756E2164756E21
|
|
@@ -5,6 +5,6 @@
|
|
|
5
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
6
|
],
|
|
7
7
|
"mappings": "AAKA,IAAM,EAAU,IAAI,YAOb,SAAS,CAAY,CAAC,EAAmC,CAC9D,IAAM,EAAQ,OAAO,IAAS,SAAW,EAAQ,OAAO,CAAI,EAAI,EAC5D,EAAS,GACT,EAAI,EAER,MAAO,EAAI,EAAM,OAAQ,CACvB,IAAM,EAAK,EAAM,IAAM,EACjB,EAAK,EAAM,EAAI,GACf,EAAK,EAAM,EAAI,GAErB,GAAI,IAAO,OAAW,CACpB,GArBe,mEAqBQ,GAAM,GAC7B,GAtBe,mEAsBS,GAAK,IAAS,GACtC,GAAU,KACV,MAGF,GAAI,IAAO,OAAW,CACpB,GA5Be,mEA4BQ,GAAM,GAC7B,GA7Be,mEA6BU,GAAK,IAAS,EAAM,GAAM,GACnD,GA9Be,mEA8BS,GAAK,KAAS,GACtC,GAAU,IACV,MAGF,GAnCiB,mEAmCM,GAAM,GAC7B,GApCiB,mEAoCQ,GAAK,IAAS,EAAM,GAAM,GACnD,GArCiB,mEAqCQ,GAAK,KAAS,EAAM,GAAM,GACnD,GAtCiB,mEAsCM,EAAK,IAC5B,GAAK,EAGP,OAAO,EAAgB,CAAM,EAMxB,SAAS,CAAY,CAAC,EAA0B,CACrD,IAAM,EAAU,EAAK,QAAQ,MAAO,EAAE,EAChC,EAAM,EAAQ,OAEpB,GAAI,IAAQ,EACV,OAAO,IAAI,WAAW,CAAC,EAGzB,GAAI,EAAM,IAAM,EACd,MAAU,MAAM,8BAA8B,EAGhD,IAAM,EAAU,EAAQ,SAAS,IAAI,EAAI,EAAI,EAAQ,SAAS,GAAG,EAAI,EAAI,EACnE,EAAa,EAAM,EAAK,EAAI,EAC5B,EAAS,IAAI,WAAW,CAAS,EAEnC,EAAW,EACf,QAAS,EAAI,EAAG,EAAI,EAAK,GAAK,EAAG,CAC/B,IAAM,EAAK,EAAkB,EAAQ,IAAM,GAAG,EACxC,EAAK,EAAkB,EAAQ,EAAI,IAAM,GAAG,EAC5C,EAAK,EAAkB,EAAQ,EAAI,IAAM,GAAG,EAC5C,EAAK,EAAkB,EAAQ,EAAI,IAAM,GAAG,EAC5C,EAAU,GAAM,GAAO,GAAM,GAAO,GAAM,EAAK,EAErD,GAAI,EAAW,EACb,EAAO,KAAe,GAAU,GAAM,IAExC,GAAI,EAAW,EACb,EAAO,KAAe,GAAU,EAAK,IAEvC,GAAI,EAAW,EACb,EAAO,KAAc,EAAS,IAIlC,OAAO,EAuDF,SAAS,CAAY,CAAC,EAAuB,CAClD,GAAI,CAAC,EAAc,CAAK,EACtB,OAAO,EAIT,MAAO,aADS,EAAa,CAAK,EAAE,QAAQ,QAAS,EAAE,MAQlD,SAAS,CAAa,CAAC,EAAuB,CACnD,QAAS,EAAI,EAAG,EAAI,EAAK,OAAQ,IAC/B,GAAI,EAAK,WAAW,CAAC,EAAI,IACvB,MAAO,GAGX,MAAO,GAGT,SAAS,CAAiB,CAAC,EAAsB,CAC/C,GAAI,IAAS,IACX,MAAO,GAET,IAAM,EApKa,mEAoKQ,QAAQ,CAAI,EACvC,GAAI,IAAU,GACZ,MAAU,MAAM,6BAA6B,GAAM,EAErD,OAAO,EAGT,SAAS,CAAe,CAAC,EAAwB,CAC/C,IAAM,EAAkB,CAAC,EACzB,QAAS,EAAI,EAAG,EAAI,EAAO,OAAQ,GA5KV,GA6KvB,EAAM,KAAK,EAAO,MAAM,EAAG,EA7KJ,EA6K0B,CAAC,EAEpD,OAAO,EAAM,KAAK;AAAA,CAAM,EASnB,SAAS,CAAU,CAAC,EAA0B,CACnD,OAAO,EAAQ,OAAO,CAAI",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "4A06DB0A7917D84164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
class v extends Error{sentlyCode;statusCode;provider;code;constructor(j,k,q){super(j,q?.cause!==void 0?{cause:q.cause}:void 0);if(this.name="SentlyError",this.sentlyCode=k,this.code=k,q?.statusCode!==void 0)this.statusCode=q.statusCode;if(q?.provider!==void 0)this.provider=q.provider}}function w(j){if(j===429)return"RATE_LIMITED";if(j>=500)return"PROVIDER_ERROR";if(j>=400)return"BAD_REQUEST";return"PROVIDER_ERROR"}function x(j,k){if(j===535||j===534)return"SMTP_AUTH_FAILED";if(j===421||j===450||j===451)return"TIMEOUT";if(j===0){if(k==="CONNECT"||k==="READ"||k==="MX")return"CONNECTION_FAILED";return"PROVIDER_ERROR"}if(j>=500)return"PROVIDER_ERROR";if(j>=400)return"BAD_REQUEST";return"PROVIDER_ERROR"}
|
|
2
|
-
export{v as
|
|
2
|
+
export{v as X,w as Y,x as Z};
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=894390EEDA2D4BE364756E2164756E21
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * @module\n * Unified error hierarchy for sently transports and SMTP.\n */\n\n/** Stable machine-readable error codes shared across providers. */\nexport type SentlyErrorCode =\n | \"INVALID_CONFIG\"\n | \"SMTP_AUTH_FAILED\"\n | \"RATE_LIMITED\"\n | \"TIMEOUT\"\n | \"CONNECTION_FAILED\"\n | \"PROVIDER_ERROR\"\n | \"BAD_REQUEST\";\n\n/** Options for constructing a {@link SentlyError}. */\nexport interface SentlyErrorOptions {\n /** HTTP status code when the failure originated from an HTTP transport. */\n statusCode?: number;\n /** Original error or response payload. */\n cause?: unknown;\n /** Provider identifier (e.g. `\"smtp\"`, `\"resend\"`). */\n provider?: string;\n}\n\n/**\n * Base error class for all sently transport and protocol failures.\n *\n * Subclasses may shadow {@link code} with provider-specific or numeric values\n * (e.g. SMTP response codes). Use {@link sentlyCode} for the stable machine-readable\n * code in those cases.\n */\nexport class SentlyError extends Error {\n /**\n * Stable machine-readable error code.\n * Preserved even when a subclass shadows {@link code}.\n */\n readonly sentlyCode: SentlyErrorCode;\n\n /** HTTP status code when applicable. */\n readonly statusCode?: number;\n\n /** Provider identifier when applicable. */\n readonly provider?: string;\n\n /**\n * Error code — machine-readable string on {@link SentlyError}, or a legacy\n * provider/SMTP-specific value on subclasses (numeric SMTP codes, Brevo/SES API codes).\n */\n code: string | number;\n\n /** Creates a sently error with a machine-readable code and optional metadata. */\n constructor(message: string, sentlyCode: SentlyErrorCode, options?: SentlyErrorOptions) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"SentlyError\";\n this.sentlyCode = sentlyCode;\n this.code = sentlyCode;\n if (options?.statusCode !== undefined) {\n this.statusCode = options.statusCode;\n }\n if (options?.provider !== undefined) {\n this.provider = options.provider;\n }\n }\n}\n\n/**\n * Map an HTTP status code to a stable sently error code.\n *\n * @param status - HTTP response status code\n * @returns Machine-readable error code\n */\nexport function httpStatusToSentlyCode(status: number): SentlyErrorCode {\n if (status === 429) {\n return \"RATE_LIMITED\";\n }\n if (status >= 500) {\n return \"PROVIDER_ERROR\";\n }\n if (status >= 400) {\n return \"BAD_REQUEST\";\n }\n return \"PROVIDER_ERROR\";\n}\n\n/**\n * Map an SMTP response code and command to a stable sently error code.\n *\n * @param smtpCode - Three-digit SMTP response code (0 for local/parse errors)\n * @param command - SMTP command that failed\n * @returns Machine-readable error code\n */\nexport function smtpCodeToSentlyCode(smtpCode: number, command: string): SentlyErrorCode {\n if (smtpCode === 535 || smtpCode === 534) {\n return \"SMTP_AUTH_FAILED\";\n }\n if (smtpCode === 421 || smtpCode === 450 || smtpCode === 451) {\n return \"TIMEOUT\";\n }\n if (smtpCode === 0) {\n if (command === \"CONNECT\" || command === \"READ\" || command === \"MX\") {\n return \"CONNECTION_FAILED\";\n }\n return \"PROVIDER_ERROR\";\n }\n if (smtpCode >= 500) {\n return \"PROVIDER_ERROR\";\n }\n if (smtpCode >= 400) {\n return \"BAD_REQUEST\";\n }\n return \"PROVIDER_ERROR\";\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "AAgCO,MAAM,UAAoB,KAAM,CAK5B,WAGA,WAGA,SAMT,KAGA,WAAW,CAAC,EAAiB,EAA6B,EAA8B,CACtF,MAAM,EAAS,GAAS,QAAU,OAAY,CAAE,MAAO,EAAQ,KAAM,EAAI,MAAS,EAIlF,GAHA,KAAK,KAAO,cACZ,KAAK,WAAa,EAClB,KAAK,KAAO,EACR,GAAS,aAAe,OAC1B,KAAK,WAAa,EAAQ,WAE5B,GAAI,GAAS,WAAa,OACxB,KAAK,SAAW,EAAQ,SAG9B,CAQO,SAAS,CAAsB,CAAC,EAAiC,CACtE,GAAI,IAAW,IACb,MAAO,eAET,GAAI,GAAU,IACZ,MAAO,iBAET,GAAI,GAAU,IACZ,MAAO,cAET,MAAO,iBAUF,SAAS,CAAoB,CAAC,EAAkB,EAAkC,CACvF,GAAI,IAAa,KAAO,IAAa,IACnC,MAAO,mBAET,GAAI,IAAa,KAAO,IAAa,KAAO,IAAa,IACvD,MAAO,UAET,GAAI,IAAa,EAAG,CAClB,GAAI,IAAY,WAAa,IAAY,QAAU,IAAY,KAC7D,MAAO,oBAET,MAAO,iBAET,GAAI,GAAY,IACd,MAAO,iBAET,GAAI,GAAY,IACd,MAAO,cAET,MAAO",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "894390EEDA2D4BE364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{T as O,U as K,W as H}from"./chunk-5scdgffb.js";import{X as v,Z as B}from"./chunk-6bkj4y4r.js";var j=64;function X(q){return q>>>0}function U(q){let z=g(q),G=1732584193,Q=4023233417,N=2562383102,J=271733878;for(let F=0;F<z.length;F+=64){let $=z.subarray(F,F+64),w=new Uint32Array(16);for(let W=0;W<16;W++){let A=W*4;w[W]=X(($[A]??0)|($[A+1]??0)<<8|($[A+2]??0)<<16|($[A+3]??0)<<24)}let x=G,D=Q,I=N,R=J;for(let W=0;W<64;W++){let A,_;if(W<16)A=X(D&I|~D&R),_=W;else if(W<32)A=X(D&R|I&~R),_=X((5*W+1)%16);else if(W<48)A=X(D^I^R),_=X((3*W+5)%16);else A=X(I^(D|~R)),_=X(7*W%16);let y=R;R=I,I=D,D=X(D+T(X(x+A+X((P[W]??0)+(w[_]??0))),L[W]??0)),x=y}G=X(G+x),Q=X(Q+D),N=X(N+I),J=X(J+R)}let Y=new Uint8Array(16),V=new DataView(Y.buffer);return V.setUint32(0,G,!0),V.setUint32(4,Q,!0),V.setUint32(8,N,!0),V.setUint32(12,J,!0),Y}var L=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],P=new Uint32Array([3614090360,3905402710,606105819,3250441966,4118548399,1200080426,2821735955,4249261313,1770035416,2336552879,4294925233,2304563134,1804603682,4254626195,2792965006,1236535329,4129170786,3225465664,643717713,3921069994,3593408605,38016083,3634488961,3889429448,568446438,3275163606,4107603335,1163531501,2850285829,4243563512,1735328473,2368359562,4294588738,2272392833,1839030562,4259657740,2763975236,1272893353,4139469664,3200236656,681279174,3936430074,3572445317,76029189,3654602809,3873151461,530742520,3299628645,4096336452,1126891415,2878612391,4237533241,1700485571,2399980690,4293915773,2240044497,1873313359,4264355552,2734768916,1309151649,4149444226,3174756917,718787259,3951481745]);function T(q,z){return X(q<<z|q>>>32-z)}function g(q){let z=q.length*8,G=(56-(q.length+1)%64+64)%64,Q=q.length+1+G+8,N=new Uint8Array(Q);N.set(q),N[q.length]=128;let J=new DataView(N.buffer);return J.setUint32(Q-8,z>>>0,!0),J.setUint32(Q-4,Math.floor(z/4294967296),!0),N}function C(q,z){let G=q;if(G.length>j)G=U(G);let Q=new Uint8Array(j);Q.set(G);let N=new Uint8Array(j),J=new Uint8Array(j);for(let $=0;$<j;$++)N[$]=(Q[$]??0)^54,J[$]=(Q[$]??0)^92;let Y=new Uint8Array(N.length+z.length);Y.set(N),Y.set(z,N.length);let V=U(Y),F=new Uint8Array(J.length+V.length);return F.set(J),F.set(V,J.length),U(F)}function E(q){return Array.from(q,(z)=>z.toString(16).padStart(2,"0")).join("")}async function M(q,z,G){let Q=K(q.trim()),N=H(G),J=C(N,Q),Y=E(J);return O(`${z} ${Y}`).replace(/\r\n/g,"")}class Z extends v{command;response;constructor(q,z,G,Q){super(q,B(z,G),{provider:"smtp"});this.command=G;this.response=Q;this.name="SMTPError",this.code=z}}function u(q){let z;switch(q.type){case"EHLO":{z=`EHLO ${q.domain.replace(/[\r\n]/g,"")}`;break}case"STARTTLS":z="STARTTLS";break;case"AUTH_LOGIN":z="AUTH LOGIN";break;case"AUTH_PLAIN":z=`AUTH PLAIN ${O(`\x00${q.user}\x00${q.pass}`).replace(/\r\n/g,"")}`;break;case"AUTH_CRAM_MD5_INIT":z="AUTH CRAM-MD5";break;case"AUTH_CRAM_MD5_RESPONSE":return H(`${q.response}\r
|
|
2
2
|
`);case"AUTH_XOAUTH2":z=`AUTH XOAUTH2 ${q.xoauth2String}`;break;case"MAIL_FROM":if(/[\r\n]/.test(q.address))throw new Z("Invalid address: contains CRLF",0,"MAIL FROM",q.address);z=`MAIL FROM:<${q.address}>`;break;case"RCPT_TO":if(/[\r\n]/.test(q.address))throw new Z("Invalid address: contains CRLF",0,"RCPT TO",q.address);z=`RCPT TO:<${q.address}>`;break;case"DATA":z="DATA";break;case"DATA_BODY":return H(S(q.content));case"QUIT":z="QUIT";break;case"RSET":z="RSET";break;case"NOOP":z="NOOP";break}return H(`${z}\r
|
|
3
3
|
`)}function l(q){let z=new TextDecoder().decode(q).trim(),G=z.split(/\r?\n/),N=(G[G.length-1]??"").match(/^(\d{3})([\s-])(.*)$/);if(!N)throw new Z("Invalid SMTP response",0,"PARSE",z);let J=Number.parseInt(N[1]??"0",10),Y=G.map((V)=>V.replace(/^\d{3}[\s-]/,"")).join(" ");return{code:J,message:Y,isSuccess:J>=200&&J<300,isReady:J>=300&&J<400,isError:J>=400}}function p(q){if(q.length===0)return null;let z=q.reduce((V,F)=>V+F.length,0),G=new Uint8Array(z),Q=0;for(let V of q)G.set(V,Q),Q+=V.length;let J=new TextDecoder().decode(G).split(/\r?\n/).filter((V)=>V.length>0);if(J.length===0)return null;let Y=J[J.length-1]??"";if(/^\d{3} /.test(Y))return G;return null}function m(q){let z=q.map((G)=>G.toUpperCase());if(z.some((G)=>G.includes("AUTH")&&G.includes("XOAUTH2")))return"OAUTH2";if(z.some((G)=>G.includes("AUTH")&&G.includes("CRAM-MD5")))return"CRAM-MD5";if(z.some((G)=>G.includes("AUTH")&&G.includes("LOGIN")))return"LOGIN";if(z.some((G)=>G.includes("AUTH")&&G.includes("PLAIN")))return"PLAIN";throw new Z("No supported AUTH method",0,"EHLO",q.join(" "))}function i(q){return q.message.split(/\s+/).flatMap((z)=>z.split(/\r?\n/)).filter(Boolean)}function r(q,z,G){if(!z.includes(q.code))throw new Z(`Unexpected SMTP response for ${G}`,q.code,G,q.message)}function S(q){return`${new TextDecoder().decode(q).split(/\r?\n/).map((N)=>N.startsWith(".")?`.${N}`:N).join(`\r
|
|
4
4
|
`)}\r
|
|
@@ -8,6 +8,6 @@ import{P as O,Q as K,S as H}from"./chunk-d5rwm3br.js";import{T as v,V as B}from"
|
|
|
8
8
|
`)}function n(q){return H(`${q}\r
|
|
9
9
|
`)}function s(q){return H(`${q}\r
|
|
10
10
|
`)}
|
|
11
|
-
export{M as
|
|
11
|
+
export{M as H,Z as I,u as J,l as K,p as L,m as M,i as N,r as O,t as P,o as Q,n as R,s as S};
|
|
12
12
|
|
|
13
|
-
//# debugId=
|
|
13
|
+
//# debugId=B24DD712C817A87F64756E2164756E21
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
"// src/core/smtp.ts\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\nimport { SentlyError, smtpCodeToSentlyCode } from \"./errors.js\";\n\nexport { computeCRAMMD5 } from \"./cram-md5.js\";\n\n/** SMTP command to send to the server. */\nexport type SMTPCommand =\n | { type: \"EHLO\"; domain: string }\n | { type: \"STARTTLS\" }\n | { type: \"AUTH_LOGIN\"; user: string; pass: string }\n | { type: \"AUTH_PLAIN\"; user: string; pass: string }\n | { type: \"AUTH_CRAM_MD5_INIT\" }\n | { type: \"AUTH_CRAM_MD5_RESPONSE\"; response: string }\n | { type: \"AUTH_XOAUTH2\"; xoauth2String: string }\n | { type: \"MAIL_FROM\"; address: string }\n | { type: \"RCPT_TO\"; address: string }\n | { type: \"DATA\" }\n | { type: \"DATA_BODY\"; content: Uint8Array }\n | { type: \"QUIT\" }\n | { type: \"RSET\" }\n | { type: \"NOOP\" };\n\n/** Parsed SMTP server response. */\nexport interface SMTPResponse {\n /** Three-digit SMTP status code. */\n code: number;\n /** Human-readable response text after the status code. */\n message: string;\n /** True when the code is in the 2xx success range. */\n isSuccess: boolean;\n /** True when the code is 354 (ready for message data). */\n isReady: boolean;\n /** True when the code is 4xx or 5xx. */\n isError: boolean;\n}\n\n/** SMTP protocol error with server response details. */\nexport class SMTPError extends SentlyError {\n /** Creates an SMTP protocol error. */\n constructor(\n message: string,\n smtpCode: number,\n public readonly command: string,\n public readonly response: string,\n ) {\n super(message, smtpCodeToSentlyCode(smtpCode, command), { provider: \"smtp\" });\n this.name = \"SMTPError\";\n this.code = smtpCode;\n }\n}\n\n/**\n * Encode an SMTPCommand into a Uint8Array for sending over the socket.\n */\nexport function encodeCommand(cmd: SMTPCommand): Uint8Array {\n let line: string;\n\n switch (cmd.type) {\n case \"EHLO\": {\n const safeDomain = cmd.domain.replace(/[\\r\\n]/g, \"\");\n line = `EHLO ${safeDomain}`;\n break;\n }\n case \"STARTTLS\":\n line = \"STARTTLS\";\n break;\n case \"AUTH_LOGIN\":\n line = \"AUTH LOGIN\";\n break;\n case \"AUTH_PLAIN\":\n line = `AUTH PLAIN ${encodeBase64(`\\0${cmd.user}\\0${cmd.pass}`).replace(/\\r\\n/g, \"\")}`;\n break;\n case \"AUTH_CRAM_MD5_INIT\":\n line = \"AUTH CRAM-MD5\";\n break;\n case \"AUTH_CRAM_MD5_RESPONSE\":\n return encodeUtf8(`${cmd.response}\\r\\n`);\n case \"AUTH_XOAUTH2\":\n line = `AUTH XOAUTH2 ${cmd.xoauth2String}`;\n break;\n case \"MAIL_FROM\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"MAIL FROM\", cmd.address);\n }\n line = `MAIL FROM:<${cmd.address}>`;\n break;\n case \"RCPT_TO\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"RCPT TO\", cmd.address);\n }\n line = `RCPT TO:<${cmd.address}>`;\n break;\n case \"DATA\":\n line = \"DATA\";\n break;\n case \"DATA_BODY\":\n return encodeUtf8(applyDotStuffing(cmd.content));\n case \"QUIT\":\n line = \"QUIT\";\n break;\n case \"RSET\":\n line = \"RSET\";\n break;\n case \"NOOP\":\n line = \"NOOP\";\n break;\n }\n\n return encodeUtf8(`${line}\\r\\n`);\n}\n\n/**\n * Parse raw bytes from the server into an SMTPResponse.\n */\nexport function parseResponse(data: Uint8Array): SMTPResponse {\n const text = new TextDecoder().decode(data).trim();\n const lines = text.split(/\\r?\\n/);\n const lastLine = lines[lines.length - 1] ?? \"\";\n const match = lastLine.match(/^(\\d{3})([\\s-])(.*)$/);\n\n if (!match) {\n throw new SMTPError(\"Invalid SMTP response\", 0, \"PARSE\", text);\n }\n\n const code = Number.parseInt(match[1] ?? \"0\", 10);\n const message = lines.map((l) => l.replace(/^\\d{3}[\\s-]/, \"\")).join(\" \");\n\n return {\n code,\n message,\n isSuccess: code >= 200 && code < 300,\n isReady: code >= 300 && code < 400,\n isError: code >= 400,\n };\n}\n\n/**\n * Accumulate byte chunks until a complete SMTP response is received.\n */\nexport function accumulateResponse(chunks: Uint8Array[]): Uint8Array | null {\n if (chunks.length === 0) {\n return null;\n }\n\n const total = chunks.reduce((sum, c) => sum + c.length, 0);\n const combined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n const text = new TextDecoder().decode(combined);\n const lines = text.split(/\\r?\\n/).filter((l) => l.length > 0);\n\n if (lines.length === 0) {\n return null;\n }\n\n const lastLine = lines[lines.length - 1] ?? \"\";\n if (/^\\d{3} /.test(lastLine)) {\n return combined;\n }\n\n return null;\n}\n\n/**\n * Select the best AUTH method from EHLO capability lines.\n * Priority: XOAUTH2 > CRAM-MD5 > LOGIN > PLAIN.\n */\nexport function selectAuthMethod(\n capabilities: string[],\n): \"LOGIN\" | \"PLAIN\" | \"CRAM-MD5\" | \"OAUTH2\" {\n const upper = capabilities.map((c) => c.toUpperCase());\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"XOAUTH2\"))) {\n return \"OAUTH2\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"CRAM-MD5\"))) {\n return \"CRAM-MD5\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"LOGIN\"))) {\n return \"LOGIN\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"PLAIN\"))) {\n return \"PLAIN\";\n }\n throw new SMTPError(\"No supported AUTH method\", 0, \"EHLO\", capabilities.join(\" \"));\n}\n\n/**\n * Parse an EHLO multi-line response and extract capability keywords.\n */\nexport function parseEHLO(response: SMTPResponse): string[] {\n return response.message\n .split(/\\s+/)\n .flatMap((part) => part.split(/\\r?\\n/))\n .filter(Boolean);\n}\n\n/**\n * Assert that an SMTPResponse code is within the expected set.\n */\nexport function assertResponse(\n response: SMTPResponse,\n expectedCodes: number[],\n command: string,\n): void {\n if (!expectedCodes.includes(response.code)) {\n throw new SMTPError(\n `Unexpected SMTP response for ${command}`,\n response.code,\n command,\n response.message,\n );\n }\n}\n\nfunction applyDotStuffing(content: Uint8Array): string {\n const text = new TextDecoder().decode(content);\n const lines = text.split(/\\r?\\n/);\n const stuffed = lines.map((line) => (line.startsWith(\".\") ? `.${line}` : line));\n return `${stuffed.join(\"\\r\\n\")}\\r\\n.\\r\\n`;\n}\n\n/** Encode AUTH LOGIN password step (second base64 chunk). */\nexport function encodeAuthLoginPass(pass: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(pass).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode AUTH LOGIN user step when sent separately after 334. */\nexport function encodeAuthLoginUser(user: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(user).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode CRAM-MD5 response after challenge. */\nexport function encodeAuthCramResponse(response: string): Uint8Array {\n return encodeUtf8(`${response}\\r\\n`);\n}\n\n/** Encode raw SMTP line with CRLF. */\nexport function encodeLine(line: string): Uint8Array {\n return encodeUtf8(`${line}\\r\\n`);\n}\n"
|
|
7
7
|
],
|
|
8
8
|
"mappings": "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",
|
|
9
|
-
"debugId": "
|
|
9
|
+
"debugId": "B24DD712C817A87F64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
function x(j){if(j.provider!==void 0&&j.provider.length>0)return j.provider;let q=j.constructor.name;if(q==="Object")return"custom";if(q.endsWith("Transport"))return q.slice(0,-9).toLowerCase();return q.toLowerCase()||"unknown"}
|
|
2
|
+
export{x as A};
|
|
3
|
+
|
|
4
|
+
//# debugId=F01F6E80D9AFEE8E64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/provider-label.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { Transport } from \"./types.js\";\n\n/**\n * Resolve a stable provider label for observability and diagnostics.\n * Prefers {@link Transport.provider} when set; falls back to constructor name.\n */\nexport function getProviderLabel(transport: Transport): string {\n if (transport.provider !== undefined && transport.provider.length > 0) {\n return transport.provider;\n }\n\n const name = transport.constructor.name;\n if (name === \"Object\") {\n return \"custom\";\n }\n if (name.endsWith(\"Transport\")) {\n return name.slice(0, -\"Transport\".length).toLowerCase();\n }\n return name.toLowerCase() || \"unknown\";\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "AAMO,SAAS,CAAgB,CAAC,EAA8B,CAC7D,GAAI,EAAU,WAAa,QAAa,EAAU,SAAS,OAAS,EAClE,OAAO,EAAU,SAGnB,IAAM,EAAO,EAAU,YAAY,KACnC,GAAI,IAAS,SACX,MAAO,SAET,GAAI,EAAK,SAAS,WAAW,EAC3B,OAAO,EAAK,MAAM,EAAG,EAAmB,EAAE,YAAY,EAExD,OAAO,EAAK,YAAY,GAAK",
|
|
8
|
+
"debugId": "F01F6E80D9AFEE8E64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"import type { MailOptions } from \"./types.js\";\n\n/**\n * Resolve the idempotency key for a message.\n * Uses explicit `idempotencyKey` or derives from `messageId` when present.\n */\nexport function resolveIdempotencyKey(options: MailOptions): string | undefined {\n if (options.idempotencyKey) {\n return options.idempotencyKey;\n }\n if (options.messageId) {\n return `msg:${options.messageId}`;\n }\n return undefined;\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "AAMO,SAAS,CAAqB,CAAC,EAA0C,CAC9E,GAAI,EAAQ,eACV,OAAO,EAAQ,eAEjB,GAAI,EAAQ,UACV,MAAO,OAAO,EAAQ,YAExB",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "8B3534058EABEDA664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{s as U}from"./chunk-wbvhaqcp.js";import{u as X}from"./chunk-zst8an2p.js";import{v as b}from"./chunk-vhxzhx8b.js";import{H as k,I as $,J as Z,K as O,L as q,M as L,N as v,O as N,P as A,Q as j,S as E}from"./chunk-8phpsns1.js";import{_ as C}from"./chunk-nbmq8ejv.js";class T{provider="smtp";config;adapter=null;constructor(z){this.config=P(z)}async send(z){let B={...z,attachments:await b(z.attachments)},G=await X(B,this.config.dkim),F=await this.getAdapter(),W=this.config.direct?await M(G.envelope.from.split("@")[1]??this.config.host):this.config.host;await F.connect(W,this.config.port),this.adapter=F;try{return await D(F,this.config),await S(F,G)}finally{await H(F),this.adapter=null}}async verify(){try{let z=await this.getAdapter();await z.connect(this.config.host,this.config.port);try{return await D(z,this.config),{ok:!0,provider:"smtp"}}finally{await H(z)}}catch(z){return{ok:!1,provider:"smtp",message:z instanceof Error?z.message:String(z)}}}async close(){if(this.adapter)await this.adapter.close(),this.adapter=null}async getAdapter(){if(!this.config.adapter)throw new $("No socket adapter configured",0,"CONNECT","");return this.config.adapter}}function P(z){let B=z.secure??!1;return{host:z.host,port:z.port??(B?465:587),secure:B,...z.auth!==void 0?{auth:z.auth}:{},...z.requireTLS!==void 0?{requireTLS:z.requireTLS}:{},...z.dkim!==void 0?{dkim:z.dkim}:{},...z.tls!==void 0?{tls:z.tls}:{},...z.connectionTimeout!==void 0?{connectionTimeout:z.connectionTimeout}:{},...z.greetingTimeout!==void 0?{greetingTimeout:z.greetingTimeout}:{},...z.socketTimeout!==void 0?{socketTimeout:z.socketTimeout}:{},...z.direct!==void 0?{direct:z.direct}:{},...z.adapter!==void 0?{adapter:z.adapter}:{}}}async function D(z,B){let G=await J(z);N(G,[220],"greeting");let F=await I(z,B.host),W=F.some((Q)=>Q.toUpperCase()==="STARTTLS");if(!B.secure&&!z.secure&&W){await Y(z,Z({type:"STARTTLS"}));let Q=await J(z);N(Q,[220],"STARTTLS"),await z.startTLS(B.tls),F=await I(z,B.host)}if(B.auth){let Q=B.requireTLS??!0,V=z.secure||B.secure;if(Q&&!V)throw new $("Refusing to authenticate over unencrypted connection. Set requireTLS: false to disable this check (not recommended).",0,"AUTH","");await l(z,B.auth,F)}}async function S(z,B){await _(z,{type:"MAIL_FROM",address:B.envelope.from});let G=await J(z);N(G,[250],"MAIL FROM");let F=[],W=[];for(let K of B.envelope.to)if(await Y(z,Z({type:"RCPT_TO",address:K})),(await J(z)).isSuccess)F.push(K);else W.push(K);await _(z,{type:"DATA"});let Q=await J(z);N(Q,[354],"DATA");let V;try{await Y(z,Z({type:"DATA_BODY",content:B.raw})),V=await J(z)}catch(K){if(await Y(z,Z({type:"DATA_BODY",content:B.raw})),V=await J(z),V.isError)throw K}return N(V,[250],"DATA end"),{messageId:B.messageId,accepted:F,rejected:W,response:V.message,envelope:B.envelope}}async function H(z){try{await _(z,{type:"QUIT"}),await J(z)}catch{}finally{await z.close()}}async function I(z,B){await _(z,{type:"EHLO",domain:B});let G=await J(z);return N(G,[250],"EHLO"),v(G)}async function l(z,B,G){if(B.type==="OAUTH2"&&B.oauth2){let K=await new U(B.oauth2).buildXOAUTH2();await _(z,{type:"AUTH_XOAUTH2",xoauth2String:K});let x=await J(z);if(x.code===334)await Y(z,E("")),x=await J(z);N(x,[235],"AUTH XOAUTH2");return}let F=B.type??L(G);if(F==="CRAM-MD5"){let V=y(B,"CRAM-MD5");await _(z,{type:"AUTH_CRAM_MD5_INIT"});let K=await J(z);N(K,[334],"AUTH CRAM-MD5");let x=K.message.trim(),w=await k(x,B.user,V);await _(z,{type:"AUTH_CRAM_MD5_RESPONSE",response:w}),K=await J(z),N(K,[235],"AUTH CRAM-MD5 response");return}if(F==="PLAIN"){let V=y(B,"PLAIN");await Y(z,Z({type:"AUTH_PLAIN",user:B.user,pass:V}));let K=await J(z);N(K,[235],"AUTH PLAIN");return}let W=y(B,"LOGIN");await Y(z,Z({type:"AUTH_LOGIN",user:B.user,pass:W}));let Q=await J(z);N(Q,[334],"AUTH LOGIN"),await Y(z,j(B.user)),Q=await J(z),N(Q,[334],"AUTH LOGIN user"),await Y(z,A(W)),Q=await J(z),N(Q,[235],"AUTH LOGIN pass")}function y(z,B){if(!z.pass)throw new $(`Password required for ${B} authentication`,0,`AUTH ${B}`,"");return z.pass}async function _(z,B){await Y(z,Z(B))}async function Y(z,B){await z.write(B)}async function J(z){let B=[];for await(let G of z.read()){B.push(G);let F=q(B);if(F)return O(F)}throw new $("Connection closed while reading SMTP response",0,"READ","")}async function M(z){let G=await(await import("node:dns/promises")).resolveMx(z);if(G.length===0)throw new $(`No MX records for ${z}`,0,"MX","");return G.sort((F,W)=>F.priority-W.priority),G[0]?.exchange??z}export{T as k,P as l,D as m,S as n,H as o,J as p};
|
|
2
|
+
|
|
3
|
+
//# debugId=27963FE5C2BAA8F464756E2164756E21
|