sently 0.9.2 → 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 +86 -0
- package/README.md +39 -34
- package/SECURITY.md +59 -0
- 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/channel-result.d.ts +6 -0
- package/dist/channel-result.js +1 -0
- package/dist/{chunk-skqhj0rm.js → chunk-1ke3nmyg.js} +3 -3
- package/dist/{chunk-skqhj0rm.js.map → chunk-1ke3nmyg.js.map} +1 -1
- package/dist/{chunk-7gy2q9sh.js → chunk-5es0v1j7.js} +2 -2
- package/dist/{chunk-7gy2q9sh.js.map → chunk-5es0v1j7.js.map} +1 -1
- package/dist/{chunk-0qxws3kj.js → chunk-7wtfjx0d.js} +2 -2
- package/dist/{chunk-0qxws3kj.js.map → chunk-7wtfjx0d.js.map} +1 -1
- package/dist/chunk-8kpgbrba.js +4 -0
- package/dist/{chunk-93vqxxj2.js.map → chunk-8kpgbrba.js.map} +2 -2
- package/dist/{chunk-vjpds2pf.js → chunk-a057jzsy.js} +2 -2
- package/dist/{chunk-vjpds2pf.js.map → chunk-a057jzsy.js.map} +1 -1
- package/dist/{chunk-8njyga62.js → chunk-bhr7w3gg.js} +2 -2
- package/dist/{chunk-8njyga62.js.map → chunk-bhr7w3gg.js.map} +1 -1
- package/dist/chunk-cs1tvehn.js +4 -0
- package/dist/chunk-cs1tvehn.js.map +10 -0
- package/dist/{chunk-29z7fkzy.js → chunk-d5aw5vag.js} +3 -3
- package/dist/{chunk-29z7fkzy.js.map → chunk-d5aw5vag.js.map} +1 -1
- package/dist/chunk-f5x8vesr.js +4 -0
- package/dist/{chunk-b602dhck.js.map → chunk-f5x8vesr.js.map} +2 -2
- package/dist/chunk-fv0bemgf.js +4 -0
- package/dist/{chunk-yp311efr.js.map → chunk-fv0bemgf.js.map} +3 -3
- package/dist/{chunk-ywq6s10g.js → chunk-hnzmn4s4.js} +2 -2
- package/dist/{chunk-ywq6s10g.js.map → chunk-hnzmn4s4.js.map} +1 -1
- package/dist/chunk-htdg075t.js +4 -0
- package/dist/{chunk-qtkd5bak.js.map → chunk-htdg075t.js.map} +4 -3
- package/dist/{chunk-0gqy32pe.js → chunk-q2mahnb7.js} +3 -3
- package/dist/chunk-q2mahnb7.js.map +10 -0
- package/dist/chunk-sbgt4165.js +4 -0
- package/dist/chunk-sbgt4165.js.map +10 -0
- package/dist/{chunk-6npp3x3c.js → chunk-th4cwrpb.js} +2 -2
- package/dist/{chunk-6npp3x3c.js.map → chunk-th4cwrpb.js.map} +1 -1
- package/dist/{chunk-tamww15j.js → chunk-ttbwyxmh.js} +2 -2
- package/dist/{chunk-tamww15j.js.map → chunk-ttbwyxmh.js.map} +1 -1
- package/dist/chunk-w4c9kj70.js +13 -0
- package/dist/{chunk-n0qeyzqm.js.map → chunk-w4c9kj70.js.map} +2 -2
- package/dist/chunk-w9sabmb0.js +4 -0
- package/dist/chunk-w9sabmb0.js.map +10 -0
- package/dist/{chunk-8dty8c4p.js → chunk-yg3kj723.js} +3 -3
- package/dist/{chunk-8dty8c4p.js.map → chunk-yg3kj723.js.map} +2 -2
- package/dist/chunk-z1589fjk.js +4 -0
- package/dist/chunk-z1589fjk.js.map +10 -0
- package/dist/core/channel-result.d.ts +36 -0
- package/dist/core/channel-result.js +3 -0
- package/dist/core/channel-result.js.map +10 -0
- package/dist/core/decorated-transport.d.ts +30 -0
- package/dist/core/decorator-hooks.d.ts +17 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/provider-label.d.ts +7 -3
- package/dist/core/push-endpoint.d.ts +5 -0
- package/dist/core/push-types.d.ts +48 -14
- package/dist/core/sms-types.d.ts +12 -0
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/whatsapp-types.d.ts +12 -0
- package/dist/detect.js +2 -2
- package/dist/detect.js.map +1 -1
- package/dist/dkim.js +2 -2
- package/dist/dkim.js.map +2 -2
- package/dist/idempotency.js +2 -2
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/mailer.js +2 -2
- package/dist/mailer.js.map +1 -1
- package/dist/observability/console.js +1 -1
- 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/push.d.ts +1 -0
- package/dist/push.js +2 -2
- package/dist/push.js.map +3 -3
- package/dist/sms.d.ts +1 -0
- package/dist/sms.js +2 -2
- package/dist/sms.js.map +3 -3
- package/dist/smtp-mailer.js +2 -2
- package/dist/smtp-mailer.js.map +1 -1
- package/dist/transports/brevo.js +2 -2
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/cloudflare-email.js +2 -2
- package/dist/transports/cloudflare-email.js.map +2 -2
- package/dist/transports/fallback.d.ts +32 -6
- package/dist/transports/fallback.js +2 -2
- package/dist/transports/fallback.js.map +1 -1
- package/dist/transports/fcm.d.ts +55 -0
- package/dist/transports/fcm.js +4 -0
- package/dist/transports/fcm.js.map +10 -0
- package/dist/transports/loops.js +2 -2
- package/dist/transports/loops.js.map +1 -1
- package/dist/transports/mailersend.js +2 -2
- package/dist/transports/mailersend.js.map +2 -2
- package/dist/transports/mailgun.js +2 -2
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/mailpit.d.ts +166 -0
- package/dist/transports/mailpit.js +3 -0
- package/dist/transports/mailpit.js.map +10 -0
- package/dist/transports/mailtrap.js +2 -2
- package/dist/transports/mailtrap.js.map +2 -2
- package/dist/transports/msegat.js +2 -2
- package/dist/transports/msegat.js.map +1 -1
- package/dist/transports/plunk.js +2 -2
- package/dist/transports/plunk.js.map +1 -1
- package/dist/transports/postmark.js +2 -2
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -2
- package/dist/transports/preview.js.map +1 -1
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.d.ts +28 -9
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +3 -3
- package/dist/transports/sendgrid.js +2 -2
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +2 -2
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -2
- package/dist/transports/smtp.js.map +1 -1
- package/dist/transports/sndr.js +2 -2
- package/dist/transports/sndr.js.map +1 -1
- package/dist/transports/sparkpost.js +2 -2
- package/dist/transports/sparkpost.js.map +2 -2
- package/dist/transports/taqnyat-mail.js +2 -2
- package/dist/transports/taqnyat-mail.js.map +1 -1
- package/dist/transports/taqnyat-sms.js +2 -2
- package/dist/transports/taqnyat-sms.js.map +1 -1
- package/dist/transports/taqnyat-whatsapp.js +2 -2
- package/dist/transports/taqnyat-whatsapp.js.map +1 -1
- package/dist/transports/twilio-sms.js +2 -2
- package/dist/transports/twilio-sms.js.map +2 -2
- package/dist/transports/unifonic.d.ts +81 -0
- package/dist/transports/unifonic.js +3 -0
- package/dist/transports/unifonic.js.map +10 -0
- package/dist/transports/webpush.js +2 -2
- package/dist/transports/webpush.js.map +3 -3
- package/dist/transports/weighted-fallback.d.ts +11 -9
- package/dist/transports/weighted-fallback.js +2 -2
- package/dist/transports/weighted-fallback.js.map +3 -3
- package/dist/transports/whatsapp-cloud.js +2 -2
- package/dist/transports/whatsapp-cloud.js.map +1 -1
- package/dist/webhooks/brevo.js +2 -2
- package/dist/webhooks/brevo.js.map +1 -1
- package/dist/webhooks/mailgun.js +2 -2
- package/dist/webhooks/mailgun.js.map +1 -1
- package/dist/webhooks/postmark.js +2 -2
- package/dist/webhooks/postmark.js.map +1 -1
- package/dist/webhooks/resend.js +2 -2
- package/dist/webhooks/resend.js.map +1 -1
- package/dist/webhooks/sendgrid.js +2 -2
- package/dist/webhooks/sendgrid.js.map +1 -1
- package/dist/webhooks/ses.js +2 -2
- package/dist/webhooks/ses.js.map +1 -1
- package/dist/webhooks/sndr.js +2 -2
- package/dist/webhooks/sndr.js.map +1 -1
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/dist/webhooks/twilio-sms.d.ts +20 -0
- package/dist/webhooks/twilio-sms.js +3 -0
- package/dist/webhooks/twilio-sms.js.map +10 -0
- package/dist/webhooks/types.d.ts +37 -3
- package/dist/webhooks/types.js +3 -0
- package/dist/webhooks/types.js.map +9 -0
- package/dist/webhooks/unifonic.d.ts +13 -0
- package/dist/webhooks/unifonic.js +3 -0
- package/dist/webhooks/unifonic.js.map +10 -0
- package/dist/webhooks/whatsapp-cloud.d.ts +14 -0
- package/dist/webhooks/whatsapp-cloud.js +3 -0
- package/dist/webhooks/whatsapp-cloud.js.map +10 -0
- package/dist/webhooks.d.ts +10 -5
- package/dist/webhooks.js +11 -0
- package/dist/whatsapp.d.ts +1 -0
- package/dist/whatsapp.js +2 -2
- package/dist/whatsapp.js.map +3 -3
- package/package.json +39 -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/hooks.mdx +6 -5
- package/site/content/docs/channels/index.mdx +5 -3
- package/site/content/docs/channels/push.mdx +31 -21
- package/site/content/docs/channels/sms.mdx +4 -1
- package/site/content/docs/decorators/fallback.mdx +45 -13
- package/site/content/docs/decorators/index.mdx +31 -13
- package/site/content/docs/decorators/preview.mdx +52 -9
- package/site/content/docs/decorators/retry.mdx +32 -9
- package/site/content/docs/decorators/weighted-fallback.mdx +27 -6
- package/site/content/docs/get-started/entrypoints.mdx +11 -8
- package/site/content/docs/get-started/index.mdx +8 -2
- package/site/content/docs/get-started/installation.mdx +3 -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/guides/webhooks.mdx +35 -20
- package/site/content/docs/index.mdx +78 -4
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/channel-result.mdx +82 -0
- package/site/content/docs/reference/exports.mdx +9 -4
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/push-options.mdx +58 -3
- package/site/content/docs/reference/sms-options.mdx +18 -2
- package/site/content/docs/reference/webhook-events.mdx +38 -14
- package/site/content/docs/reference/whatsapp-options.mdx +19 -1
- package/site/content/docs/transports/fcm.mdx +84 -0
- package/site/content/docs/transports/index.mdx +11 -3
- package/site/content/docs/transports/mailpit.mdx +123 -0
- package/site/content/docs/transports/meta.json +4 -1
- package/site/content/docs/transports/smtp.mdx +61 -22
- package/site/content/docs/transports/unifonic.mdx +85 -0
- package/dist/chunk-0gqy32pe.js.map +0 -10
- package/dist/chunk-5915vbcj.js +0 -4
- package/dist/chunk-5915vbcj.js.map +0 -10
- package/dist/chunk-93vqxxj2.js +0 -4
- package/dist/chunk-b602dhck.js +0 -4
- package/dist/chunk-gp4fwrs8.js +0 -4
- package/dist/chunk-gp4fwrs8.js.map +0 -10
- package/dist/chunk-n0qeyzqm.js +0 -13
- package/dist/chunk-q8k3m6qe.js +0 -4
- package/dist/chunk-q8k3m6qe.js.map +0 -10
- package/dist/chunk-qtkd5bak.js +0 -4
- package/dist/chunk-yp311efr.js +0 -4
- 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 -36
- package/site/content/docs/quick-start/sms.mdx +0 -36
- package/site/content/docs/quick-start/whatsapp.mdx +0 -38
|
@@ -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>
|
|
@@ -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>
|