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
@@ -0,0 +1,435 @@
1
+ ---
2
+ title: Hostinger
3
+ description: Send email from a Hostinger mailbox through the Mail API or ready SMTP config.
4
+ icon: Truck
5
+ source: "src/transports/hostinger.ts"
6
+ ---
7
+
8
+ Hostinger Email gives you a branded mailbox. Wire it into sently two ways — the Mail API over HTTPS, or the SMTP relay with Hostinger defaults already filled in.
9
+
10
+ <Callout title="The one rule">
11
+ Use `createMailer` with `HostingerTransport` for the Mail API, or `createSMTPMailer` with `hostingerSmtpConfig` for SMTP.
12
+ Vendor extras (`listMailboxes`, `sendReply`, `sendForward`) stay on the transport instance — never on the channel sender.
13
+ </Callout>
14
+
15
+ | Path | Import | When to use |
16
+ | --- | --- | --- |
17
+ | Mail API | `sently/transports/hostinger` → `HostingerTransport` | Tokens, Agentic Mail, reply/forward threading |
18
+ | SMTP | `hostingerSmtpConfig` + `sently/smtp` | Classic relay, clients, apps that already speak SMTP |
19
+
20
+ Official references: [Hostinger API](https://developers.hostinger.com/), [Mail API](https://api.mail.hostinger.com/), [SMTP ports](https://www.hostinger.com/tutorials/smtp-port/).
21
+
22
+ ## Mail API
23
+
24
+ | Option | Type | Default or requirement |
25
+ | --- | --- | --- |
26
+ | `token` | `string` | required — Agentic Mail API token (shown once) |
27
+ | `mailbox` | `string` | required — mailbox resource ID, e.g. `AC1a2b3c4d5e6f7g` |
28
+ | `baseUrl` | `string` | `https://api.mail.hostinger.com` |
29
+
30
+ The API sends **from the managed mailbox**. `from` only contributes the sender display name. A copy is saved to the Sent folder on every successful send (`204 No Content`).
31
+
32
+ ### Setup
33
+
34
+ <Steps>
35
+ <Step title="Create an API token">
36
+
37
+ In hPanel open **Emails → your domain → Agentic Mail → API access**, create a token scoped to the mailbox, and copy it — it is shown only once.
38
+
39
+ </Step>
40
+ <Step title="Discover the mailbox resource ID">
41
+
42
+ ```ts
43
+ import { HostingerTransport } from "sently/transports/hostinger";
44
+
45
+ const hostinger = new HostingerTransport({
46
+ token: process.env.HOSTINGER_API_TOKEN!,
47
+ mailbox: "AC_placeholder", // replaced after listMailboxes()
48
+ });
49
+
50
+ const mailboxes = await hostinger.listMailboxes();
51
+ // [{ resourceId: "AC1a2b3c4d5e6f7g", address: "you@yourdomain.com" }]
52
+ ```
53
+
54
+ </Step>
55
+ <Step title="Create the mailer and send">
56
+
57
+ ```ts
58
+ import { createMailer } from "sently/mailer";
59
+ import { HostingerTransport } from "sently/transports/hostinger";
60
+
61
+ const hostinger = new HostingerTransport({
62
+ token: process.env.HOSTINGER_API_TOKEN!,
63
+ mailbox: process.env.HOSTINGER_MAILBOX_ID!,
64
+ });
65
+ const mailer = await createMailer({ transport: hostinger });
66
+
67
+ const result = await mailer.send({
68
+ from: "Acme <you@yourdomain.com>",
69
+ to: "person@example.com",
70
+ subject: "Hello",
71
+ text: "Sent through the Hostinger Mail API",
72
+ });
73
+ console.log(result.response); // Message sent and saved to the Sent folder
74
+ ```
75
+
76
+ </Step>
77
+ </Steps>
78
+
79
+ ### Features
80
+
81
+ Pick a branch. Channel send goes through `mailer`; extras stay on `hostinger`.
82
+
83
+ <Tabs items={["Send", "HTML", "Attachments", "CC / BCC", "Reply", "Forward", "Mailboxes", "Verify"]}>
84
+ <Tab value="Send">
85
+
86
+ Transactional email via the channel mailer.
87
+
88
+ ```ts
89
+ await mailer.send({
90
+ from: "you@yourdomain.com",
91
+ to: "person@example.com",
92
+ subject: "Order confirmed",
93
+ text: "Thanks for your order.",
94
+ });
95
+ ```
96
+
97
+ At least one of `to`, `cc`, or `bcc` must be present. There is no batch endpoint — `sendBulk` sends one by one.
98
+
99
+ </Tab>
100
+ <Tab value="HTML">
101
+
102
+ Send HTML, plain text, or both. A display name on `from` becomes API `displayName`.
103
+
104
+ ```ts
105
+ await mailer.send({
106
+ from: "Acme Billing <billing@yourdomain.com>",
107
+ to: "person@example.com",
108
+ subject: "Invoice ready",
109
+ text: "Your invoice is ready.",
110
+ html: "<p>Your invoice is <strong>ready</strong>.</p>",
111
+ });
112
+ ```
113
+
114
+ `replyTo`, custom `headers`, and `priority` are not mapped by the Mail API — use SMTP if you need them.
115
+
116
+ </Tab>
117
+ <Tab value="Attachments">
118
+
119
+ Attachments are base64-encoded for you. Inline images use `contentId` → API `cid`.
120
+
121
+ ```ts
122
+ await mailer.send({
123
+ from: "you@yourdomain.com",
124
+ to: "person@example.com",
125
+ subject: "Report",
126
+ html: '<p>Logo: <img src="cid:logo" /></p>',
127
+ attachments: [
128
+ {
129
+ filename: "report.pdf",
130
+ content: pdfBytes,
131
+ contentType: "application/pdf",
132
+ },
133
+ {
134
+ filename: "logo.png",
135
+ content: logoBytes,
136
+ contentType: "image/png",
137
+ contentId: "logo",
138
+ inline: true,
139
+ },
140
+ ],
141
+ });
142
+ ```
143
+
144
+ </Tab>
145
+ <Tab value="CC / BCC">
146
+
147
+ Carbon-copy and blind carbon-copy map to API `cc` / `bcc` arrays.
148
+
149
+ ```ts
150
+ await mailer.send({
151
+ from: "you@yourdomain.com",
152
+ to: "person@example.com",
153
+ cc: ["ops@example.com", "lead@example.com"],
154
+ bcc: "audit@example.com",
155
+ subject: "Weekly update",
156
+ text: "Status for the week.",
157
+ });
158
+ ```
159
+
160
+ </Tab>
161
+ <Tab value="Reply">
162
+
163
+ Reply to a mailbox message by folder + IMAP UID. Flags the source `\Answered`.
164
+
165
+ ```ts
166
+ await hostinger.sendReply(
167
+ {
168
+ from: "you@yourdomain.com",
169
+ to: "person@example.com",
170
+ subject: "Re: Support request",
171
+ text: "Thanks — we are looking into it.",
172
+ },
173
+ { folder: "INBOX", uid: 42 },
174
+ );
175
+ ```
176
+
177
+ Mutually exclusive with **Forward**. Call this on the transport, not on `mailer`.
178
+
179
+ </Tab>
180
+ <Tab value="Forward">
181
+
182
+ Forward a mailbox message by folder + IMAP UID. Flags the source `$forwarded`.
183
+
184
+ ```ts
185
+ await hostinger.sendForward(
186
+ {
187
+ from: "you@yourdomain.com",
188
+ to: "team@example.com",
189
+ subject: "Fwd: Support request",
190
+ text: "Passing this along.",
191
+ },
192
+ { folder: "INBOX", uid: 42 },
193
+ );
194
+ ```
195
+
196
+ Mutually exclusive with **Reply**.
197
+
198
+ </Tab>
199
+ <Tab value="Mailboxes">
200
+
201
+ List every mailbox the token can manage — required to learn the `resourceId`.
202
+
203
+ ```ts
204
+ const mailboxes = await hostinger.listMailboxes();
205
+ for (const box of mailboxes) {
206
+ console.log(box.resourceId, box.address);
207
+ }
208
+ ```
209
+
210
+ Resource IDs look like `AC1a2b3c4d5e6f7g`. Pass the matching one as `mailbox` in the transport config.
211
+
212
+ </Tab>
213
+ <Tab value="Verify">
214
+
215
+ Check the token and that the configured `mailbox` is in its scope — without sending mail.
216
+
217
+ ```ts
218
+ const check = await hostinger.verify();
219
+ // { ok: true, provider: "hostinger",
220
+ // message: "API token is valid — sending as you@yourdomain.com" }
221
+
222
+ const viaMailer = await mailer.verify(); // same check through the channel sender
223
+ ```
224
+
225
+ </Tab>
226
+ </Tabs>
227
+
228
+ ### Mail options mapping
229
+
230
+ | Mail option | Hostinger field | Notes |
231
+ | --- | --- | --- |
232
+ | `from` name | `displayName` | Address is the managed mailbox |
233
+ | `to` / `cc` / `bcc` | `to` / `cc` / `bcc` | Email arrays |
234
+ | `subject` | `subject` | |
235
+ | `text` / `html` | `text` / `html` | Either or both |
236
+ | `attachments` | `attachments` | Base64 `content`, optional `contentType` / `cid` |
237
+ | `messageId` | — | Kept on `SendResult` (API returns empty body) |
238
+ | `replyTo` / `headers` / `priority` | — | Not supported on the Mail API |
239
+
240
+ ## SMTP
241
+
242
+ <LiveVerified>
243
+ SMTP send against Hostinger’s production relay (`smtp.hostinger.com`) succeeded previously with a real mailbox — SSL port 465 and STARTTLS port 587.
244
+ </LiveVerified>
245
+
246
+ Ready Hostinger relay settings — no host/port guesswork. Pass `hostingerSmtpConfig(...)` straight into `createSMTPMailer`.
247
+
248
+ | Setting | Value |
249
+ | --- | --- |
250
+ | Host | `smtp.hostinger.com` |
251
+ | Port `465` | SSL/TLS on connect — **default** |
252
+ | Port `587` | STARTTLS |
253
+ | Username | Full mailbox address |
254
+ | Password | Mailbox password from hPanel |
255
+
256
+ Hostinger supports ports **465** and **587** only — not `2525` ([SMTP ports guide](https://www.hostinger.com/tutorials/smtp-port/)).
257
+
258
+ ### Setup
259
+
260
+ <Steps>
261
+ <Step title="Copy SMTP credentials from hPanel">
262
+
263
+ **Emails → your domain → Configuration settings → Manual Configuration** — take the outgoing server host, port, and mailbox password.
264
+
265
+ </Step>
266
+ <Step title="Create the SMTP mailer">
267
+
268
+ ```ts
269
+ import { createSMTPMailer } from "sently/smtp";
270
+ import { hostingerSmtpConfig } from "sently/transports/hostinger";
271
+
272
+ const mailer = await createSMTPMailer(
273
+ hostingerSmtpConfig({
274
+ user: "you@yourdomain.com",
275
+ pass: process.env.HOSTINGER_SMTP_PASSWORD!,
276
+ }),
277
+ );
278
+ ```
279
+
280
+ </Step>
281
+ <Step title="Send with the channel API">
282
+
283
+ ```ts
284
+ await mailer.send({
285
+ from: "you@yourdomain.com",
286
+ to: "person@example.com",
287
+ subject: "Hello",
288
+ text: "Sent through Hostinger SMTP",
289
+ });
290
+ ```
291
+
292
+ </Step>
293
+ </Steps>
294
+
295
+ ### Features
296
+
297
+ <Tabs items={["SSL 465", "STARTTLS 587", "Pool", "Full MIME"]}>
298
+ <Tab value="SSL 465">
299
+
300
+ Default — implicit TLS on connect.
301
+
302
+ ```ts
303
+ const mailer = await createSMTPMailer(
304
+ hostingerSmtpConfig({
305
+ user: "you@yourdomain.com",
306
+ pass: process.env.HOSTINGER_SMTP_PASSWORD!,
307
+ // port: 465, // default
308
+ }),
309
+ );
310
+ ```
311
+
312
+ Exports: `HOSTINGER_SMTP_HOST`, `HOSTINGER_SMTP_PORT_SSL` (`465`).
313
+
314
+ </Tab>
315
+ <Tab value="STARTTLS 587">
316
+
317
+ Plain connect, then upgrade with STARTTLS.
318
+
319
+ ```ts
320
+ const mailer = await createSMTPMailer(
321
+ hostingerSmtpConfig({
322
+ user: "you@yourdomain.com",
323
+ pass: process.env.HOSTINGER_SMTP_PASSWORD!,
324
+ port: 587,
325
+ }),
326
+ );
327
+ ```
328
+
329
+ `secure` is set to `false` automatically. Constant: `HOSTINGER_SMTP_PORT_STARTTLS`.
330
+
331
+ </Tab>
332
+ <Tab value="Pool">
333
+
334
+ Reuse SMTP connections under load.
335
+
336
+ ```ts
337
+ const mailer = await createSMTPMailer(
338
+ hostingerSmtpConfig({
339
+ user: "you@yourdomain.com",
340
+ pass: process.env.HOSTINGER_SMTP_PASSWORD!,
341
+ pool: true,
342
+ maxConnections: 3,
343
+ }),
344
+ );
345
+ ```
346
+
347
+ </Tab>
348
+ <Tab value="Full MIME">
349
+
350
+ SMTP carries the full MIME message — `replyTo`, custom headers, `priority`, DKIM, and attachments work as on any other SMTP relay.
351
+
352
+ ```ts
353
+ await mailer.send({
354
+ from: "Acme <you@yourdomain.com>",
355
+ to: "person@example.com",
356
+ replyTo: "support@yourdomain.com",
357
+ subject: "Hello",
358
+ html: "<p>Hi</p>",
359
+ headers: { "X-Campaign": "welcome" },
360
+ priority: "high",
361
+ });
362
+ ```
363
+
364
+ See [SMTP](./smtp) for pooling, DKIM, and adapter details.
365
+
366
+ </Tab>
367
+ </Tabs>
368
+
369
+ ### Config helper
370
+
371
+ | Option | Type | Default |
372
+ | --- | --- | --- |
373
+ | `user` | `string` | required — full mailbox address |
374
+ | `pass` | `string` | required — mailbox password |
375
+ | `port` | `465 \| 587` | `465` |
376
+ | `pool` | `boolean` | unset |
377
+ | `maxConnections` | `number` | unset (SMTP default `5` when pooled) |
378
+
379
+ ## Mail API vs SMTP
380
+
381
+ | Need | Prefer |
382
+ | --- | --- |
383
+ | Agentic Mail token / mailbox resource ID | Mail API |
384
+ | Reply / forward by IMAP UID | Mail API (`sendReply` / `sendForward`) |
385
+ | `replyTo`, custom headers, `priority`, DKIM | SMTP |
386
+ | Existing SMTP client / form stack | SMTP |
387
+ | Sent-folder copy via Hostinger’s API | Mail API (automatic) |
388
+
389
+ ## Troubleshooting
390
+
391
+ <Accordions>
392
+ <Accordion title="401 — Missing or invalid credentials">
393
+ The Mail API token is wrong or revoked. Create a fresh token under Agentic Mail → API access; tokens are shown only once.
394
+ </Accordion>
395
+ <Accordion title="403 — Token is not authorized to manage the requested mailbox">
396
+ The `mailbox` resource ID is outside the token's scope. Open the **Mailboxes** branch (`listMailboxes`) or recreate the token with access to that mailbox.
397
+ </Accordion>
398
+ <Accordion title="422 — Request payload failed validation">
399
+ At least one of `to`, `cc`, or `bcc` must be present. The error's `params` map names the fields that failed. Reply and Forward are mutually exclusive.
400
+ </Accordion>
401
+ <Accordion title="502 — Upstream service unavailable">
402
+ Hostinger’s upstream mail service returned an unexpected response. Retry with a [Retry](/docs/decorators/retry) decorator, or fall back to SMTP.
403
+ </Accordion>
404
+ <Accordion title="SMTP auth fails">
405
+ Username must be the **full** mailbox address. Copy the password from hPanel → Configuration settings → Manual Configuration. Use port `465` (`secure: true`) or `587` only.
406
+ </Accordion>
407
+ <Accordion title="Should I call the provider SDK?">
408
+ No. Use the matching sently channel sender; open a feature branch above for vendor extras on the transport.
409
+ </Accordion>
410
+ </Accordions>
411
+
412
+ ## Contact & resources
413
+
414
+ | Resource | Link |
415
+ | --- | --- |
416
+ | Developers portal | [developers.hostinger.com](https://developers.hostinger.com/) |
417
+ | Mail API reference | [api.mail.hostinger.com](https://api.mail.hostinger.com/) |
418
+ | SMTP ports tutorial | [hostinger.com/tutorials/smtp-port](https://www.hostinger.com/tutorials/smtp-port/) |
419
+ | Business email product | [hostinger.com/business-email](https://www.hostinger.com/business-email) |
420
+ | hPanel | Emails → domain → Agentic Mail / Configuration settings |
421
+
422
+ ## Learn more
423
+
424
+ - [Email channel](/docs/channels/email) — mailer options and send pipeline
425
+ - [SMTP](./smtp) — relay pooling, DKIM, adapters
426
+ - [Retry](/docs/decorators/retry) — wrap any transport on 429 / 5xx
427
+ - [Support matrix](/docs/get-started/support-matrix) — Supported vs Available
428
+
429
+ ## Next
430
+
431
+ <Cards>
432
+ <Card title="Email channel" href="/docs/channels/email" />
433
+ <Card title="SMTP" href="/docs/transports/smtp" />
434
+ <Card title="Transports" href="/docs/transports" />
435
+ </Cards>
@@ -0,0 +1,200 @@
1
+ ---
2
+ title: Inbucket
3
+ description: Catch outbound email in a local Inbucket instance during development.
4
+ icon: Inbox
5
+ source: "src/transports/inbucket.ts"
6
+ ---
7
+
8
+ Catch outbound email in a local [Inbucket](https://inbucket.org/) instance while you develop.
9
+ Use it for the welcome email or password-reset flow before you point at a production provider.
10
+
11
+ <LiveVerified>
12
+ Email send against a local Inbucket instance succeeded in sently’s live suite (SMTP capture + REST list/get/source/markSeen/purge).
13
+ </LiveVerified>
14
+
15
+ <Callout title="The one rule">
16
+ Use Inbucket only in development — swap to a production transport before you deploy.
17
+ Vendor extras stay on the `InbucketTransport` instance — never on `createMailer`.
18
+ </Callout>
19
+
20
+ ## Quick start
21
+
22
+ Start Inbucket (SMTP `2500`, UI `9000`):
23
+
24
+ ```sh
25
+ docker run -d --rm --name inbucket -p 9000:9000 -p 2500:2500 -p 1100:1100 inbucket/inbucket
26
+ ```
27
+
28
+ <Steps>
29
+ <Step title="Create the transport">
30
+
31
+ ```ts
32
+ import { createMailer } from "sently/mailer";
33
+ import { InbucketTransport } from "sently/transports/inbucket";
34
+
35
+ const inbucket = new InbucketTransport();
36
+ const mailer = await createMailer({ transport: inbucket });
37
+ ```
38
+
39
+ </Step>
40
+ <Step title="Send through the mailer">
41
+
42
+ ```ts
43
+ await mailer.send({
44
+ from: "dev@example.com",
45
+ to: "you@example.com",
46
+ subject: "Hello",
47
+ text: "Captured by Inbucket",
48
+ });
49
+ ```
50
+
51
+ </Step>
52
+ <Step title="Inspect the mailbox">
53
+
54
+ Open `http://localhost:9000`, or list messages from code.
55
+ Stock Inbucket stores `you@example.com` under mailbox `you`:
56
+
57
+ ```ts
58
+ const mailbox = inbucket.mailboxForAddress("you@example.com");
59
+ const inbox = await inbucket.listMailbox(mailbox);
60
+ console.log(inbox[0]?.subject);
61
+ ```
62
+
63
+ </Step>
64
+ </Steps>
65
+
66
+ ## Configuration
67
+
68
+ | Option | Type | Default | Meaning |
69
+ | --- | --- | --- | --- |
70
+ | `host` | `string` | `"localhost"` | SMTP hostname |
71
+ | `port` | `number` | `2500` | SMTP port |
72
+ | `secure` | `boolean` | `false` | Implicit TLS on connect |
73
+ | `requireTLS` | `boolean` | `false` | Refuse AUTH without TLS |
74
+ | `auth` | `SMTPAuth` | — | Optional SMTP credentials |
75
+ | `tls` | `TLSOptions` | — | TLS options when TLS is enabled |
76
+ | `connectionTimeout` | `number` | — | Socket connect timeout (ms) |
77
+ | `adapter` | `SocketAdapter` | auto-detected | Runtime TCP adapter |
78
+ | `apiUrl` | `string` | `"http://localhost:9000"` | Web UI / REST API base |
79
+ | `mailboxNaming` | `"local" \| "full" \| "domain"` | `"local"` | How `mailboxForAddress` maps an email |
80
+
81
+ `provider` is `"inbucket"`. `verify()` checks SMTP; `close()` closes the socket adapter.
82
+ `webUrl` is the UI base (same as `apiUrl`).
83
+
84
+ ## Features
85
+
86
+ Pick a branch. Channel send goes through `mailer`; everything else is called on `inbucket`.
87
+ Inbucket is mailbox-centric — pass a mailbox name (or derive it with `mailboxForAddress`).
88
+
89
+ <Tabs items={["Send", "List", "Message", "Source", "Mark seen", "Delete", "Purge"]}>
90
+ <Tab value="Send">
91
+
92
+ Transactional send via the channel mailer (SMTP into Inbucket).
93
+
94
+ ```ts
95
+ await mailer.send({
96
+ from: "dev@example.com",
97
+ to: "you@example.com",
98
+ subject: "Welcome",
99
+ html: "<h1>Hello</h1>",
100
+ text: "Hello",
101
+ });
102
+ ```
103
+
104
+ </Tab>
105
+ <Tab value="List">
106
+
107
+ List messages in a mailbox (`GET /api/v1/mailbox/{name}`).
108
+
109
+ ```ts
110
+ const mailbox = inbucket.mailboxForAddress("you@example.com");
111
+ const inbox = await inbucket.listMailbox(mailbox);
112
+ console.log(inbox.length, inbox[0]?.subject);
113
+ ```
114
+
115
+ </Tab>
116
+ <Tab value="Message">
117
+
118
+ Full body, headers, and attachments for a message id.
119
+
120
+ ```ts
121
+ const full = await inbucket.getMessage(mailbox, inbox[0]!.id);
122
+ console.log(full.body.text, full.body.html, full.header.Subject);
123
+ ```
124
+
125
+ </Tab>
126
+ <Tab value="Source">
127
+
128
+ Raw RFC822 source (`GET …/source`) for MIME assertions.
129
+
130
+ ```ts
131
+ const source = await inbucket.getSource(mailbox, inbox[0]!.id);
132
+ console.log(source.includes("Subject: Welcome"));
133
+ ```
134
+
135
+ </Tab>
136
+ <Tab value="Mark seen">
137
+
138
+ Mark one message as seen (`PATCH` with `{ seen: true }`).
139
+
140
+ ```ts
141
+ await inbucket.markSeen(mailbox, inbox[0]!.id);
142
+ ```
143
+
144
+ </Tab>
145
+ <Tab value="Delete">
146
+
147
+ Delete one message by id.
148
+
149
+ ```ts
150
+ await inbucket.deleteMessage(mailbox, inbox[0]!.id);
151
+ ```
152
+
153
+ </Tab>
154
+ <Tab value="Purge">
155
+
156
+ Clear every message in a mailbox.
157
+
158
+ ```ts
159
+ await inbucket.purgeMailbox(mailbox);
160
+ ```
161
+
162
+ </Tab>
163
+ </Tabs>
164
+
165
+ REST failures throw `InbucketError` (`provider: "inbucket"`).
166
+ Empty mailbox / id arguments throw with status `400`.
167
+
168
+ ## Troubleshooting
169
+
170
+ <Accordions>
171
+ <Accordion title="Connection refused on port 2500">
172
+ Inbucket is not running, or the SMTP port is remapped. Start the container above, or set `host` / `port` to match your install.
173
+ </Accordion>
174
+ <Accordion title="listMailbox returns empty after send">
175
+ Check mailbox naming. Stock Inbucket uses the local-part (`you` for `you@example.com`).
176
+ If your instance sets `INBUCKET_MAILBOXNAMING=full` or `domain`, match that with `mailboxNaming`.
177
+ </Accordion>
178
+ <Accordion title="API helpers fail but send works">
179
+ SMTP and the UI/API can bind to different hosts. Set `apiUrl` to the web base (default `http://localhost:9000`).
180
+ </Accordion>
181
+ <Accordion title="Should I use createSMTPMailer instead?">
182
+ Yes, if you only need SMTP. `InbucketTransport` adds local defaults and REST helpers for tests and inspection.
183
+ </Accordion>
184
+ </Accordions>
185
+
186
+ ## Learn more
187
+
188
+ - [Mailpit](./mailpit) — another local SMTP catcher with a different REST shape
189
+ - [SMTP](./smtp) — generic SMTP when you are not on a catcher
190
+ - [Preview](/docs/decorators/preview) — write `.eml` files to disk instead
191
+ - [Email channel](/docs/channels/email) — `createMailer` contract
192
+ - [Inbucket REST API](https://github.com/inbucket/inbucket/wiki/REST-API) — mailbox endpoints on the catcher
193
+
194
+ ## Next
195
+
196
+ <Cards>
197
+ <Card title="Mailpit" href="/docs/transports/mailpit" />
198
+ <Card title="Email channel" href="/docs/channels/email" />
199
+ <Card title="Transports" href="/docs/transports" />
200
+ </Cards>
@@ -57,18 +57,18 @@ Every import below is an exported package subpath.
57
57
  | [SparkPost](./sparkpost) | Email | `sently/transports/sparkpost` |
58
58
  | [Mailtrap](./mailtrap) | Email | `sently/transports/mailtrap` |
59
59
  | [Mailpit](./mailpit) | Email (dev) | `sently/transports/mailpit` |
60
+ | [Inbucket](./inbucket) | Email (dev) | `sently/transports/inbucket` |
60
61
  | [Loops](./loops) | Email | `sently/transports/loops` |
61
62
  | [Cloudflare Email](./cloudflare-email) | Email | `sently/transports/cloudflare-email` |
62
63
  | [SNDR](./sndr) | Email | `sently/transports/sndr` |
63
- | [Taqnyat Mail](./taqnyat-mail) | Email | `sently/transports/taqnyat-mail` |
64
+ | [Hostinger](./hostinger) | Email | `sently/transports/hostinger` |
64
65
  | [Twilio SMS](./twilio-sms) | SMS | `sently/transports/twilio-sms` |
65
- | [Taqnyat SMS](./taqnyat-sms) | SMS | `sently/transports/taqnyat-sms` |
66
66
  | [Msegat](./msegat) | SMS | `sently/transports/msegat` |
67
67
  | [Unifonic](./unifonic) | SMS | `sently/transports/unifonic` |
68
68
  | [WhatsApp Cloud](./whatsapp-cloud) | WhatsApp | `sently/transports/whatsapp-cloud` |
69
- | [Taqnyat WhatsApp](./taqnyat-whatsapp) | WhatsApp | `sently/transports/taqnyat-whatsapp` |
70
69
  | [Web Push](./webpush) | Push | `sently/transports/webpush` |
71
70
  | [FCM](./fcm) | Push | `sently/transports/fcm` |
71
+ | [Taqnyat](./taqnyat) | Email · SMS · WhatsApp | `sently/transports/taqnyat-mail`, `taqnyat-sms`, `taqnyat-whatsapp` |
72
72
 
73
73
  For retry, fallback, preview, and idempotency wrappers, see [Decorators](../decorators).
74
74