sently 1.0.1 → 1.2.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.
Files changed (49) hide show
  1. package/AGENTS.md +3 -2
  2. package/CHANGELOG.md +110 -1
  3. package/README.md +20 -5
  4. package/dist/chunk-z1589fjk.js.map +2 -2
  5. package/dist/core/push-types.d.ts +53 -4
  6. package/dist/transports/hostinger.d.ts +128 -0
  7. package/dist/transports/hostinger.js +3 -0
  8. package/dist/transports/hostinger.js.map +10 -0
  9. package/dist/transports/inbucket.d.ts +196 -0
  10. package/dist/transports/inbucket.js +3 -0
  11. package/dist/transports/inbucket.js.map +10 -0
  12. package/dist/transports/mailpit.d.ts +108 -8
  13. package/dist/transports/mailpit.js +2 -2
  14. package/dist/transports/mailpit.js.map +3 -3
  15. package/dist/transports/taqnyat-sms.d.ts +85 -0
  16. package/dist/transports/taqnyat-sms.js +2 -2
  17. package/dist/transports/taqnyat-sms.js.map +3 -3
  18. package/dist/transports/taqnyat-whatsapp.d.ts +112 -4
  19. package/dist/transports/taqnyat-whatsapp.js +2 -2
  20. package/dist/transports/taqnyat-whatsapp.js.map +3 -3
  21. package/dist/transports/webpush.d.ts +19 -0
  22. package/dist/transports/webpush.js +2 -2
  23. package/dist/transports/webpush.js.map +3 -3
  24. package/dist/webhooks/sndr.js +2 -2
  25. package/dist/webhooks/sndr.js.map +3 -3
  26. package/package.json +12 -2
  27. package/site/content/docs/ai/llms-txt.mdx +2 -0
  28. package/site/content/docs/channels/email.mdx +2 -1
  29. package/site/content/docs/channels/push.mdx +3 -1
  30. package/site/content/docs/decorators/preview.mdx +2 -1
  31. package/site/content/docs/get-started/entrypoints.mdx +1 -1
  32. package/site/content/docs/get-started/support-matrix.mdx +2 -2
  33. package/site/content/docs/guides/vendor-extras-otp.mdx +2 -1
  34. package/site/content/docs/guides/webhooks.mdx +2 -0
  35. package/site/content/docs/guides/webpush-interop.mdx +9 -1
  36. package/site/content/docs/reference/exports.mdx +1 -1
  37. package/site/content/docs/reference/push-options.mdx +22 -5
  38. package/site/content/docs/transports/hostinger.mdx +435 -0
  39. package/site/content/docs/transports/inbucket.mdx +200 -0
  40. package/site/content/docs/transports/index.mdx +3 -3
  41. package/site/content/docs/transports/mailpit.mdx +114 -14
  42. package/site/content/docs/transports/meta.json +6 -4
  43. package/site/content/docs/transports/smtp.mdx +3 -2
  44. package/site/content/docs/transports/sndr.mdx +68 -5
  45. package/site/content/docs/transports/taqnyat.mdx +365 -0
  46. package/site/content/docs/transports/webpush.mdx +201 -17
  47. package/site/content/docs/transports/taqnyat-mail.mdx +0 -41
  48. package/site/content/docs/transports/taqnyat-sms.mdx +0 -41
  49. package/site/content/docs/transports/taqnyat-whatsapp.mdx +0 -40
@@ -8,8 +8,13 @@ source: "src/transports/mailpit.ts"
8
8
  Catch outbound email in a local [Mailpit](https://github.com/axllent/mailpit) instance while you develop.
9
9
  Use it for the welcome email or password-reset flow before you point at a production provider.
10
10
 
11
+ <LiveVerified>
12
+ Email send against a local Mailpit instance succeeded in sently’s integration suite (SMTP capture + REST list/get).
13
+ </LiveVerified>
14
+
11
15
  <Callout title="The one rule">
12
16
  Use Mailpit only in development — swap to a production transport before you deploy.
17
+ Vendor extras stay on the `MailpitTransport` instance — never on `createMailer`.
13
18
  </Callout>
14
19
 
15
20
  ## Quick start
@@ -72,27 +77,117 @@ console.log(inbox.messages[0]?.Subject);
72
77
  | `apiAuth` | `{ user, pass }` | — | Basic auth for the UI/API |
73
78
 
74
79
  `provider` is `"mailpit"`. `verify()` checks SMTP; `close()` closes the socket adapter.
80
+ `webUrl` is the UI base (same as `apiUrl`).
81
+
82
+ ## Features
75
83
 
76
- ## REST helpers
84
+ Pick a branch. Channel send goes through `mailer`; everything else is called on `mailpit`.
85
+ Message ids may be a Mailpit id or `"latest"`.
77
86
 
78
- Vendor extras stay on the transport instance (not on `createMailer`):
87
+ <Tabs items={["Send", "List", "Search", "Message", "Headers", "HTML check", "Link check", "Read", "Delete"]}>
88
+ <Tab value="Send">
79
89
 
80
- | Method | Mailpit API | Meaning |
81
- | --- | --- | --- |
82
- | `messages({ limit?, start? })` | `GET /api/v1/messages` | List captured messages (newest first) |
83
- | `getMessage(id)` | `GET /api/v1/message/{id}` | Full message body |
84
- | `deleteMessages(ids)` | `DELETE /api/v1/messages` | Delete by id |
85
- | `deleteAll()` | `DELETE` with empty `IDs` | Clear the inbox |
86
- | `webUrl` | — | UI base URL (same as `apiUrl`) |
90
+ Transactional send via the channel mailer (SMTP into Mailpit).
87
91
 
88
92
  ```ts
93
+ await mailer.send({
94
+ from: "dev@example.com",
95
+ to: "you@example.com",
96
+ subject: "Welcome",
97
+ html: "<h1>Hello</h1><a href=\"https://example.com\">Go</a>",
98
+ text: "Hello",
99
+ });
100
+ ```
101
+
102
+ </Tab>
103
+ <Tab value="List">
104
+
105
+ List captured messages (`GET /api/v1/messages`), newest first.
106
+
107
+ ```ts
108
+ const inbox = await mailpit.messages({ limit: 10, start: 0 });
109
+ console.log(inbox.total, inbox.messages[0]?.Subject);
110
+ ```
111
+
112
+ </Tab>
113
+ <Tab value="Search">
114
+
115
+ Find a message with Mailpit’s query syntax (`subject:`, `to:`, `tag:`, …).
116
+
117
+ ```ts
118
+ const found = await mailpit.search("subject:Welcome", { limit: 1 });
119
+ console.log(found.messages[0]?.ID);
120
+ ```
121
+
122
+ </Tab>
123
+ <Tab value="Message">
124
+
125
+ Full body for the message id (or `"latest"`).
126
+
127
+ ```ts
128
+ const full = await mailpit.getMessage("latest");
129
+ console.log(full.Text, full.HTML);
130
+ ```
131
+
132
+ </Tab>
133
+ <Tab value="Headers">
134
+
135
+ Header map for assertions (`Message-Id`, custom headers, …).
136
+
137
+ ```ts
138
+ const headers = await mailpit.getHeaders("latest");
139
+ console.log(headers.Subject, headers["Message-Id"]);
140
+ ```
141
+
142
+ </Tab>
143
+ <Tab value="HTML check">
144
+
145
+ Client HTML/CSS compatibility score from Mailpit’s checker.
146
+
147
+ ```ts
148
+ const html = await mailpit.htmlCheck("latest");
149
+ console.log(html.Total.Supported, html.Total.Unsupported, html.Warnings.length);
150
+ ```
151
+
152
+ Needs an HTML part — plain-text-only messages can return `400`.
153
+
154
+ </Tab>
155
+ <Tab value="Link check">
156
+
157
+ Probe links and images in the message (`follow` optional).
158
+
159
+ ```ts
160
+ const links = await mailpit.linkCheck("latest", { follow: true });
161
+ console.log(links.Errors, links.Links);
162
+ ```
163
+
164
+ </Tab>
165
+ <Tab value="Read">
166
+
167
+ Mark messages read or unread between assertions.
168
+
169
+ ```ts
170
+ const inbox = await mailpit.messages({ limit: 1 });
171
+ await mailpit.setRead([inbox.messages[0]!.ID], true);
172
+ // empty ids → update every message
173
+ await mailpit.setRead([], false);
174
+ ```
175
+
176
+ </Tab>
177
+ <Tab value="Delete">
178
+
179
+ Delete by id, or clear the whole inbox.
180
+
181
+ ```ts
182
+ await mailpit.deleteMessages(["abc"]);
89
183
  await mailpit.deleteAll();
90
- await mailer.send({ from: "a@test.com", to: "b@test.com", subject: "T", text: "ok" });
91
- const list = await mailpit.messages({ limit: 1 });
92
- const full = await mailpit.getMessage(list.messages[0]!.ID);
93
184
  ```
94
185
 
95
- REST failures throw `MailpitError` (`provider: "mailpit"`). Empty `getMessage("")` throws with status `400`.
186
+ </Tab>
187
+ </Tabs>
188
+
189
+ REST failures throw `MailpitError` (`provider: "mailpit"`).
190
+ Empty `getMessage("")` / `search("")` throws with status `400`.
96
191
 
97
192
  ## Troubleshooting
98
193
 
@@ -103,6 +198,9 @@ REST failures throw `MailpitError` (`provider: "mailpit"`). Empty `getMessage(""
103
198
  <Accordion title="API helpers fail but send works">
104
199
  SMTP and the UI/API can bind to different hosts. Set `apiUrl` (and `apiAuth` if the UI requires Basic auth).
105
200
  </Accordion>
201
+ <Accordion title="htmlCheck returns 400">
202
+ The message has no HTML part, or Mailpit could not parse it. Send `html` (not only `text`) and retry with `"latest"` or the message id.
203
+ </Accordion>
106
204
  <Accordion title="Should I use createSMTPMailer instead?">
107
205
  Yes, if you only need SMTP. `MailpitTransport` adds local defaults and REST helpers for tests and inspection.
108
206
  </Accordion>
@@ -110,14 +208,16 @@ REST failures throw `MailpitError` (`provider: "mailpit"`). Empty `getMessage(""
110
208
 
111
209
  ## Learn more
112
210
 
211
+ - [Inbucket](./inbucket) — another local SMTP catcher with a mailbox REST API
113
212
  - [SMTP](./smtp) — generic SMTP when you are not on Mailpit
114
213
  - [Preview](/docs/decorators/preview) — write `.eml` files to disk instead
115
214
  - [Email channel](/docs/channels/email) — `createMailer` contract
215
+ - [Mailpit API](https://mailpit.axllent.org/docs/api-v1/) — full REST surface on the catcher
116
216
 
117
217
  ## Next
118
218
 
119
219
  <Cards>
220
+ <Card title="Inbucket" href="/docs/transports/inbucket" />
120
221
  <Card title="Email channel" href="/docs/channels/email" />
121
- <Card title="SMTP" href="/docs/transports/smtp" />
122
222
  <Card title="Transports" href="/docs/transports" />
123
223
  </Cards>
@@ -3,6 +3,8 @@
3
3
  "icon": "Truck",
4
4
  "pages": [
5
5
  "index",
6
+ "---Multi-channel---",
7
+ "taqnyat",
6
8
  "---Email---",
7
9
  "smtp",
8
10
  "resend",
@@ -15,19 +17,19 @@
15
17
  "plunk",
16
18
  "sparkpost",
17
19
  "mailtrap",
18
- "mailpit",
19
20
  "loops",
20
21
  "cloudflare-email",
21
22
  "sndr",
22
- "taqnyat-mail",
23
+ "hostinger",
24
+ "---Email Dev---",
25
+ "mailpit",
26
+ "inbucket",
23
27
  "---SMS---",
24
28
  "twilio-sms",
25
- "taqnyat-sms",
26
29
  "msegat",
27
30
  "unifonic",
28
31
  "---WhatsApp---",
29
32
  "whatsapp-cloud",
30
- "taqnyat-whatsapp",
31
33
  "---Push---",
32
34
  "webpush",
33
35
  "fcm"
@@ -55,7 +55,7 @@ await mailer.send({
55
55
  | `pool` | `boolean` | `false` |
56
56
  | `dkim` | `DKIMConfig` | optional |
57
57
 
58
- For local capture with defaults and REST helpers, use [Mailpit](./mailpit) instead of hand-wiring `localhost:1025`.
58
+ For local capture with defaults and REST helpers, use [Mailpit](./mailpit) or [Inbucket](./inbucket) instead of hand-wiring SMTP ports.
59
59
 
60
60
  ## Troubleshooting
61
61
 
@@ -67,13 +67,14 @@ For local capture with defaults and REST helpers, use [Mailpit](./mailpit) inste
67
67
  Pass an explicit transport, or use `createSMTPMailer` for relay configuration.
68
68
  </Accordion>
69
69
  <Accordion title="Local development without a real relay">
70
- Use [Mailpit](./mailpit) (`sently/transports/mailpit`) or [Preview](/docs/decorators/preview).
70
+ Use [Mailpit](./mailpit), [Inbucket](./inbucket), or [Preview](/docs/decorators/preview).
71
71
  </Accordion>
72
72
  </Accordions>
73
73
 
74
74
  ## Learn more
75
75
 
76
76
  - [Mailpit](./mailpit) — local SMTP catcher with inbox API
77
+ - [Inbucket](./inbucket) — local SMTP catcher with mailbox REST API
77
78
  - [Email channel](/docs/channels/email) — `createMailer` / `createSMTPMailer`
78
79
  - [Runtimes](/docs/get-started/runtimes) — socket adapters
79
80
 
@@ -5,8 +5,12 @@ icon: Truck
5
5
  source: "src/transports/sndr.ts"
6
6
  ---
7
7
 
8
- Use SNDR when you want an HTTP email API for receipts, sign-in mail, and other app-triggered messages.
9
- Wire `SndrTransport` into `createMailer` — do not call the vendor SDK from application code.
8
+ Use SNDR for app-triggered email — receipts, sign-in mail, and other transactional sends — over HTTPS JSON.
9
+ Wire `SndrTransport` into `createMailer`; do not call `@rkiza/sndr` from application code.
10
+
11
+ <LiveVerified>
12
+ Email send against SNDR’s production API succeeded in sently’s opt-in live suite (verified domain + real recipient).
13
+ </LiveVerified>
10
14
 
11
15
  <Callout title="The one rule">
12
16
  Import from `sently/transports/sndr`, pass the transport to `await createMailer(...)`, and send
@@ -55,7 +59,7 @@ console.log(result.messageId); // e.g. em_…
55
59
  console.log(result.response); // e.g. queued
56
60
  ```
57
61
 
58
- `from` must use a domain verified in the SNDR dashboard.
62
+ `from` must use a domain verified in the [SNDR dashboard](https://www.sndr.sh/) (SPF, DKIM, DMARC).
59
63
 
60
64
  </Step>
61
65
 
@@ -82,10 +86,12 @@ console.log(result.response); // e.g. queued
82
86
  | `idempotencyKey` / `messageId` | `Idempotency-Key` | Sent when present. |
83
87
  | `data` | `variables` | Only when a template id is set. |
84
88
 
89
+ `POST /v1/send` is idempotent when `Idempotency-Key` is supplied.
90
+
85
91
  ## Templates
86
92
 
87
93
  Set a template with the `x-sndr-template-id` header (or `defaultTemplateId` on the transport).
88
- Pass template variables through `data`.
94
+ Pass template variables through `data` (`{{ variable }}` placeholders on SNDR).
89
95
 
90
96
  ```ts
91
97
  import { SNDR_TEMPLATE_ID_HEADER } from "sently/transports/sndr";
@@ -105,13 +111,57 @@ await mailer.send({
105
111
 
106
112
  Failed HTTP responses throw `SndrError` (`SentlyError`) with the API `error.message` when present.
107
113
 
114
+ | SNDR `error.code` (common) | Meaning |
115
+ | --- | --- |
116
+ | `invalid_request` | Malformed payload |
117
+ | `unauthenticated` | Missing or invalid API key |
118
+ | `rate_limited` | Slow down |
119
+ | `domain_not_verified` | Verify the sending domain first |
120
+ | `recipient_suppressed` | Address is on the suppression list |
121
+
122
+ ## Webhooks
123
+
124
+ Import from `sently/webhooks/sndr`. Verify `X-Sndr-Signature` (`t=…,v1=…`) over the **raw** body, then `parse`.
125
+
126
+ | SNDR event | Normalized `EmailEvent.type` |
127
+ | --- | --- |
128
+ | `email.queued` | `deferred` |
129
+ | `email.delivered` | `delivered` |
130
+ | `email.bounced` | `bounced` |
131
+ | `email.failed` | `unknown` |
132
+ | `email.complained` | `complained` |
133
+ | `email.opened` | `opened` |
134
+ | `email.clicked` | `clicked` |
135
+ | `email.unsubscribed` | `unknown` |
136
+
137
+ See [Webhooks](/docs/guides/webhooks#sndr-signatures) for the HMAC details.
138
+
139
+ ## What sently covers vs SNDR platform
140
+
141
+ | Surface | In sently | Notes |
142
+ | --- | --- | --- |
143
+ | Send (`POST /v1/send`) | Yes — `SndrTransport` | Channel: `createMailer` |
144
+ | Templates + variables | Yes | Header / `defaultTemplateId` + `data` |
145
+ | Idempotency | Yes | `idempotencyKey` / `messageId` |
146
+ | Domain verify | Yes — `verify()` | `GET /v1/domains` |
147
+ | Delivery webhooks | Yes — `sently/webhooks/sndr` | Signed parse |
148
+ | Contacts / contact groups / broadcasts | No | SNDR dashboard / their API |
149
+ | Analytics / suppressions admin | No | Use SNDR dashboard |
150
+ | Attachments on HTTP send | Not mapped | Prefer SMTP or ask SNDR if their send API gains attachments |
151
+
108
152
  ## Troubleshooting
109
153
 
110
154
  <Accordions>
111
155
 
112
156
  <Accordion title="from domain is not verified">
113
157
 
114
- SNDR rejects sends from unverified domains. Verify DNS in the SNDR dashboard, then retry with that domain in `from`.
158
+ SNDR rejects sends from unverified domains. Add SPF, DKIM, and DMARC in the dashboard Domains page, click Verify, then retry with that domain in `from`.
159
+
160
+ </Accordion>
161
+
162
+ <Accordion title="recipient_suppressed">
163
+
164
+ The address is on SNDR’s suppression list (bounce/complaint). Remove it only if the recipient opted back in; otherwise pick another address.
115
165
 
116
166
  </Accordion>
117
167
 
@@ -129,6 +179,19 @@ No. Use `createMailer` plus `SndrTransport`. Keep vendor extras off the shared e
129
179
 
130
180
  </Accordions>
131
181
 
182
+ ## Contact & resources
183
+
184
+ | Contact | Detail |
185
+ | --- | --- |
186
+ | Direct email | [sndr@rkiza.sa](mailto:sndr@rkiza.sa) |
187
+ | Contact form | [sndr.sh/contact](https://www.sndr.sh/contact) |
188
+ | Docs | [sndr.sh/docs](https://www.sndr.sh/docs) |
189
+ | API reference | [API Reference](https://www.sndr.sh/docs/api-reference) |
190
+ | Status | [sndr.sh/status](https://www.sndr.sh/status) |
191
+ | X / Twitter | [@usesndr](https://x.com/usesndr) |
192
+ | LinkedIn | [SNDR company](https://www.linkedin.com/company/104143949) |
193
+ | GitHub | [github.com/rkiza/sndr](https://github.com/rkiza/sndr) |
194
+
132
195
  ## Learn more
133
196
 
134
197
  - [Email channel](/docs/channels/email) — mailer options and send pipeline