sently 0.10.0 → 1.0.1

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.
Files changed (49) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +50 -2
  3. package/README.md +21 -18
  4. package/SECURITY.md +59 -0
  5. package/dist/chunk-z1589fjk.js.map +1 -1
  6. package/dist/core/push-types.d.ts +1 -1
  7. package/dist/transports/mailpit.d.ts +166 -0
  8. package/dist/transports/mailpit.js +3 -0
  9. package/dist/transports/mailpit.js.map +10 -0
  10. package/dist/transports/webpush.d.ts +5 -1
  11. package/dist/transports/webpush.js +2 -2
  12. package/dist/transports/webpush.js.map +3 -3
  13. package/package.json +15 -2
  14. package/site/content/docs/ai/llms-txt.mdx +47 -2
  15. package/site/content/docs/channels/email.mdx +3 -0
  16. package/site/content/docs/channels/index.mdx +3 -1
  17. package/site/content/docs/channels/push.mdx +4 -0
  18. package/site/content/docs/decorators/fallback.mdx +5 -0
  19. package/site/content/docs/decorators/index.mdx +2 -1
  20. package/site/content/docs/decorators/preview.mdx +52 -9
  21. package/site/content/docs/get-started/entrypoints.mdx +5 -3
  22. package/site/content/docs/get-started/index.mdx +8 -2
  23. package/site/content/docs/get-started/introduction.mdx +7 -3
  24. package/site/content/docs/get-started/meta.json +4 -1
  25. package/site/content/docs/get-started/migrate-nodemailer.mdx +132 -5
  26. package/site/content/docs/get-started/non-goals.mdx +57 -0
  27. package/site/content/docs/get-started/stability.mdx +69 -0
  28. package/site/content/docs/get-started/support-matrix.mdx +62 -0
  29. package/site/content/docs/guides/compare.mdx +87 -0
  30. package/site/content/docs/guides/failover.mdx +121 -0
  31. package/site/content/docs/guides/index.mdx +2 -0
  32. package/site/content/docs/guides/meta.json +2 -0
  33. package/site/content/docs/guides/security.mdx +38 -8
  34. package/site/content/docs/index.mdx +78 -4
  35. package/site/content/docs/meta.json +0 -1
  36. package/site/content/docs/reference/exports.mdx +5 -2
  37. package/site/content/docs/reference/push-options.mdx +14 -0
  38. package/site/content/docs/transports/fcm.mdx +8 -4
  39. package/site/content/docs/transports/index.mdx +9 -3
  40. package/site/content/docs/transports/mailpit.mdx +123 -0
  41. package/site/content/docs/transports/meta.json +1 -0
  42. package/site/content/docs/transports/smtp.mdx +61 -22
  43. package/site/content/docs/transports/webpush.mdx +9 -2
  44. package/site/content/docs/quick-start/email.mdx +0 -40
  45. package/site/content/docs/quick-start/index.mdx +0 -15
  46. package/site/content/docs/quick-start/meta.json +0 -5
  47. package/site/content/docs/quick-start/push.mdx +0 -53
  48. package/site/content/docs/quick-start/sms.mdx +0 -36
  49. package/site/content/docs/quick-start/whatsapp.mdx +0 -38
@@ -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" />
@@ -3,6 +3,8 @@
3
3
  "icon": "Map",
4
4
  "pages": [
5
5
  "index",
6
+ "compare",
7
+ "failover",
6
8
  "adapters",
7
9
  "dkim",
8
10
  "oauth2",
@@ -1,17 +1,47 @@
1
1
  ---
2
2
  title: Security
3
- description: Protect credentials, delivery endpoints, and webhook integrity.
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
- <Callout title="The one rule">Keep provider keys and VAPID private keys in a secrets manager or environment variables.</Callout>
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
- - Enable TLS for SMTP authentication; `requireTLS` defaults to true when auth is set.
11
- - Verify provider webhook signatures before parsing trusted events.
12
- - Do not log email bodies, SMS bodies, or full push subscription endpoints.
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
- <Accordions><Accordion title="Why is a push endpoint redacted in hooks?">Its path can contain a long-lived delivery token.</Accordion></Accordions>
15
+ ## Checklist
16
16
 
17
- <Cards><Card title="Webhooks" href="/docs/guides/webhooks" /></Cards>
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-first messaging for email, SMS, WhatsApp, and push (Web Push / FCM).
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
- Use a channel sender in application code and put provider-specific configuration in a transport.
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="Transports" href="/docs/transports" />
88
+ <Card title="Support matrix" href="/docs/get-started/support-matrix" />
15
89
  </Cards>
@@ -4,7 +4,6 @@
4
4
  "pages": [
5
5
  "index",
6
6
  "get-started",
7
- "quick-start",
8
7
  "channels",
9
8
  "transports",
10
9
  "decorators",
@@ -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`, `unifonic`, `fcm`, `retry`) |
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
- - [Bundle size](/docs/guides/bundle-size) — measured stacks
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 v1.
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 HTTP v1 for native Android and iOS clients.
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="Send to a device token">
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="Email channel" href="/docs/channels/email" />
106
- <Card title="SMS channel" href="/docs/channels/sms" />
112
+ <Card title="Failover" href="/docs/guides/failover" />
107
113
  </Cards>