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
|
@@ -55,13 +55,14 @@ const sms = createSmsSender({
|
|
|
55
55
|
|
|
56
56
|
## Learn more
|
|
57
57
|
|
|
58
|
+
- [Failover recipe](../guides/failover) — retry inside a provider, then fail over
|
|
58
59
|
- [Transports](../transports)
|
|
59
60
|
- [Hooks](../channels/hooks)
|
|
60
61
|
|
|
61
62
|
## Next
|
|
62
63
|
|
|
63
64
|
<Cards>
|
|
65
|
+
<Card title="Failover guide" href="/docs/guides/failover" />
|
|
64
66
|
<Card title="Retry" href="/docs/decorators/retry" />
|
|
65
67
|
<Card title="Fallback" href="/docs/decorators/fallback" />
|
|
66
|
-
<Card title="Transports" href="/docs/transports" />
|
|
67
68
|
</Cards>
|
|
@@ -6,29 +6,72 @@ source: "src/transports/preview.ts"
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
Write email previews to disk instead of delivering them.
|
|
9
|
+
Use this when you want a local `.eml` or HTML file for the welcome email without a mail server.
|
|
9
10
|
|
|
10
11
|
<Callout title="The one rule">Preview is for development, not delivery.</Callout>
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
`outDir`, `open`, and `format` are optional; defaults are `./.emails`, false, and `eml`.
|
|
13
|
+
## Quick start
|
|
15
14
|
|
|
16
|
-
<Steps
|
|
15
|
+
<Steps>
|
|
16
|
+
<Step title="Create the preview transport">
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
|
+
import { createMailer } from "sently/mailer";
|
|
19
20
|
import { PreviewTransport } from "sently/transports/preview";
|
|
20
21
|
|
|
21
22
|
const transport = new PreviewTransport({ outDir: ".emails", open: true });
|
|
23
|
+
const mailer = await createMailer({ transport });
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
</Step
|
|
26
|
+
</Step>
|
|
27
|
+
<Step title="Send and open the file">
|
|
25
28
|
|
|
26
29
|
```ts
|
|
27
|
-
|
|
30
|
+
await mailer.send({
|
|
31
|
+
from: "dev@example.com",
|
|
32
|
+
to: "you@example.com",
|
|
33
|
+
subject: "Welcome",
|
|
34
|
+
text: "Thanks for joining.",
|
|
35
|
+
});
|
|
28
36
|
```
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
Files land under `outDir` (default `./.emails`). With `open: true`, the OS opens the written file.
|
|
39
|
+
|
|
40
|
+
</Step>
|
|
41
|
+
</Steps>
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
| Option | Type | Default | Meaning |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `outDir` | `string` | `"./.emails"` | Directory for preview files |
|
|
48
|
+
| `open` | `boolean` | `false` | Open the file after write |
|
|
49
|
+
| `format` | `"eml" \| "html"` | `"eml"` | Full MIME or HTML body only |
|
|
50
|
+
|
|
51
|
+
`provider` is `"preview"`. `verify()` always succeeds.
|
|
52
|
+
|
|
53
|
+
For a real SMTP catcher with a web UI, use [Mailpit](/docs/transports/mailpit) instead.
|
|
54
|
+
|
|
55
|
+
## Troubleshooting
|
|
56
|
+
|
|
57
|
+
<Accordions>
|
|
58
|
+
<Accordion title="Does Preview wrap another transport?">
|
|
59
|
+
No. `PreviewTransport` is the delivery transport — it writes to disk and does not call a provider.
|
|
60
|
+
</Accordion>
|
|
61
|
+
<Accordion title="Where is the file?">
|
|
62
|
+
Check `outDir` (default `./.emails`). The console logs `[sently preview] Written: …` on each send.
|
|
63
|
+
</Accordion>
|
|
64
|
+
</Accordions>
|
|
65
|
+
|
|
66
|
+
## Learn more
|
|
67
|
+
|
|
68
|
+
- [Mailpit](/docs/transports/mailpit) — local SMTP catcher with inbox API
|
|
69
|
+
- [Email channel](/docs/channels/email) — `createMailer` contract
|
|
31
70
|
|
|
32
|
-
|
|
71
|
+
## Next
|
|
33
72
|
|
|
34
|
-
<Cards
|
|
73
|
+
<Cards>
|
|
74
|
+
<Card title="Mailpit" href="/docs/transports/mailpit" />
|
|
75
|
+
<Card title="Email channel" href="/docs/channels/email" />
|
|
76
|
+
<Card title="Decorators" href="/docs/decorators" />
|
|
77
|
+
</Cards>
|
|
@@ -88,7 +88,7 @@ No. `createMailer` requires a transport. Import `createSMTPMailer` from `sently/
|
|
|
88
88
|
|
|
89
89
|
<Accordion title="Where do provider transports come from?">
|
|
90
90
|
|
|
91
|
-
Import the concrete transport from its exported `sently/transports/*` subpath, such as `sently/transports/sndr` or `sently/transports/
|
|
91
|
+
Import the concrete transport from its exported `sently/transports/*` subpath, such as `sently/transports/sndr`, `sently/transports/resend`, or `sently/transports/mailpit`.
|
|
92
92
|
|
|
93
93
|
</Accordion>
|
|
94
94
|
|
|
@@ -103,13 +103,15 @@ HTTP providers were removed from the main barrel so bare Node/Deno imports do no
|
|
|
103
103
|
## Learn more
|
|
104
104
|
|
|
105
105
|
- [Export reference](/docs/reference/exports) — full entrypoint map
|
|
106
|
+
- [Support matrix](/docs/get-started/support-matrix) — Supported vs Available
|
|
107
|
+
- [Stability policy](/docs/get-started/stability) — frozen entrypoints at 1.x
|
|
106
108
|
- [All transports](/docs/transports) — providers by channel
|
|
107
|
-
- [Bundle size](/docs/guides/bundle-size) — keep stacks small
|
|
109
|
+
- [Bundle size](/docs/guides/bundle-size) — keep stacks small on Workers
|
|
108
110
|
|
|
109
111
|
## Next
|
|
110
112
|
|
|
111
113
|
<Cards>
|
|
112
114
|
<Card title="Exports reference" description="Public package entrypoints." href="/docs/reference/exports" />
|
|
115
|
+
<Card title="Support matrix" description="Production support promise." href="/docs/get-started/support-matrix" />
|
|
113
116
|
<Card title="Email channel" description="createMailer and MailOptions." href="/docs/channels/email" />
|
|
114
|
-
<Card title="SNDR" description="HTTP email via SNDR." href="/docs/transports/sndr" />
|
|
115
117
|
</Cards>
|
|
@@ -5,10 +5,16 @@ icon: FolderOpen
|
|
|
5
5
|
source: "README.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Install sently and send your first message.
|
|
8
|
+
Install sently and send your first message. Learn the channel model once, then add SMS or push without a new failure model.
|
|
9
9
|
|
|
10
10
|
<Cards>
|
|
11
11
|
<Card title="Introduction" href="/docs/get-started/introduction" />
|
|
12
12
|
<Card title="Installation" href="/docs/get-started/installation" />
|
|
13
|
-
<Card title="
|
|
13
|
+
<Card title="Entrypoints" href="/docs/get-started/entrypoints" />
|
|
14
|
+
<Card title="Runtimes" href="/docs/get-started/runtimes" />
|
|
15
|
+
<Card title="Channels" href="/docs/channels" />
|
|
16
|
+
<Card title="Migrate from Nodemailer" href="/docs/get-started/migrate-nodemailer" />
|
|
17
|
+
<Card title="Stability policy" href="/docs/get-started/stability" />
|
|
18
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
19
|
+
<Card title="Non-goals" href="/docs/get-started/non-goals" />
|
|
14
20
|
</Cards>
|
|
@@ -5,8 +5,8 @@ icon: Compass
|
|
|
5
5
|
source: "README.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
sently
|
|
9
|
-
Choose
|
|
8
|
+
sently is the channel-delivery layer for email, SMS, WhatsApp, and push (Web Push or FCM).
|
|
9
|
+
Choose a channel sender first, then attach a provider transport — so adding SMS or push later does not invent a new error and retry model in your app.
|
|
10
10
|
|
|
11
11
|
<Callout title="The one rule">Use `createMailer`, `createSmsSender`, `createWhatsAppSender`, or `createPushSender` in app code; swap transports when provider configuration changes.</Callout>
|
|
12
12
|
|
|
@@ -67,11 +67,15 @@ Email factories are asynchronous. The other channel factories return their sende
|
|
|
67
67
|
|
|
68
68
|
- [Install sently](./installation)
|
|
69
69
|
- [Choose an entrypoint](./entrypoints)
|
|
70
|
+
- [Stability policy](./stability)
|
|
71
|
+
- [Support matrix](./support-matrix)
|
|
72
|
+
- [Compare](/docs/guides/compare)
|
|
70
73
|
- [Explore channels](../channels)
|
|
71
74
|
|
|
72
75
|
## Next
|
|
73
76
|
|
|
74
77
|
<Cards>
|
|
75
|
-
<Card title="Email
|
|
78
|
+
<Card title="Email channel" href="/docs/channels/email" />
|
|
79
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
76
80
|
<Card title="Channels" href="/docs/channels" />
|
|
77
81
|
</Cards>
|
|
@@ -2,22 +2,149 @@
|
|
|
2
2
|
title: Migrate from Nodemailer
|
|
3
3
|
description: Move SMTP configuration and message fields to sently incrementally.
|
|
4
4
|
icon: ArrowRightLeft
|
|
5
|
-
source: "
|
|
5
|
+
source: "src/smtp-mailer.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Keep your
|
|
8
|
+
Keep your welcome email, receipt, or password-reset fields (`from`, `to`, `subject`, `text` / `html`).
|
|
9
|
+
Replace `createTransport` with `createSMTPMailer`, rename `sendMail` to `send`, and `await` the factory before the first send.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Use `createSMTPMailer` for relay host/port/auth. Use `createMailer` only when you already have an explicit transport.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
<Steps>
|
|
18
|
+
<Step title="Create the mailer">
|
|
9
19
|
|
|
10
20
|
```ts
|
|
11
21
|
import { createSMTPMailer } from "sently/smtp";
|
|
12
22
|
|
|
13
23
|
const mailer = await createSMTPMailer({
|
|
14
24
|
host: "smtp.example.com",
|
|
25
|
+
port: 587,
|
|
15
26
|
auth: { user: "user@example.com", pass: process.env.SMTP_PASSWORD! },
|
|
16
27
|
});
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
</Step>
|
|
31
|
+
<Step title="Send with the same fields">
|
|
32
|
+
|
|
33
|
+
```diff
|
|
34
|
+
- const info = await transporter.sendMail({
|
|
35
|
+
+ const result = await mailer.send({
|
|
36
|
+
from: "Acme <hello@example.com>",
|
|
37
|
+
to: "person@example.com",
|
|
38
|
+
subject: "Welcome",
|
|
39
|
+
text: "Thanks for joining.",
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
</Step>
|
|
44
|
+
<Step title="Read the result">
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
console.log(result.messageId, result.accepted, result.rejected);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
</Step>
|
|
51
|
+
</Steps>
|
|
52
|
+
|
|
53
|
+
## API map
|
|
54
|
+
|
|
55
|
+
| Nodemailer | sently |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `createTransport({ host, port, auth })` | `await createSMTPMailer({ host, port, auth })` |
|
|
58
|
+
| `transporter.sendMail(msg)` | `mailer.send(msg)` |
|
|
59
|
+
| `transporter.verify()` | `mailer.verify()` |
|
|
60
|
+
| `transporter.close()` | `mailer.close()` |
|
|
61
|
+
| `info.messageId` / `accepted` / `rejected` / `response` / `envelope` | Same fields on `SendResult` |
|
|
62
|
+
| HTTP providers via plugins / custom | `createMailer` + `sently/transports/<provider>` |
|
|
63
|
+
|
|
64
|
+
## Message fields
|
|
65
|
+
|
|
66
|
+
| Nodemailer | sently |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `from` / `to` / `cc` / `bcc` / `replyTo` | Same names on `MailOptions` |
|
|
69
|
+
| `subject`, `text`, `html`, `headers`, `messageId`, `date` | Same names |
|
|
70
|
+
| `priority` | `"high" \| "normal" \| "low"` |
|
|
71
|
+
| `attachments[].filename` / `content` / `path` / `contentType` | Same names |
|
|
72
|
+
| `attachments[].cid` | `attachments[].contentId` |
|
|
73
|
+
| `attachments[].contentDisposition: "inline"` | `attachments[].inline: true` |
|
|
74
|
+
| `icalEvent`, SOCKS proxy options | Not supported — see [Non-goals](/docs/get-started/non-goals) |
|
|
75
|
+
|
|
76
|
+
## SMTP options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Meaning |
|
|
79
|
+
| --- | --- | --- | --- |
|
|
80
|
+
| `host` | `string` | required | Relay hostname |
|
|
81
|
+
| `port` | `number` | `587` (`465` if `secure`) | SMTP port |
|
|
82
|
+
| `secure` | `boolean` | `false` | Implicit TLS on connect |
|
|
83
|
+
| `auth` | `SMTPAuth` | — | `{ user, pass?, type?, oauth2? }` |
|
|
84
|
+
| `pool` | `boolean` | `false` | Connection pooling |
|
|
85
|
+
| `requireTLS` | `boolean` | `true` when `auth` is set | Refuse AUTH on a cleartext connection |
|
|
86
|
+
| `tls` | `TLSOptions` | — | `rejectUnauthorized`, `servername`, `minVersion` |
|
|
87
|
+
| `connectionTimeout` | `number` | — | Socket connect timeout (ms) |
|
|
88
|
+
| `greetingTimeout` | `number` | — | Wait for SMTP greeting (ms) |
|
|
89
|
+
| `socketTimeout` | `number` | — | Idle socket timeout (ms) |
|
|
90
|
+
|
|
91
|
+
## Switch to an HTTP provider later
|
|
92
|
+
|
|
93
|
+
Keep `mailer.send` and change only construction:
|
|
94
|
+
|
|
95
|
+
```diff
|
|
96
|
+
- import { createSMTPMailer } from "sently/smtp";
|
|
97
|
+
- const mailer = await createSMTPMailer({ host, port, auth });
|
|
98
|
+
+ import { createMailer } from "sently/mailer";
|
|
99
|
+
+ import { ResendTransport } from "sently/transports/resend";
|
|
100
|
+
+ const mailer = await createMailer({
|
|
101
|
+
+ transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
102
|
+
+ });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Consequence:** message fields stay the same; only the factory and transport change.
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
<Accordions>
|
|
110
|
+
<Accordion title='Error: "SMTP config passed to transport-only createMailer"'>
|
|
111
|
+
`createMailer` accepts only `{ transport, plugins?, hooks? }`. Move host/port/auth to `createSMTPMailer` from `sently/smtp`.
|
|
112
|
+
</Accordion>
|
|
113
|
+
<Accordion title="Do I need to rewrite every message object?">
|
|
114
|
+
Usually no. Keep `from`, `to`, `subject`, and body fields; change construction and `sendMail` → `send`. Rename attachment `cid` to `contentId` when you use inline images.
|
|
115
|
+
</Accordion>
|
|
116
|
+
<Accordion title="Why is createSMTPMailer async?">
|
|
117
|
+
The factory prepares the runtime SMTP connection path (and optionally the pool) before returning. Always `await` it before `send`.
|
|
118
|
+
</Accordion>
|
|
119
|
+
<Accordion title="Is sently a drop-in for every Nodemailer plugin?">
|
|
120
|
+
No. SOCKS and iCal are intentional non-goals. See [Non-goals](/docs/get-started/non-goals) and [Compare](/docs/guides/compare).
|
|
121
|
+
</Accordion>
|
|
122
|
+
</Accordions>
|
|
123
|
+
|
|
124
|
+
## Learn more
|
|
125
|
+
|
|
126
|
+
- [Email channel](/docs/channels/email) — mailer + transport model after you migrate
|
|
127
|
+
- [Mail options](/docs/reference/mail-options) — full `MailOptions` field list
|
|
128
|
+
- [Attachments](/docs/guides/attachments) — `content`, `path`, and `contentId`
|
|
129
|
+
- [Entrypoints](/docs/get-started/entrypoints) — `sently/smtp` vs `sently/mailer`
|
|
130
|
+
- [Support matrix](/docs/get-started/support-matrix) — which runtimes and exports are supported
|
|
20
131
|
|
|
21
|
-
|
|
132
|
+
## Next
|
|
22
133
|
|
|
23
|
-
<Cards
|
|
134
|
+
<Cards>
|
|
135
|
+
<Card
|
|
136
|
+
title="Email channel"
|
|
137
|
+
description="Send with createMailer or createSMTPMailer."
|
|
138
|
+
href="/docs/channels/email"
|
|
139
|
+
/>
|
|
140
|
+
<Card
|
|
141
|
+
title="SMTP transport"
|
|
142
|
+
description="Relay options when you wire SMTPTransport yourself."
|
|
143
|
+
href="/docs/transports/smtp"
|
|
144
|
+
/>
|
|
145
|
+
<Card
|
|
146
|
+
title="Compare"
|
|
147
|
+
description="Nodemailer, vendor SDKs, and orchestration platforms."
|
|
148
|
+
href="/docs/guides/compare"
|
|
149
|
+
/>
|
|
150
|
+
</Cards>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Non-goals
|
|
3
|
+
description: What sently intentionally does not ship — and what belongs on top.
|
|
4
|
+
icon: Ban
|
|
5
|
+
source: "README.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
sently is a **channel-delivery library**. Keeping the surface focused is part of the 1.x promise.
|
|
9
|
+
|
|
10
|
+
<Callout title="The one rule">
|
|
11
|
+
Do not expect dashboards, preference centers, digests, or workflow builders inside sently. Put that product logic — or Novu / Knock / Courier — on top of the delivery layer.
|
|
12
|
+
</Callout>
|
|
13
|
+
|
|
14
|
+
## Out of scope
|
|
15
|
+
|
|
16
|
+
| Non-goal | Notes |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| Orchestration platforms | Preference centers, digests, journey builders, in-app inboxes |
|
|
19
|
+
| Hosted SaaS / per-send billing | MIT library in your process |
|
|
20
|
+
| APNs as a native push transport | Use FCM and/or Web Push for 1.x |
|
|
21
|
+
| Voice channels | Not a sently channel |
|
|
22
|
+
| Inbound email productization | Outbound + webhook parsers only |
|
|
23
|
+
| Nodemailer SOCKS / iCal parity | Intentionally omitted |
|
|
24
|
+
| Equal support for every export | See [Support matrix](./support-matrix) |
|
|
25
|
+
| Cross-channel preview / idempotency | Email-only today |
|
|
26
|
+
|
|
27
|
+
## Complementary stack
|
|
28
|
+
|
|
29
|
+
| Need | Place it… |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Send email / SMS / WhatsApp / push | sently channel senders |
|
|
32
|
+
| Swap providers without rewriting call sites | sently transports |
|
|
33
|
+
| Preferences, digests, workflows, in-app | Your app or Novu / Knock / Courier **on top of** sently |
|
|
34
|
+
|
|
35
|
+
## Troubleshooting
|
|
36
|
+
|
|
37
|
+
<Accordions>
|
|
38
|
+
<Accordion title="Will sently add a preference center?">
|
|
39
|
+
No. That is platform territory and conflicts with the library-not-platform position.
|
|
40
|
+
</Accordion>
|
|
41
|
+
<Accordion title="Where should I read the positioning story?">
|
|
42
|
+
See [Compare](/docs/guides/compare).
|
|
43
|
+
</Accordion>
|
|
44
|
+
</Accordions>
|
|
45
|
+
|
|
46
|
+
## Learn more
|
|
47
|
+
|
|
48
|
+
- [Compare](/docs/guides/compare)
|
|
49
|
+
- [Support matrix](./support-matrix)
|
|
50
|
+
- [Stability policy](./stability)
|
|
51
|
+
|
|
52
|
+
## Next
|
|
53
|
+
|
|
54
|
+
<Cards>
|
|
55
|
+
<Card title="Compare" href="/docs/guides/compare" />
|
|
56
|
+
<Card title="Introduction" href="/docs/get-started/introduction" />
|
|
57
|
+
</Cards>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Stability policy
|
|
3
|
+
description: What is frozen at 1.0 and what may grow under semver.
|
|
4
|
+
icon: Shield
|
|
5
|
+
source: "package.json"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
From **1.0.0**, sently follows semver for the public channel-delivery surface.
|
|
9
|
+
Apps should depend on channel senders and contracts — not on vendor SDK shapes.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Treat channel factories, transport contracts, hooks, `SentlyError` codes, and
|
|
13
|
+
published subpath entrypoints as stable. New transports and optional fields may
|
|
14
|
+
appear in minor releases; renames or narrowed types require a major.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
## Frozen at 1.x (Tier A)
|
|
18
|
+
|
|
19
|
+
| Surface | Stable APIs |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Factories | `createMailer`, `createSMTPMailer`, `createSmsSender`, `createWhatsAppSender`, `createPushSender` |
|
|
22
|
+
| Contracts | `Transport`, `SmsTransport`, `WhatsAppTransport`, `PushTransport` option and result shapes |
|
|
23
|
+
| Shared | Lifecycle hooks, `SentlyError` / `sentlyCode`, `ChannelSendResult` / `toChannelSendResult` |
|
|
24
|
+
| Decorators | `RetryTransport`, `FallbackTransport` on all channels |
|
|
25
|
+
| Email-only decorators | `IdempotencyTransport`, `PreviewTransport` (documented as email-only; not removed) |
|
|
26
|
+
| Entrypoints | Subpaths listed in `package.json` `exports` — no silent moves |
|
|
27
|
+
|
|
28
|
+
## Intentional shapes (stable, not transitional)
|
|
29
|
+
|
|
30
|
+
| Shape | Meaning |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `PushOptions` | Discriminated union: Web Push `subscription` **or** FCM `token` |
|
|
33
|
+
| `providerIndex` | Optional on send results when a fallback decorator handled the send |
|
|
34
|
+
| Email-only preview / idempotency | Stay email-scoped; not a signal they will move to other channels soon |
|
|
35
|
+
|
|
36
|
+
## Allowed without a major
|
|
37
|
+
|
|
38
|
+
- New transport classes and webhook parsers
|
|
39
|
+
- New optional fields on options/results
|
|
40
|
+
- New subpath exports
|
|
41
|
+
- Bug fixes and security patches on the current **1.x** line
|
|
42
|
+
|
|
43
|
+
## Security patches
|
|
44
|
+
|
|
45
|
+
See [`SECURITY.md`](https://github.com/alialnaghmoush/sently/blob/main/SECURITY.md) for reporting and which versions receive patches.
|
|
46
|
+
|
|
47
|
+
## Troubleshooting
|
|
48
|
+
|
|
49
|
+
<Accordions>
|
|
50
|
+
<Accordion title="Can I pin 0.x in production after 1.0?">
|
|
51
|
+
Prefer upgrading to 1.x. Pre-1.0 may still receive best-effort fixes but is not the supported security line.
|
|
52
|
+
</Accordion>
|
|
53
|
+
<Accordion title="Where is the production support promise?">
|
|
54
|
+
See [Support matrix](./support-matrix) for Supported vs Available transports.
|
|
55
|
+
</Accordion>
|
|
56
|
+
</Accordions>
|
|
57
|
+
|
|
58
|
+
## Learn more
|
|
59
|
+
|
|
60
|
+
- [Support matrix](./support-matrix)
|
|
61
|
+
- [Non-goals](./non-goals)
|
|
62
|
+
- [Compare](/docs/guides/compare)
|
|
63
|
+
|
|
64
|
+
## Next
|
|
65
|
+
|
|
66
|
+
<Cards>
|
|
67
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
68
|
+
<Card title="Non-goals" href="/docs/get-started/non-goals" />
|
|
69
|
+
</Cards>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Support matrix
|
|
3
|
+
description: Supported vs Available transports and runtimes for production use.
|
|
4
|
+
icon: LayoutGrid
|
|
5
|
+
source: "package.json"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Not every exported transport is part of the **1.x production promise**.
|
|
9
|
+
**Supported** paths are documented, tested, and operable (verify, retry/fallback, and webhooks where listed).
|
|
10
|
+
**Available** paths ship and have unit coverage but are not the support bar.
|
|
11
|
+
|
|
12
|
+
<Callout title="The one rule">
|
|
13
|
+
Build production apps on Supported transports. Treat Available exports as optional extras you can adopt knowing the promise is thinner.
|
|
14
|
+
</Callout>
|
|
15
|
+
|
|
16
|
+
## Supported (v1 promise)
|
|
17
|
+
|
|
18
|
+
| Channel | Supported transports | Operability |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Email | SMTP, Resend, SES, SendGrid, Postmark | Attachments / HTML / text, `verify`, retry / fallback, webhooks for the HTTP majors |
|
|
21
|
+
| SMS | Twilio SMS, Unifonic | Webhooks for Twilio and Unifonic |
|
|
22
|
+
| WhatsApp | WhatsApp Cloud | Signature verify + delivery parse |
|
|
23
|
+
| Push | Web Push, FCM | Option union: `subscription` (Web Push) or `token` (FCM) |
|
|
24
|
+
| Runtimes | Node ≥18, Bun, Deno, Cloudflare Workers | Adapters and smoke scripts |
|
|
25
|
+
|
|
26
|
+
FCM uses the current Firebase HTTP API (service-account JWT, no Google SDK). That is baseline push support, not a special feature tier.
|
|
27
|
+
|
|
28
|
+
## Available (exported, not the v1 bar)
|
|
29
|
+
|
|
30
|
+
| Channel | Available examples |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Email | Mailgun, Brevo, MailerSend, Plunk, SparkPost, Mailtrap, Mailpit (dev), Loops, SNDR, Taqnyat Mail, Cloudflare Email, … |
|
|
33
|
+
| SMS | Taqnyat SMS, Msegat |
|
|
34
|
+
| WhatsApp | Taqnyat WhatsApp |
|
|
35
|
+
| Decorators | `WeightedFallbackTransport` (advanced); preview / idempotency remain email-only |
|
|
36
|
+
| Local email | [Mailpit](/docs/transports/mailpit) (SMTP catcher), [Preview](/docs/decorators/preview) (disk) |
|
|
37
|
+
|
|
38
|
+
Available transports stay in the package. They are promoted to Supported when docs, operability, and smoke coverage meet the bar above.
|
|
39
|
+
|
|
40
|
+
## Troubleshooting
|
|
41
|
+
|
|
42
|
+
<Accordions>
|
|
43
|
+
<Accordion title="Can I use an Available transport in production?">
|
|
44
|
+
Yes, at your own risk. Prefer Supported paths when you need the documented operability promise.
|
|
45
|
+
</Accordion>
|
|
46
|
+
<Accordion title="Why is Mailgun Available but Postmark Supported?">
|
|
47
|
+
The Supported set is a deliberate short list for the 1.x promise — not a popularity ranking of every working export.
|
|
48
|
+
</Accordion>
|
|
49
|
+
</Accordions>
|
|
50
|
+
|
|
51
|
+
## Learn more
|
|
52
|
+
|
|
53
|
+
- [Stability policy](./stability)
|
|
54
|
+
- [Non-goals](./non-goals)
|
|
55
|
+
- [Transports](/docs/transports)
|
|
56
|
+
|
|
57
|
+
## Next
|
|
58
|
+
|
|
59
|
+
<Cards>
|
|
60
|
+
<Card title="Stability policy" href="/docs/get-started/stability" />
|
|
61
|
+
<Card title="Transports" href="/docs/transports" />
|
|
62
|
+
</Cards>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compare
|
|
3
|
+
description: How sently fits next to vendor SDKs, Nodemailer, and orchestration platforms.
|
|
4
|
+
icon: Scale
|
|
5
|
+
source: "README.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Teams often start with one channel — usually email via SendGrid or Resend — then add SMS and push.
|
|
9
|
+
Each new vendor SDK brings its own auth, retries, and error shapes into app code.
|
|
10
|
+
|
|
11
|
+
<Callout title="The one rule">
|
|
12
|
+
Treat sently as the channel-delivery layer. Put preference centers, digests, workflow builders, and in-app inboxes on top of it — custom code or a tool like Novu, Knock, or Courier — not instead of it.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## What sently replaces
|
|
16
|
+
|
|
17
|
+
| Instead of… | Use sently for… |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| A growing pile of vendor SDKs in call sites | Channel senders + pluggable transports |
|
|
20
|
+
| Per-channel retry and error glue | Shared `SentlyError` codes and retry/fallback decorators |
|
|
21
|
+
| Nodemailer when you need multi-channel or non-Node runtimes | `createMailer` / `createSMTPMailer` plus SMS, WhatsApp, and push |
|
|
22
|
+
|
|
23
|
+
## What sently does not replace
|
|
24
|
+
|
|
25
|
+
| Need | Where it lives |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| Preference centers and unsubscribe UX | Your product, or Novu / Knock / Courier |
|
|
28
|
+
| Digest / batching product logic | Your product, or an orchestration platform |
|
|
29
|
+
| Workflow builders and multi-step journeys | Your product, or Novu / Knock / Courier |
|
|
30
|
+
| In-app notification inboxes | Your product, or an orchestration platform |
|
|
31
|
+
|
|
32
|
+
Those tools (or your own routing) call sently — or any transport — to deliver. sently does not ship dashboards or per-send SaaS.
|
|
33
|
+
|
|
34
|
+
## Versus a vendor SDK pile
|
|
35
|
+
|
|
36
|
+
| Concern | Typical stack | sently |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| As you add channels | New SDK, new auth, new retries | Same sender factories |
|
|
39
|
+
| Failures | Per-vendor exceptions | Stable `SentlyError` codes |
|
|
40
|
+
| Reliability | Ad-hoc loops per client | `RetryTransport` + `FallbackTransport` |
|
|
41
|
+
| Provider swap | Rewrite call sites | Swap the transport |
|
|
42
|
+
|
|
43
|
+
## Versus Nodemailer
|
|
44
|
+
|
|
45
|
+
| Concern | Nodemailer | sently |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Channels | Email | Email · SMS · WhatsApp · Push |
|
|
48
|
+
| Runtimes | Node.js | Node · Bun · Deno · CF Workers |
|
|
49
|
+
| Module format | CommonJS | ESM only |
|
|
50
|
+
| Edge / Workers weight | Full mail stack on import | Tree-shakeable subpaths (~6.3 KB HTTP) |
|
|
51
|
+
|
|
52
|
+
Bundle size matters most on Workers and cold starts. It is supporting evidence, not the primary reason to adopt.
|
|
53
|
+
|
|
54
|
+
## Versus Novu, Knock, and Courier
|
|
55
|
+
|
|
56
|
+
| Concern | Orchestration platforms | sently |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Role | Workflows, preferences, digests, in-app | Channel delivery / transports |
|
|
59
|
+
| Hosting | Usually hosted SaaS | Library in your process |
|
|
60
|
+
| Pricing model | Often per-send or seat | MIT package; you pay providers |
|
|
61
|
+
|
|
62
|
+
Use both when you need product orchestration **and** a typed, multi-runtime delivery layer underneath.
|
|
63
|
+
|
|
64
|
+
## Troubleshooting
|
|
65
|
+
|
|
66
|
+
<Accordions>
|
|
67
|
+
<Accordion title="Do I need sently if I already use Novu or Knock?">
|
|
68
|
+
Only if you want local, typed transports under your own routing — or to send outside that platform. Many teams use an orchestration tool for preferences and journeys, and a library like sently for direct transactional sends.
|
|
69
|
+
</Accordion>
|
|
70
|
+
<Accordion title="Is sently a Nodemailer drop-in?">
|
|
71
|
+
No. Migrate email with [Migrate from Nodemailer](/docs/get-started/migrate-nodemailer), then add other channels with the same sender pattern.
|
|
72
|
+
</Accordion>
|
|
73
|
+
</Accordions>
|
|
74
|
+
|
|
75
|
+
## Learn more
|
|
76
|
+
|
|
77
|
+
- [Introduction](/docs/get-started/introduction)
|
|
78
|
+
- [Stability policy](/docs/get-started/stability)
|
|
79
|
+
- [Support matrix](/docs/get-started/support-matrix)
|
|
80
|
+
- [Migrate from Nodemailer](/docs/get-started/migrate-nodemailer)
|
|
81
|
+
|
|
82
|
+
## Next
|
|
83
|
+
|
|
84
|
+
<Cards>
|
|
85
|
+
<Card title="Introduction" href="/docs/get-started/introduction" />
|
|
86
|
+
<Card title="Support matrix" href="/docs/get-started/support-matrix" />
|
|
87
|
+
</Cards>
|