sently 0.10.0 → 1.0.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/AGENTS.md +1 -1
- package/CHANGELOG.md +42 -2
- package/README.md +21 -18
- package/SECURITY.md +59 -0
- package/dist/chunk-z1589fjk.js.map +1 -1
- package/dist/core/push-types.d.ts +1 -1
- package/dist/transports/mailpit.d.ts +166 -0
- package/dist/transports/mailpit.js +3 -0
- package/dist/transports/mailpit.js.map +10 -0
- package/package.json +15 -2
- package/site/content/docs/ai/llms-txt.mdx +47 -2
- package/site/content/docs/channels/email.mdx +3 -0
- package/site/content/docs/channels/index.mdx +3 -1
- package/site/content/docs/channels/push.mdx +4 -0
- package/site/content/docs/decorators/fallback.mdx +5 -0
- package/site/content/docs/decorators/index.mdx +2 -1
- package/site/content/docs/decorators/preview.mdx +52 -9
- package/site/content/docs/get-started/entrypoints.mdx +5 -3
- package/site/content/docs/get-started/index.mdx +8 -2
- package/site/content/docs/get-started/introduction.mdx +7 -3
- package/site/content/docs/get-started/meta.json +4 -1
- package/site/content/docs/get-started/migrate-nodemailer.mdx +132 -5
- package/site/content/docs/get-started/non-goals.mdx +57 -0
- package/site/content/docs/get-started/stability.mdx +69 -0
- package/site/content/docs/get-started/support-matrix.mdx +62 -0
- package/site/content/docs/guides/compare.mdx +87 -0
- package/site/content/docs/guides/failover.mdx +121 -0
- package/site/content/docs/guides/index.mdx +2 -0
- package/site/content/docs/guides/meta.json +2 -0
- package/site/content/docs/guides/security.mdx +38 -8
- package/site/content/docs/index.mdx +78 -4
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/exports.mdx +5 -2
- package/site/content/docs/reference/push-options.mdx +14 -0
- package/site/content/docs/transports/fcm.mdx +8 -4
- package/site/content/docs/transports/index.mdx +9 -3
- package/site/content/docs/transports/mailpit.mdx +123 -0
- package/site/content/docs/transports/meta.json +1 -0
- package/site/content/docs/transports/smtp.mdx +61 -22
- package/site/content/docs/quick-start/email.mdx +0 -40
- package/site/content/docs/quick-start/index.mdx +0 -15
- package/site/content/docs/quick-start/meta.json +0 -5
- package/site/content/docs/quick-start/push.mdx +0 -53
- package/site/content/docs/quick-start/sms.mdx +0 -36
- package/site/content/docs/quick-start/whatsapp.mdx +0 -38
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Failover
|
|
3
|
+
description: Retry inside a provider, then fail over across providers on any channel.
|
|
4
|
+
icon: GitBranch
|
|
5
|
+
source: "src/transports/fallback.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
When a provider blips, retry that transport first, then fail over to the next.
|
|
9
|
+
The same decorator pattern works for email, SMS, WhatsApp, and push.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Wrap each provider in `RetryTransport`, then pass the list to `FallbackTransport`. Use the channel sender that matches the transport contract.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
<Steps>
|
|
18
|
+
<Step title="Email — Resend then SES">
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createMailer } from "sently/mailer";
|
|
22
|
+
import { FallbackTransport } from "sently/transports/fallback";
|
|
23
|
+
import { RetryTransport } from "sently/transports/retry";
|
|
24
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
25
|
+
import { SESTransport } from "sently/transports/ses";
|
|
26
|
+
|
|
27
|
+
const mailer = await createMailer({
|
|
28
|
+
transport: new FallbackTransport(
|
|
29
|
+
[
|
|
30
|
+
new RetryTransport(new ResendTransport({ apiKey: process.env.RESEND_API_KEY! })),
|
|
31
|
+
new RetryTransport(
|
|
32
|
+
new SESTransport({
|
|
33
|
+
region: process.env.AWS_REGION!,
|
|
34
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
35
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
{ cooldownMs: 300_000 },
|
|
40
|
+
),
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
</Step>
|
|
45
|
+
<Step title="SMS — Twilio then Unifonic">
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createSmsSender } from "sently/sms";
|
|
49
|
+
import { FallbackTransport } from "sently/transports/fallback";
|
|
50
|
+
import { RetryTransport } from "sently/transports/retry";
|
|
51
|
+
import { TwilioSmsTransport } from "sently/transports/twilio-sms";
|
|
52
|
+
import { UnifonicTransport } from "sently/transports/unifonic";
|
|
53
|
+
|
|
54
|
+
const sms = createSmsSender({
|
|
55
|
+
transport: new FallbackTransport(
|
|
56
|
+
[
|
|
57
|
+
new RetryTransport(
|
|
58
|
+
new TwilioSmsTransport({
|
|
59
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID!,
|
|
60
|
+
authToken: process.env.TWILIO_AUTH_TOKEN!,
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
new RetryTransport(
|
|
64
|
+
new UnifonicTransport({
|
|
65
|
+
appSid: process.env.UNIFONIC_APPSID!,
|
|
66
|
+
senderId: "MyBrand",
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
{ cooldownMs: 300_000 },
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
</Step>
|
|
76
|
+
<Step title="Inspect which provider won">
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const result = await sms.send({
|
|
80
|
+
to: "+15551234567",
|
|
81
|
+
body: "Hello",
|
|
82
|
+
from: "+15557654321",
|
|
83
|
+
});
|
|
84
|
+
console.log(result.provider, result.providerIndex);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
</Step>
|
|
88
|
+
</Steps>
|
|
89
|
+
|
|
90
|
+
## Behavior
|
|
91
|
+
|
|
92
|
+
| Behavior | Default |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| Fail over on 5xx / network | Yes |
|
|
95
|
+
| Fail over on 400 / 401 / 403 | No (same client error on every provider) |
|
|
96
|
+
| `cooldownMs` | Skip a failed provider until the cooldown ends |
|
|
97
|
+
| Hooks | `onRetry` / `onFallback` on the channel sender when wired |
|
|
98
|
+
|
|
99
|
+
## Troubleshooting
|
|
100
|
+
|
|
101
|
+
<Accordions>
|
|
102
|
+
<Accordion title="Why did both providers reject the same message?">
|
|
103
|
+
Permanent client errors do not fail over by default. Fix the payload or credentials before expecting a second provider to succeed.
|
|
104
|
+
</Accordion>
|
|
105
|
+
<Accordion title="Can I mix email and SMS in one FallbackTransport?">
|
|
106
|
+
No. Every entry must implement the same channel contract as the sender.
|
|
107
|
+
</Accordion>
|
|
108
|
+
</Accordions>
|
|
109
|
+
|
|
110
|
+
## Learn more
|
|
111
|
+
|
|
112
|
+
- [Retry](/docs/decorators/retry)
|
|
113
|
+
- [Fallback](/docs/decorators/fallback)
|
|
114
|
+
- [Support matrix](/docs/get-started/support-matrix)
|
|
115
|
+
|
|
116
|
+
## Next
|
|
117
|
+
|
|
118
|
+
<Cards>
|
|
119
|
+
<Card title="Fallback decorator" href="/docs/decorators/fallback" />
|
|
120
|
+
<Card title="Retry decorator" href="/docs/decorators/retry" />
|
|
121
|
+
</Cards>
|
|
@@ -8,6 +8,8 @@ source: "README.md"
|
|
|
8
8
|
Configure sently features around your delivery workflow.
|
|
9
9
|
|
|
10
10
|
<Cards>
|
|
11
|
+
<Card title="Compare" href="/docs/guides/compare" />
|
|
12
|
+
<Card title="Failover" href="/docs/guides/failover" />
|
|
11
13
|
<Card title="Adapters" href="/docs/guides/adapters" />
|
|
12
14
|
<Card title="Webhooks" href="/docs/guides/webhooks" />
|
|
13
15
|
<Card title="Security" href="/docs/guides/security" />
|
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Security
|
|
3
|
-
description: Protect credentials, delivery endpoints, and
|
|
3
|
+
description: Protect credentials, delivery endpoints, webhook integrity, and publish trust.
|
|
4
4
|
icon: ShieldCheck
|
|
5
5
|
source: "src/transports/webpush.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Provider API keys, VAPID private keys, and push endpoints are long-lived credentials.
|
|
9
|
+
Keep them out of source control and out of unstructured logs.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- Treat a push endpoint as a delivery token.
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Keep provider keys and VAPID private keys in a secrets manager or environment variables.
|
|
13
|
+
</Callout>
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Checklist
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
| Practice | Detail |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| SMTP TLS | `requireTLS` defaults to true when auth is set |
|
|
20
|
+
| Webhooks | Verify signatures before trusting parsed events |
|
|
21
|
+
| Logging | Do not log email/SMS bodies or full push endpoints |
|
|
22
|
+
| Push | Treat subscription endpoints and FCM tokens as delivery tokens |
|
|
23
|
+
| Reporting | Use private advisories — see root `SECURITY.md` |
|
|
24
|
+
|
|
25
|
+
## Troubleshooting
|
|
26
|
+
|
|
27
|
+
<Accordions>
|
|
28
|
+
<Accordion title="Why is a push endpoint redacted in hooks?">
|
|
29
|
+
Its path can contain a long-lived delivery token. Hook context keeps a short fingerprint.
|
|
30
|
+
</Accordion>
|
|
31
|
+
<Accordion title="Where do I report a vulnerability?">
|
|
32
|
+
Open a private GitHub Security Advisory for this repository. Do not file a public issue. Details and response targets are in [`SECURITY.md`](https://github.com/alialnaghmoush/sently/blob/main/SECURITY.md).
|
|
33
|
+
</Accordion>
|
|
34
|
+
</Accordions>
|
|
35
|
+
|
|
36
|
+
## Learn more
|
|
37
|
+
|
|
38
|
+
- [Stability policy](/docs/get-started/stability)
|
|
39
|
+
- [Webhooks](/docs/guides/webhooks)
|
|
40
|
+
- [Web Push](/docs/transports/webpush)
|
|
41
|
+
|
|
42
|
+
## Next
|
|
43
|
+
|
|
44
|
+
<Cards>
|
|
45
|
+
<Card title="Webhooks" href="/docs/guides/webhooks" />
|
|
46
|
+
<Card title="Stability policy" href="/docs/get-started/stability" />
|
|
47
|
+
</Cards>
|
|
@@ -1,15 +1,89 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: sently handbook
|
|
3
|
-
description: Channel-
|
|
3
|
+
description: Channel-delivery for email, SMS, WhatsApp, and push — one sender shape as channels grow.
|
|
4
4
|
icon: BookOpen
|
|
5
5
|
source: "README.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
sently is the channel-delivery layer for the order confirmation email, the OTP SMS, and the device push that follow.
|
|
9
|
+
Apps call a channel sender; providers live in transports you can swap without rewriting send sites.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Use `createMailer`, `createSmsSender`, `createWhatsAppSender`, or `createPushSender` in app code — never a vendor SDK as your application API.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
<Steps>
|
|
18
|
+
<Step title="Install">
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun add sently
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
</Step>
|
|
25
|
+
<Step title="Send with a channel sender">
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createMailer } from "sently/mailer";
|
|
29
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
30
|
+
|
|
31
|
+
const mailer = await createMailer({
|
|
32
|
+
transport: new ResendTransport({
|
|
33
|
+
apiKey: process.env.RESEND_API_KEY!,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const result = await mailer.send({
|
|
38
|
+
from: "hello@example.com",
|
|
39
|
+
to: "person@example.com",
|
|
40
|
+
subject: "Welcome",
|
|
41
|
+
text: "Thanks for joining.",
|
|
42
|
+
});
|
|
43
|
+
console.log(result.messageId);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
</Step>
|
|
47
|
+
</Steps>
|
|
48
|
+
|
|
49
|
+
## Channels
|
|
50
|
+
|
|
51
|
+
| Channel | Sender | Start here |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Email | `createMailer` / `createSMTPMailer` | [Email](/docs/channels/email) |
|
|
54
|
+
| SMS | `createSmsSender` | [SMS](/docs/channels/sms) |
|
|
55
|
+
| WhatsApp | `createWhatsAppSender` | [WhatsApp](/docs/channels/whatsapp) |
|
|
56
|
+
| Push | `createPushSender` | [Push](/docs/channels/push) |
|
|
57
|
+
|
|
58
|
+
Email factories are asynchronous. SMS, WhatsApp, and push factories return the sender synchronously; every `send` is async.
|
|
59
|
+
|
|
60
|
+
## Where next
|
|
61
|
+
|
|
62
|
+
| Path | When |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| [Get started](/docs/get-started) | Install, entrypoints, runtimes, migrate from Nodemailer |
|
|
65
|
+
| [Channels](/docs/channels) | Full sender docs + lifecycle hooks |
|
|
66
|
+
| [Transports](/docs/transports) | Provider list by channel |
|
|
67
|
+
| [Failover](/docs/guides/failover) | Retry inside a provider, then fail over |
|
|
68
|
+
| [Support matrix](/docs/get-started/support-matrix) | Supported vs Available for production |
|
|
69
|
+
| [Compare](/docs/guides/compare) | vs vendor SDKs, Nodemailer, Novu / Knock / Courier |
|
|
70
|
+
| [Stability](/docs/get-started/stability) | What is frozen at 1.x |
|
|
71
|
+
|
|
72
|
+
## Troubleshooting
|
|
73
|
+
|
|
74
|
+
<Accordions>
|
|
75
|
+
<Accordion title="Is sently a notification platform like Novu or Knock?">
|
|
76
|
+
No. It is the delivery layer. Preference centers, digests, workflows, and in-app inboxes sit on top — custom code or those tools. See [Compare](/docs/guides/compare).
|
|
77
|
+
</Accordion>
|
|
78
|
+
<Accordion title="Where should I send first?">
|
|
79
|
+
Start with [Email](/docs/channels/email) or [Installation](/docs/get-started/installation), then open the channel page for SMS, WhatsApp, or push when you add that channel.
|
|
80
|
+
</Accordion>
|
|
81
|
+
</Accordions>
|
|
82
|
+
|
|
83
|
+
## Next
|
|
9
84
|
|
|
10
85
|
<Cards>
|
|
11
86
|
<Card title="Get started" href="/docs/get-started" />
|
|
12
|
-
<Card title="Quick start" href="/docs/quick-start" />
|
|
13
87
|
<Card title="Channels" href="/docs/channels" />
|
|
14
|
-
<Card title="
|
|
88
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
15
89
|
</Cards>
|
|
@@ -37,7 +37,7 @@ The main `sently` package is for shared types and factories — not every provid
|
|
|
37
37
|
|
|
38
38
|
| Import | Use |
|
|
39
39
|
| ------ | --- |
|
|
40
|
-
| `sently/transports/<name>` | One provider or decorator (e.g. `sndr`, `
|
|
40
|
+
| `sently/transports/<name>` | One provider or decorator (e.g. `sndr`, `mailpit`, `fcm`, `retry`) |
|
|
41
41
|
|
|
42
42
|
HTTP providers such as SNDR, Resend, and Plunk are **not** re-exported from `sently`.
|
|
43
43
|
|
|
@@ -75,11 +75,14 @@ Provider transports live on `sently/transports/*` so bare Node/Deno imports of `
|
|
|
75
75
|
## Learn more
|
|
76
76
|
|
|
77
77
|
- [Entrypoints](/docs/get-started/entrypoints) — which import to pick first
|
|
78
|
-
- [
|
|
78
|
+
- [Stability policy](/docs/get-started/stability) — frozen entrypoints at 1.x
|
|
79
|
+
- [Support matrix](/docs/get-started/support-matrix) — Supported vs Available
|
|
80
|
+
- [Bundle size](/docs/guides/bundle-size) — measured stacks (supporting for Workers)
|
|
79
81
|
|
|
80
82
|
## Next
|
|
81
83
|
|
|
82
84
|
<Cards>
|
|
83
85
|
<Card title="Entrypoints" description="Choose a channel import path." href="/docs/get-started/entrypoints" />
|
|
86
|
+
<Card title="Support matrix" description="Production support promise." href="/docs/get-started/support-matrix" />
|
|
84
87
|
<Card title="Transports" description="Provider list by channel." href="/docs/transports" />
|
|
85
88
|
</Cards>
|
|
@@ -52,6 +52,20 @@ FCM stringifies non-string `data` values before send.
|
|
|
52
52
|
|
|
53
53
|
Map any channel result with [channel send result](./channel-result).
|
|
54
54
|
|
|
55
|
+
## Troubleshooting
|
|
56
|
+
|
|
57
|
+
<Accordions>
|
|
58
|
+
<Accordion title="Can I send both subscription and token?">
|
|
59
|
+
No. Each call is either Web Push (`subscription`) or FCM (`token`). Match the shape to the transport.
|
|
60
|
+
</Accordion>
|
|
61
|
+
</Accordions>
|
|
62
|
+
|
|
63
|
+
## Learn more
|
|
64
|
+
|
|
65
|
+
- [Push channel](/docs/channels/push)
|
|
66
|
+
- [Stability policy](/docs/get-started/stability) — union shape is frozen at 1.x
|
|
67
|
+
- [Channel send result](./channel-result)
|
|
68
|
+
|
|
55
69
|
## Next
|
|
56
70
|
|
|
57
71
|
<Cards>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: FCM
|
|
3
|
-
description: Send mobile push through Firebase Cloud Messaging HTTP
|
|
3
|
+
description: Send mobile push through Firebase Cloud Messaging (current HTTP API).
|
|
4
4
|
icon: Bell
|
|
5
5
|
source: "src/transports/fcm.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Send device-token push through Firebase Cloud Messaging
|
|
8
|
+
Send device-token push through Firebase Cloud Messaging for native Android and iOS clients.
|
|
9
|
+
The transport uses the current FCM HTTP API with a service-account JWT — no Google SDK.
|
|
9
10
|
Use [Web Push](./webpush) when you have a browser Push API subscription instead.
|
|
10
11
|
|
|
11
12
|
<Callout title="The one rule">Pass `token` in push options — not a Web Push `subscription`.</Callout>
|
|
@@ -29,15 +30,18 @@ const push = createPushSender({
|
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
</Step>
|
|
32
|
-
<Step title="
|
|
33
|
+
<Step title="Verify credentials, then send">
|
|
33
34
|
|
|
34
35
|
```ts
|
|
36
|
+
const check = await push.verify();
|
|
37
|
+
console.log(check.ok, check.message);
|
|
38
|
+
|
|
35
39
|
const result = await push.send({
|
|
36
40
|
token: deviceRegistrationToken,
|
|
37
41
|
title: "Hello",
|
|
38
42
|
body: "World",
|
|
39
43
|
});
|
|
40
|
-
console.log(result.messageId, result.status);
|
|
44
|
+
console.log(result.messageId, result.status, result.provider);
|
|
41
45
|
```
|
|
42
46
|
|
|
43
47
|
</Step>
|
|
@@ -56,6 +56,7 @@ Every import below is an exported package subpath.
|
|
|
56
56
|
| [Plunk](./plunk) | Email | `sently/transports/plunk` |
|
|
57
57
|
| [SparkPost](./sparkpost) | Email | `sently/transports/sparkpost` |
|
|
58
58
|
| [Mailtrap](./mailtrap) | Email | `sently/transports/mailtrap` |
|
|
59
|
+
| [Mailpit](./mailpit) | Email (dev) | `sently/transports/mailpit` |
|
|
59
60
|
| [Loops](./loops) | Email | `sently/transports/loops` |
|
|
60
61
|
| [Cloudflare Email](./cloudflare-email) | Email | `sently/transports/cloudflare-email` |
|
|
61
62
|
| [SNDR](./sndr) | Email | `sently/transports/sndr` |
|
|
@@ -87,13 +88,18 @@ For retry, fallback, preview, and idempotency wrappers, see [Decorators](../deco
|
|
|
87
88
|
No. A transport implements one channel contract. Choose a transport listed for the channel sender you use.
|
|
88
89
|
</Accordion>
|
|
89
90
|
<Accordion title="How do I add retries or fallback?">
|
|
90
|
-
Wrap any channel transport with a [decorator](../decorators), then pass the wrapper to the matching sender.
|
|
91
|
+
Wrap any channel transport with a [decorator](../decorators), then pass the wrapper to the matching sender. See also the [failover guide](../guides/failover).
|
|
92
|
+
</Accordion>
|
|
93
|
+
<Accordion title="Is every exported transport equally Supported?">
|
|
94
|
+
No. See the [support matrix](../get-started/support-matrix) for Supported vs Available.
|
|
91
95
|
</Accordion>
|
|
92
96
|
</Accordions>
|
|
93
97
|
|
|
94
98
|
## Learn more
|
|
95
99
|
|
|
100
|
+
- [Support matrix](../get-started/support-matrix)
|
|
96
101
|
- [Decorators](../decorators)
|
|
102
|
+
- [Failover](../guides/failover)
|
|
97
103
|
- [Email channel](../channels/email)
|
|
98
104
|
- [SMS channel](../channels/sms)
|
|
99
105
|
- [Transport contracts](../reference/transport-contracts)
|
|
@@ -101,7 +107,7 @@ For retry, fallback, preview, and idempotency wrappers, see [Decorators](../deco
|
|
|
101
107
|
## Next
|
|
102
108
|
|
|
103
109
|
<Cards>
|
|
110
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
104
111
|
<Card title="Decorators" href="/docs/decorators" />
|
|
105
|
-
<Card title="
|
|
106
|
-
<Card title="SMS channel" href="/docs/channels/sms" />
|
|
112
|
+
<Card title="Failover" href="/docs/guides/failover" />
|
|
107
113
|
</Cards>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mailpit
|
|
3
|
+
description: Catch outbound email in a local Mailpit instance during development.
|
|
4
|
+
icon: Inbox
|
|
5
|
+
source: "src/transports/mailpit.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Catch outbound email in a local [Mailpit](https://github.com/axllent/mailpit) instance while you develop.
|
|
9
|
+
Use it for the welcome email or password-reset flow before you point at a production provider.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Use Mailpit only in development — swap to a production transport before you deploy.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
Start Mailpit (SMTP `1025`, UI `8025`):
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
docker run -d --rm -p 1025:1025 -p 8025:8025 axllent/mailpit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
<Steps>
|
|
24
|
+
<Step title="Create the transport">
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createMailer } from "sently/mailer";
|
|
28
|
+
import { MailpitTransport } from "sently/transports/mailpit";
|
|
29
|
+
|
|
30
|
+
const mailpit = new MailpitTransport();
|
|
31
|
+
const mailer = await createMailer({ transport: mailpit });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
</Step>
|
|
35
|
+
<Step title="Send through the mailer">
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
await mailer.send({
|
|
39
|
+
from: "dev@example.com",
|
|
40
|
+
to: "you@example.com",
|
|
41
|
+
subject: "Hello",
|
|
42
|
+
text: "Captured by Mailpit",
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
</Step>
|
|
47
|
+
<Step title="Inspect the inbox">
|
|
48
|
+
|
|
49
|
+
Open `http://localhost:8025`, or list messages from code:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const inbox = await mailpit.messages();
|
|
53
|
+
console.log(inbox.messages[0]?.Subject);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
</Step>
|
|
57
|
+
</Steps>
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
| Option | Type | Default | Meaning |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `host` | `string` | `"localhost"` | SMTP hostname |
|
|
64
|
+
| `port` | `number` | `1025` | SMTP port |
|
|
65
|
+
| `secure` | `boolean` | `false` | Implicit TLS on connect |
|
|
66
|
+
| `requireTLS` | `boolean` | `false` | Refuse AUTH without TLS |
|
|
67
|
+
| `auth` | `SMTPAuth` | — | Optional SMTP credentials |
|
|
68
|
+
| `tls` | `TLSOptions` | — | TLS options when TLS is enabled |
|
|
69
|
+
| `connectionTimeout` | `number` | — | Socket connect timeout (ms) |
|
|
70
|
+
| `adapter` | `SocketAdapter` | auto-detected | Runtime TCP adapter |
|
|
71
|
+
| `apiUrl` | `string` | `"http://localhost:8025"` | Web UI / REST API base |
|
|
72
|
+
| `apiAuth` | `{ user, pass }` | — | Basic auth for the UI/API |
|
|
73
|
+
|
|
74
|
+
`provider` is `"mailpit"`. `verify()` checks SMTP; `close()` closes the socket adapter.
|
|
75
|
+
|
|
76
|
+
## REST helpers
|
|
77
|
+
|
|
78
|
+
Vendor extras stay on the transport instance (not on `createMailer`):
|
|
79
|
+
|
|
80
|
+
| Method | Mailpit API | Meaning |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `messages({ limit?, start? })` | `GET /api/v1/messages` | List captured messages (newest first) |
|
|
83
|
+
| `getMessage(id)` | `GET /api/v1/message/{id}` | Full message body |
|
|
84
|
+
| `deleteMessages(ids)` | `DELETE /api/v1/messages` | Delete by id |
|
|
85
|
+
| `deleteAll()` | `DELETE` with empty `IDs` | Clear the inbox |
|
|
86
|
+
| `webUrl` | — | UI base URL (same as `apiUrl`) |
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
await mailpit.deleteAll();
|
|
90
|
+
await mailer.send({ from: "a@test.com", to: "b@test.com", subject: "T", text: "ok" });
|
|
91
|
+
const list = await mailpit.messages({ limit: 1 });
|
|
92
|
+
const full = await mailpit.getMessage(list.messages[0]!.ID);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
REST failures throw `MailpitError` (`provider: "mailpit"`). Empty `getMessage("")` throws with status `400`.
|
|
96
|
+
|
|
97
|
+
## Troubleshooting
|
|
98
|
+
|
|
99
|
+
<Accordions>
|
|
100
|
+
<Accordion title="Connection refused on port 1025">
|
|
101
|
+
Mailpit is not running, or the SMTP port is remapped. Start the container above, or set `host` / `port` to match your install.
|
|
102
|
+
</Accordion>
|
|
103
|
+
<Accordion title="API helpers fail but send works">
|
|
104
|
+
SMTP and the UI/API can bind to different hosts. Set `apiUrl` (and `apiAuth` if the UI requires Basic auth).
|
|
105
|
+
</Accordion>
|
|
106
|
+
<Accordion title="Should I use createSMTPMailer instead?">
|
|
107
|
+
Yes, if you only need SMTP. `MailpitTransport` adds local defaults and REST helpers for tests and inspection.
|
|
108
|
+
</Accordion>
|
|
109
|
+
</Accordions>
|
|
110
|
+
|
|
111
|
+
## Learn more
|
|
112
|
+
|
|
113
|
+
- [SMTP](./smtp) — generic SMTP when you are not on Mailpit
|
|
114
|
+
- [Preview](/docs/decorators/preview) — write `.eml` files to disk instead
|
|
115
|
+
- [Email channel](/docs/channels/email) — `createMailer` contract
|
|
116
|
+
|
|
117
|
+
## Next
|
|
118
|
+
|
|
119
|
+
<Cards>
|
|
120
|
+
<Card title="Email channel" href="/docs/channels/email" />
|
|
121
|
+
<Card title="SMTP" href="/docs/transports/smtp" />
|
|
122
|
+
<Card title="Transports" href="/docs/transports" />
|
|
123
|
+
</Cards>
|
|
@@ -1,47 +1,86 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: SMTP
|
|
3
|
-
description: Connect to an SMTP relay with
|
|
3
|
+
description: Connect to an SMTP relay with host, port, and auth.
|
|
4
4
|
icon: Truck
|
|
5
5
|
source: "src/transports/smtp.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Connect to an SMTP relay
|
|
8
|
+
Connect to an SMTP relay for production or a local catcher.
|
|
9
|
+
Prefer `createSMTPMailer` when your config starts with host and port — it picks a runtime adapter for you.
|
|
9
10
|
|
|
10
|
-
<Callout title="The one rule">
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Use `createSMTPMailer` for host/port config; use `createMailer` only with an explicit `SMTPTransport` that already has an adapter.
|
|
13
|
+
</Callout>
|
|
11
14
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
| Option | Type | Default or requirement |
|
|
15
|
-
| --- | --- | --- |
|
|
16
|
-
| `host` | `string` | required |
|
|
17
|
-
| `port` | `number` | 587 or 465 based on secure |
|
|
18
|
-
| `secure` | `boolean` | false |
|
|
19
|
-
| `auth` | `SMTPAuth` | optional |
|
|
20
|
-
| `requireTLS` | `boolean` | true when auth is set |
|
|
21
|
-
| `adapter` | `SocketAdapter` | optional |
|
|
22
|
-
| `pool` | `boolean` | false |
|
|
23
|
-
| `dkim` | `DKIMConfig` | optional |
|
|
15
|
+
## Quick start
|
|
24
16
|
|
|
25
17
|
<Steps>
|
|
26
|
-
<Step title="
|
|
18
|
+
<Step title="Create an SMTP mailer">
|
|
27
19
|
|
|
28
20
|
```ts
|
|
29
|
-
import {
|
|
30
|
-
import { SMTPTransport } from "sently/transports/smtp";
|
|
21
|
+
import { createSMTPMailer } from "sently/smtp";
|
|
31
22
|
|
|
32
|
-
const
|
|
23
|
+
const mailer = await createSMTPMailer({
|
|
24
|
+
host: "smtp.example.com",
|
|
25
|
+
port: 587,
|
|
26
|
+
auth: { user: "you@example.com", pass: process.env.SMTP_PASSWORD! },
|
|
27
|
+
});
|
|
33
28
|
```
|
|
34
29
|
|
|
35
30
|
</Step>
|
|
36
31
|
<Step title="Send with the channel API">
|
|
37
32
|
|
|
38
33
|
```ts
|
|
39
|
-
await
|
|
34
|
+
await mailer.send({
|
|
35
|
+
from: "hello@example.com",
|
|
36
|
+
to: "person@example.com",
|
|
37
|
+
subject: "Hello",
|
|
38
|
+
text: "Hi",
|
|
39
|
+
});
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
</Step>
|
|
43
43
|
</Steps>
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Option | Type | Default or requirement |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `host` | `string` | required |
|
|
50
|
+
| `port` | `number` | `587`, or `465` when `secure` |
|
|
51
|
+
| `secure` | `boolean` | `false` |
|
|
52
|
+
| `auth` | `SMTPAuth` | optional |
|
|
53
|
+
| `requireTLS` | `boolean` | `true` when `auth` is set |
|
|
54
|
+
| `adapter` | `SocketAdapter` | auto via `createSMTPMailer` |
|
|
55
|
+
| `pool` | `boolean` | `false` |
|
|
56
|
+
| `dkim` | `DKIMConfig` | optional |
|
|
57
|
+
|
|
58
|
+
For local capture with defaults and REST helpers, use [Mailpit](./mailpit) instead of hand-wiring `localhost:1025`.
|
|
59
|
+
|
|
60
|
+
## Troubleshooting
|
|
61
|
+
|
|
62
|
+
<Accordions>
|
|
63
|
+
<Accordion title="Should I call the provider SDK?">
|
|
64
|
+
No. Use the sently sender; provider-specific extras stay on the transport instance.
|
|
65
|
+
</Accordion>
|
|
66
|
+
<Accordion title="createMailer rejects host and port">
|
|
67
|
+
Pass an explicit transport, or use `createSMTPMailer` for relay configuration.
|
|
68
|
+
</Accordion>
|
|
69
|
+
<Accordion title="Local development without a real relay">
|
|
70
|
+
Use [Mailpit](./mailpit) (`sently/transports/mailpit`) or [Preview](/docs/decorators/preview).
|
|
71
|
+
</Accordion>
|
|
72
|
+
</Accordions>
|
|
73
|
+
|
|
74
|
+
## Learn more
|
|
75
|
+
|
|
76
|
+
- [Mailpit](./mailpit) — local SMTP catcher with inbox API
|
|
77
|
+
- [Email channel](/docs/channels/email) — `createMailer` / `createSMTPMailer`
|
|
78
|
+
- [Runtimes](/docs/get-started/runtimes) — socket adapters
|
|
79
|
+
|
|
80
|
+
## Next
|
|
46
81
|
|
|
47
|
-
<Cards
|
|
82
|
+
<Cards>
|
|
83
|
+
<Card title="Mailpit" href="/docs/transports/mailpit" />
|
|
84
|
+
<Card title="Email channel" href="/docs/channels/email" />
|
|
85
|
+
<Card title="Transports" href="/docs/transports" />
|
|
86
|
+
</Cards>
|