sently 1.0.0 → 1.1.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.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +97 -0
- package/README.md +20 -5
- package/dist/chunk-z1589fjk.js.map +2 -2
- package/dist/core/push-types.d.ts +53 -4
- package/dist/transports/inbucket.d.ts +196 -0
- package/dist/transports/inbucket.js +3 -0
- package/dist/transports/inbucket.js.map +10 -0
- package/dist/transports/mailpit.d.ts +108 -8
- package/dist/transports/mailpit.js +2 -2
- package/dist/transports/mailpit.js.map +3 -3
- package/dist/transports/taqnyat-sms.d.ts +85 -0
- package/dist/transports/taqnyat-sms.js +2 -2
- package/dist/transports/taqnyat-sms.js.map +3 -3
- package/dist/transports/taqnyat-whatsapp.d.ts +112 -4
- package/dist/transports/taqnyat-whatsapp.js +2 -2
- package/dist/transports/taqnyat-whatsapp.js.map +3 -3
- package/dist/transports/webpush.d.ts +24 -1
- package/dist/transports/webpush.js +2 -2
- package/dist/transports/webpush.js.map +3 -3
- package/dist/webhooks/sndr.js +2 -2
- package/dist/webhooks/sndr.js.map +3 -3
- package/package.json +7 -2
- package/site/content/docs/ai/llms-txt.mdx +2 -0
- package/site/content/docs/channels/email.mdx +2 -1
- package/site/content/docs/channels/push.mdx +3 -1
- package/site/content/docs/decorators/preview.mdx +2 -1
- package/site/content/docs/get-started/entrypoints.mdx +1 -1
- package/site/content/docs/get-started/support-matrix.mdx +2 -2
- package/site/content/docs/guides/vendor-extras-otp.mdx +2 -1
- package/site/content/docs/guides/webhooks.mdx +2 -0
- package/site/content/docs/guides/webpush-interop.mdx +9 -1
- package/site/content/docs/reference/exports.mdx +1 -1
- package/site/content/docs/reference/push-options.mdx +22 -5
- package/site/content/docs/transports/inbucket.mdx +200 -0
- package/site/content/docs/transports/index.mdx +2 -3
- package/site/content/docs/transports/mailpit.mdx +114 -14
- package/site/content/docs/transports/meta.json +5 -4
- package/site/content/docs/transports/smtp.mdx +3 -2
- package/site/content/docs/transports/sndr.mdx +68 -5
- package/site/content/docs/transports/taqnyat.mdx +361 -0
- package/site/content/docs/transports/webpush.mdx +208 -17
- package/site/content/docs/transports/taqnyat-mail.mdx +0 -41
- package/site/content/docs/transports/taqnyat-sms.mdx +0 -41
- package/site/content/docs/transports/taqnyat-whatsapp.mdx +0 -40
|
@@ -5,39 +5,230 @@ icon: Truck
|
|
|
5
5
|
source: "src/transports/webpush.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Encrypt and send browser notifications with VAPID
|
|
8
|
+
Encrypt and send browser notifications with VAPID — the welcome ping, the
|
|
9
|
+
“report ready” alert, or a silent data sync to a service worker.
|
|
9
10
|
|
|
10
|
-
<
|
|
11
|
+
<LiveVerified>
|
|
12
|
+
Browser notification send with VAPID succeeded against a real push service (okengine live verification).
|
|
13
|
+
</LiveVerified>
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
<Callout title="The one rule">
|
|
16
|
+
Create this transport under `createPushSender` and pass a browser
|
|
17
|
+
`subscription` — not an FCM device token.
|
|
18
|
+
</Callout>
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
| --- | --- | --- |
|
|
16
|
-
| `vapidPublicKey` | `string` | required |
|
|
17
|
-
| `vapidPrivateKey` | `string` | required |
|
|
18
|
-
| `subject` | `string` | required |
|
|
19
|
-
| `allowedEndpointHosts` | `string[]` | optional |
|
|
20
|
+
## Quick start
|
|
20
21
|
|
|
21
22
|
<Steps>
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
<Step>
|
|
25
|
+
### Generate VAPID keys
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { generateVapidKeys } from "sently/transports/webpush";
|
|
29
|
+
|
|
30
|
+
const { publicKey, privateKey } = await generateVapidKeys();
|
|
31
|
+
// Store privateKey in env / secrets manager. Use publicKey in the browser subscribe call.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
</Step>
|
|
35
|
+
|
|
36
|
+
<Step>
|
|
37
|
+
### Configure the sender
|
|
23
38
|
|
|
24
39
|
```ts
|
|
25
40
|
import { createPushSender } from "sently/push";
|
|
26
41
|
import { WebPushTransport } from "sently/transports/webpush";
|
|
27
42
|
|
|
28
|
-
const
|
|
43
|
+
const push = createPushSender({
|
|
44
|
+
transport: new WebPushTransport({
|
|
45
|
+
vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,
|
|
46
|
+
vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,
|
|
47
|
+
subject: "mailto:you@example.com",
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
29
50
|
```
|
|
30
51
|
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
</Step>
|
|
53
|
+
|
|
54
|
+
<Step>
|
|
55
|
+
### Send
|
|
33
56
|
|
|
34
57
|
```ts
|
|
35
|
-
await
|
|
58
|
+
await push.send({
|
|
59
|
+
subscription,
|
|
60
|
+
title: "Report ready",
|
|
61
|
+
body: "Your weekly report is ready to view.",
|
|
62
|
+
urgency: "high",
|
|
63
|
+
topic: "report-ready",
|
|
64
|
+
});
|
|
36
65
|
```
|
|
37
66
|
|
|
38
|
-
|
|
67
|
+
</Step>
|
|
68
|
+
|
|
39
69
|
</Steps>
|
|
40
70
|
|
|
41
|
-
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default or requirement |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `vapidPublicKey` | `string` | required — base64url uncompressed P-256 (65 bytes) |
|
|
76
|
+
| `vapidPrivateKey` | `string` | required — base64url raw private key (32 bytes) |
|
|
77
|
+
| `subject` | `string` | required — `mailto:you@example.com` or `https://example.com/contact` |
|
|
78
|
+
| `allowedEndpointHosts` | `string[]` | optional — exact hostnames for private push relays |
|
|
79
|
+
|
|
80
|
+
## Send options (Web Push)
|
|
81
|
+
|
|
82
|
+
| Field | Type | Notes |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `subscription` | `PushSubscription` | required — `endpoint`, `keys.p256dh`, `keys.auth` |
|
|
85
|
+
| `title` / `body` | `string` | required together for a visible notification |
|
|
86
|
+
| `data` | `Record<string, unknown>` | optional; required for `silent` / data-only |
|
|
87
|
+
| `icon` / `badge` / `image` | `string` | Notification API URLs |
|
|
88
|
+
| `tag` | `string` | replace an existing notification with the same tag |
|
|
89
|
+
| `actions` | `{ action, title, icon? }[]` | action buttons |
|
|
90
|
+
| `requireInteraction` | `boolean` | keep open until the user interacts |
|
|
91
|
+
| `renotify` | `boolean` | re-alert when replacing by `tag` |
|
|
92
|
+
| `ttl` | `number` | seconds (default `2419200` / 28 days) |
|
|
93
|
+
| `urgency` | `"very-low" \| "low" \| "normal" \| "high"` | RFC 8030 `Urgency` header |
|
|
94
|
+
| `topic` | `string` | RFC 8030 `Topic` — 1–32 printable ASCII; collapses pending messages |
|
|
95
|
+
| `silent` | `boolean` | encrypt only `data` (no visible fields); requires `data` |
|
|
96
|
+
| `messageId` | `string` | optional client id |
|
|
97
|
+
|
|
98
|
+
## Features
|
|
99
|
+
|
|
100
|
+
Pick a branch. Channel send goes through `push`; key generation is imported from
|
|
101
|
+
`sently/transports/webpush`.
|
|
102
|
+
|
|
103
|
+
<Tabs items={["Send", "Urgency", "Topic", "Rich", "Silent", "Keys"]}>
|
|
104
|
+
<Tab value="Send">
|
|
105
|
+
|
|
106
|
+
Visible notification via the channel sender.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
await push.send({
|
|
110
|
+
subscription,
|
|
111
|
+
title: "Report ready",
|
|
112
|
+
body: "Your weekly report is ready to view.",
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
</Tab>
|
|
117
|
+
<Tab value="Urgency">
|
|
118
|
+
|
|
119
|
+
RFC 8030 `Urgency` header — delivery priority hint for the push service.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
await push.send({
|
|
123
|
+
subscription,
|
|
124
|
+
title: "Payment failed",
|
|
125
|
+
body: "Update your card to keep service running.",
|
|
126
|
+
urgency: "high",
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
</Tab>
|
|
131
|
+
<Tab value="Topic">
|
|
132
|
+
|
|
133
|
+
RFC 8030 `Topic` — a newer message replaces a pending one with the same topic.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await push.send({
|
|
137
|
+
subscription,
|
|
138
|
+
title: "Order update",
|
|
139
|
+
body: "Your package is out for delivery.",
|
|
140
|
+
topic: "order-42",
|
|
141
|
+
ttl: 3600,
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
</Tab>
|
|
146
|
+
<Tab value="Rich">
|
|
147
|
+
|
|
148
|
+
Notification API fields encrypted into the JSON payload for the service worker.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
await push.send({
|
|
152
|
+
subscription,
|
|
153
|
+
title: "Report ready",
|
|
154
|
+
body: "Tap to open your weekly report.",
|
|
155
|
+
icon: "https://example.com/icon.png",
|
|
156
|
+
badge: "https://example.com/badge.png",
|
|
157
|
+
image: "https://example.com/hero.png",
|
|
158
|
+
tag: "report-ready",
|
|
159
|
+
requireInteraction: true,
|
|
160
|
+
renotify: true,
|
|
161
|
+
actions: [{ action: "open", title: "Open" }],
|
|
162
|
+
data: { reportId: "wk-12" },
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</Tab>
|
|
167
|
+
<Tab value="Silent">
|
|
168
|
+
|
|
169
|
+
Data-only / silent push — encrypt `data` without visible notification fields.
|
|
170
|
+
The service worker must handle `push` without calling `showNotification`.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
await push.send({
|
|
174
|
+
subscription,
|
|
175
|
+
silent: true,
|
|
176
|
+
data: { sync: "inbox", since: "2026-08-02T00:00:00Z" },
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Same shape without the flag: omit title/body and pass data.
|
|
180
|
+
await push.send({
|
|
181
|
+
subscription,
|
|
182
|
+
data: { ping: 1 },
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
</Tab>
|
|
187
|
+
<Tab value="Keys">
|
|
188
|
+
|
|
189
|
+
Generate a VAPID key pair (`generateVapidKeys` — not on `createPushSender`).
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { generateVapidKeys } from "sently/transports/webpush";
|
|
193
|
+
|
|
194
|
+
const { publicKey, privateKey } = await generateVapidKeys();
|
|
195
|
+
// Store privateKey in secrets. Use publicKey in pushManager.subscribe.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
</Tab>
|
|
199
|
+
</Tabs>
|
|
200
|
+
|
|
201
|
+
Invalid `urgency` / `topic`, `silent` without `data`, or a partial visible
|
|
202
|
+
payload (`title` without `body`) throw `WebPushError` (`provider: "webpush"`)
|
|
203
|
+
with status `400` before fetch.
|
|
204
|
+
|
|
205
|
+
## Troubleshooting
|
|
206
|
+
|
|
207
|
+
<Accordions>
|
|
208
|
+
<Accordion title="Should I call the provider SDK?">
|
|
209
|
+
No. Use the sently sender; provider-specific extras stay on the transport module.
|
|
210
|
+
</Accordion>
|
|
211
|
+
<Accordion title="Why does construction throw on subject?">
|
|
212
|
+
VAPID `subject` must be a real `mailto:` address or `https:` URL. Values like `@oke.local` (no prefix) are rejected immediately as `WebPushError` so push services never return a confusing 403 later.
|
|
213
|
+
</Accordion>
|
|
214
|
+
<Accordion title="Why does silent send fail?">
|
|
215
|
+
`silent: true` requires `data`. Without it the transport throws `WebPushError` with status `400`.
|
|
216
|
+
</Accordion>
|
|
217
|
+
<Accordion title="Why was my topic rejected?">
|
|
218
|
+
`topic` must be 1–32 printable ASCII characters (no spaces). Invalid values throw before fetch.
|
|
219
|
+
</Accordion>
|
|
220
|
+
</Accordions>
|
|
221
|
+
|
|
222
|
+
## Learn more
|
|
223
|
+
|
|
224
|
+
- [Push channel](/docs/channels/push) — sender, hooks, plugins
|
|
225
|
+
- [Push options](/docs/reference/push-options) — union fields for Web Push and FCM
|
|
226
|
+
- [Web Push interoperability](/docs/guides/webpush-interop) — browser subscription shape
|
|
227
|
+
|
|
228
|
+
## Next
|
|
42
229
|
|
|
43
|
-
<Cards
|
|
230
|
+
<Cards>
|
|
231
|
+
<Card title="Push channel" href="/docs/channels/push" />
|
|
232
|
+
<Card title="Push options" href="/docs/reference/push-options" />
|
|
233
|
+
<Card title="FCM" href="/docs/transports/fcm" />
|
|
234
|
+
</Cards>
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Taqnyat Mail
|
|
3
|
-
description: Send email through the Taqnyat Email API.
|
|
4
|
-
icon: Truck
|
|
5
|
-
source: "src/transports/taqnyat-mail.ts"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Send email through the Taqnyat Email API.
|
|
9
|
-
|
|
10
|
-
<Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
|
|
11
|
-
|
|
12
|
-
## Configuration
|
|
13
|
-
|
|
14
|
-
| Option | Type | Default or requirement |
|
|
15
|
-
| --- | --- | --- |
|
|
16
|
-
| `bearerToken` | `string` | required |
|
|
17
|
-
| `campaignName` | `string` | required |
|
|
18
|
-
|
|
19
|
-
<Steps>
|
|
20
|
-
<Step title="Configure the transport">
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { createMailer } from "sently/mailer";
|
|
24
|
-
import { TaqnyatMailTransport } from "sently/transports/taqnyat-mail";
|
|
25
|
-
|
|
26
|
-
const sender = createMailer({ transport: new TaqnyatMailTransport({ bearerToken: process.env.TAQNYAT_MAIL_TOKEN!, campaignName: "transactional" }) });
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
</Step>
|
|
30
|
-
<Step title="Send with the channel API">
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
await sender.send({ from: "hello@example.com", to: "person@example.com", subject: "Hello", text: "Hi" });
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
</Step>
|
|
37
|
-
</Steps>
|
|
38
|
-
|
|
39
|
-
<Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
|
|
40
|
-
|
|
41
|
-
<Cards><Card title="Email channel" href="/docs/channels/email" /></Cards>
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Taqnyat SMS
|
|
3
|
-
description: Send SMS through the Taqnyat API.
|
|
4
|
-
icon: Truck
|
|
5
|
-
source: "src/transports/taqnyat-sms.ts"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Send SMS through the Taqnyat API.
|
|
9
|
-
|
|
10
|
-
<Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
|
|
11
|
-
|
|
12
|
-
## Configuration
|
|
13
|
-
|
|
14
|
-
| Option | Type | Default or requirement |
|
|
15
|
-
| --- | --- | --- |
|
|
16
|
-
| `bearerToken` | `string` | required |
|
|
17
|
-
| `sender` | `string` | required |
|
|
18
|
-
|
|
19
|
-
<Steps>
|
|
20
|
-
<Step title="Configure the transport">
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { createSmsSender } from "sently/sms";
|
|
24
|
-
import { TaqnyatSmsTransport } from "sently/transports/taqnyat-sms";
|
|
25
|
-
|
|
26
|
-
const sender = createSmsSender({ transport: new TaqnyatSmsTransport({ bearerToken: "...", sender: "MyBrand" }) });
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
</Step>
|
|
30
|
-
<Step title="Send with the channel API">
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
await sender.send({ to: "+15551234567", body: "Hello" });
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
</Step>
|
|
37
|
-
</Steps>
|
|
38
|
-
|
|
39
|
-
<Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
|
|
40
|
-
|
|
41
|
-
<Cards><Card title="Sms channel" href="/docs/channels/sms" /></Cards>
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Taqnyat WhatsApp
|
|
3
|
-
description: Send WhatsApp templates and session text through Taqnyat.
|
|
4
|
-
icon: Truck
|
|
5
|
-
source: "src/transports/taqnyat-whatsapp.ts"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Send WhatsApp templates and session text through Taqnyat.
|
|
9
|
-
|
|
10
|
-
<Callout title="The one rule">Create this transport under the matching sently channel sender.</Callout>
|
|
11
|
-
|
|
12
|
-
## Configuration
|
|
13
|
-
|
|
14
|
-
| Option | Type | Default or requirement |
|
|
15
|
-
| --- | --- | --- |
|
|
16
|
-
| `bearerToken` | `string` | required |
|
|
17
|
-
|
|
18
|
-
<Steps>
|
|
19
|
-
<Step title="Configure the transport">
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import { createWhatsAppSender } from "sently/whatsapp";
|
|
23
|
-
import { TaqnyatWhatsAppTransport } from "sently/transports/taqnyat-whatsapp";
|
|
24
|
-
|
|
25
|
-
const sender = createWhatsAppSender({ transport: new TaqnyatWhatsAppTransport({ bearerToken: "..." }) });
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
</Step>
|
|
29
|
-
<Step title="Send with the channel API">
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
await sender.send({ to: "15551234567", template: { name: "welcome", language: "en_US" } });
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
</Step>
|
|
36
|
-
</Steps>
|
|
37
|
-
|
|
38
|
-
<Accordions><Accordion title="Should I call the provider SDK?">No. Use the sently sender; provider-specific extras stay on the transport instance.</Accordion></Accordions>
|
|
39
|
-
|
|
40
|
-
<Cards><Card title="Whatsapp channel" href="/docs/channels/whatsapp" /></Cards>
|