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.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +110 -1
- 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/hostinger.d.ts +128 -0
- package/dist/transports/hostinger.js +3 -0
- package/dist/transports/hostinger.js.map +10 -0
- 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 +19 -0
- 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 +12 -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/hostinger.mdx +435 -0
- package/site/content/docs/transports/inbucket.mdx +200 -0
- package/site/content/docs/transports/index.mdx +3 -3
- package/site/content/docs/transports/mailpit.mdx +114 -14
- package/site/content/docs/transports/meta.json +6 -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 +365 -0
- package/site/content/docs/transports/webpush.mdx +201 -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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Inbucket development transport — SMTP to a local Inbucket catcher with
|
|
4
|
+
* REST helpers for inspecting, reading, and purging mailbox messages.
|
|
5
|
+
*
|
|
6
|
+
* Defaults match a stock Inbucket install: SMTP `localhost:2500`,
|
|
7
|
+
* UI/API `http://localhost:9000`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createMailer } from "sently/mailer";
|
|
12
|
+
* import { InbucketTransport } from "sently/transports/inbucket";
|
|
13
|
+
*
|
|
14
|
+
* const inbucket = new InbucketTransport();
|
|
15
|
+
* const mailer = await createMailer({ transport: inbucket });
|
|
16
|
+
*
|
|
17
|
+
* await mailer.send({
|
|
18
|
+
* from: "dev@example.com",
|
|
19
|
+
* to: "you@example.com",
|
|
20
|
+
* subject: "Hello",
|
|
21
|
+
* text: "Captured by Inbucket",
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* const mailbox = inbucket.mailboxForAddress("you@example.com");
|
|
25
|
+
* const inbox = await inbucket.listMailbox(mailbox);
|
|
26
|
+
* console.log(inbox[0]?.subject);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
import { SentlyError } from "../core/errors.js";
|
|
30
|
+
import type { MailOptions, SendResult, SMTPAuth, SocketAdapter, TLSOptions, Transport, VerifyResult } from "../core/types.js";
|
|
31
|
+
/** Default SMTP host for a local Inbucket instance. */
|
|
32
|
+
export declare const INBUCKET_DEFAULT_HOST = "localhost";
|
|
33
|
+
/** Default SMTP port for a local Inbucket instance. */
|
|
34
|
+
export declare const INBUCKET_DEFAULT_PORT = 2500;
|
|
35
|
+
/** Default web UI / REST API base URL for a local Inbucket instance. */
|
|
36
|
+
export declare const INBUCKET_DEFAULT_API_URL = "http://localhost:9000";
|
|
37
|
+
/**
|
|
38
|
+
* How Inbucket maps an email address to a mailbox name.
|
|
39
|
+
* Matches `INBUCKET_MAILBOXNAMING` on the catcher (`local` by default).
|
|
40
|
+
*/
|
|
41
|
+
export type InbucketMailboxNaming = "local" | "full" | "domain";
|
|
42
|
+
/** Inbucket development transport configuration. */
|
|
43
|
+
export interface InbucketConfig {
|
|
44
|
+
/** SMTP hostname. Default: `"localhost"`. */
|
|
45
|
+
host?: string;
|
|
46
|
+
/** SMTP port. Default: `2500`. */
|
|
47
|
+
port?: number;
|
|
48
|
+
/** Use implicit TLS on connect. Default: `false` (Inbucket is plain SMTP). */
|
|
49
|
+
secure?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Refuse AUTH over a non-TLS connection.
|
|
52
|
+
* Default: `false` so optional local SMTP auth works without TLS.
|
|
53
|
+
*/
|
|
54
|
+
requireTLS?: boolean;
|
|
55
|
+
/** Optional SMTP authentication. */
|
|
56
|
+
auth?: SMTPAuth;
|
|
57
|
+
/** TLS options when STARTTLS or implicit TLS is enabled. */
|
|
58
|
+
tls?: TLSOptions;
|
|
59
|
+
/** Socket connect timeout in milliseconds. */
|
|
60
|
+
connectionTimeout?: number;
|
|
61
|
+
/** Runtime socket adapter. Auto-detected on first send when omitted. */
|
|
62
|
+
adapter?: SocketAdapter;
|
|
63
|
+
/**
|
|
64
|
+
* Inbucket web UI / REST API base URL (no trailing slash).
|
|
65
|
+
* Default: `"http://localhost:9000"`.
|
|
66
|
+
*/
|
|
67
|
+
apiUrl?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Mailbox naming strategy for {@link InbucketTransport.mailboxForAddress}.
|
|
70
|
+
* Default: `"local"` (local-part only), matching stock Inbucket.
|
|
71
|
+
*/
|
|
72
|
+
mailboxNaming?: InbucketMailboxNaming;
|
|
73
|
+
}
|
|
74
|
+
/** Summary row from `GET /api/v1/mailbox/{name}`. */
|
|
75
|
+
export interface InbucketMessageHeader {
|
|
76
|
+
/** Mailbox name. */
|
|
77
|
+
mailbox: string;
|
|
78
|
+
/** Inbucket message id. */
|
|
79
|
+
id: string;
|
|
80
|
+
/** Sender address string. */
|
|
81
|
+
from: string;
|
|
82
|
+
/** Recipient address strings. */
|
|
83
|
+
to: string[];
|
|
84
|
+
/** Subject line. */
|
|
85
|
+
subject: string;
|
|
86
|
+
/** ISO created timestamp. */
|
|
87
|
+
date: string;
|
|
88
|
+
/** Unix epoch milliseconds. */
|
|
89
|
+
"posix-millis": number;
|
|
90
|
+
/** Message size in bytes. */
|
|
91
|
+
size: number;
|
|
92
|
+
/** Whether the message has been seen in the UI/API. */
|
|
93
|
+
seen: boolean;
|
|
94
|
+
}
|
|
95
|
+
/** Attachment metadata from a full Inbucket message. */
|
|
96
|
+
export interface InbucketAttachment {
|
|
97
|
+
/** Attachment filename. */
|
|
98
|
+
filename: string;
|
|
99
|
+
/** MIME content type. */
|
|
100
|
+
"content-type": string;
|
|
101
|
+
/** Absolute download URL served by Inbucket. */
|
|
102
|
+
"download-link": string;
|
|
103
|
+
/** Absolute view URL served by Inbucket. */
|
|
104
|
+
"view-link": string;
|
|
105
|
+
/** MD5 checksum of the attachment bytes. */
|
|
106
|
+
md5: string;
|
|
107
|
+
}
|
|
108
|
+
/** Body parts from a full Inbucket message. */
|
|
109
|
+
export interface InbucketMessageBody {
|
|
110
|
+
/** Plain-text body. */
|
|
111
|
+
text: string;
|
|
112
|
+
/** HTML body. */
|
|
113
|
+
html: string;
|
|
114
|
+
}
|
|
115
|
+
/** Full message from `GET /api/v1/mailbox/{name}/{id}`. */
|
|
116
|
+
export interface InbucketMessage extends InbucketMessageHeader {
|
|
117
|
+
/** Text and HTML bodies. */
|
|
118
|
+
body: InbucketMessageBody;
|
|
119
|
+
/** Parsed header map. */
|
|
120
|
+
header: Record<string, string[]>;
|
|
121
|
+
/** Attachments (may be empty). */
|
|
122
|
+
attachments: InbucketAttachment[];
|
|
123
|
+
}
|
|
124
|
+
/** Error thrown when the Inbucket REST API returns a non-success response. */
|
|
125
|
+
export declare class InbucketError extends SentlyError {
|
|
126
|
+
readonly statusCode: number;
|
|
127
|
+
readonly apiError: unknown;
|
|
128
|
+
/** Creates an Inbucket API error with status code (`0` = network/connect failure). */
|
|
129
|
+
constructor(message: string, statusCode: number, apiError: unknown);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Development transport for [Inbucket](https://inbucket.org/).
|
|
133
|
+
*
|
|
134
|
+
* Sends via SMTP (defaults: `localhost:2500`) and exposes REST helpers for
|
|
135
|
+
* listing, reading, marking seen, deleting, and purging mailbox messages.
|
|
136
|
+
*/
|
|
137
|
+
export declare class InbucketTransport implements Transport {
|
|
138
|
+
readonly provider = "inbucket";
|
|
139
|
+
private readonly host;
|
|
140
|
+
private readonly port;
|
|
141
|
+
private readonly secure;
|
|
142
|
+
private readonly requireTLS;
|
|
143
|
+
private readonly auth?;
|
|
144
|
+
private readonly tls?;
|
|
145
|
+
private readonly connectionTimeout?;
|
|
146
|
+
private readonly adapter?;
|
|
147
|
+
private readonly apiUrl;
|
|
148
|
+
private readonly mailboxNaming;
|
|
149
|
+
private smtp;
|
|
150
|
+
/** Creates an Inbucket transport with local-dev defaults. */
|
|
151
|
+
constructor(config?: InbucketConfig);
|
|
152
|
+
/** Web UI base URL (same origin as the REST API). */
|
|
153
|
+
get webUrl(): string;
|
|
154
|
+
/** Sends an email to Inbucket over SMTP. */
|
|
155
|
+
send(options: MailOptions): Promise<SendResult>;
|
|
156
|
+
/** Verifies SMTP connectivity to Inbucket. */
|
|
157
|
+
verify(): Promise<VerifyResult>;
|
|
158
|
+
/** Closes the underlying SMTP adapter if connected. */
|
|
159
|
+
close(): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Maps an email address to an Inbucket mailbox name using {@link mailboxNaming}.
|
|
162
|
+
* Stock Inbucket uses `"local"` (the part before `@`).
|
|
163
|
+
*/
|
|
164
|
+
mailboxForAddress(address: string): string;
|
|
165
|
+
/**
|
|
166
|
+
* Lists messages in a mailbox via `GET /api/v1/mailbox/{name}`.
|
|
167
|
+
* Newest messages appear last in a stock Inbucket response.
|
|
168
|
+
*/
|
|
169
|
+
listMailbox(mailbox: string): Promise<InbucketMessageHeader[]>;
|
|
170
|
+
/**
|
|
171
|
+
* Fetches a full message via `GET /api/v1/mailbox/{name}/{id}`.
|
|
172
|
+
*/
|
|
173
|
+
getMessage(mailbox: string, id: string): Promise<InbucketMessage>;
|
|
174
|
+
/**
|
|
175
|
+
* Fetches the raw message source via `GET /api/v1/mailbox/{name}/{id}/source`.
|
|
176
|
+
*/
|
|
177
|
+
getSource(mailbox: string, id: string): Promise<string>;
|
|
178
|
+
/**
|
|
179
|
+
* Marks a message as seen via `PATCH /api/v1/mailbox/{name}/{id}`.
|
|
180
|
+
*/
|
|
181
|
+
markSeen(mailbox: string, id: string): Promise<void>;
|
|
182
|
+
/**
|
|
183
|
+
* Deletes one message via `DELETE /api/v1/mailbox/{name}/{id}`.
|
|
184
|
+
*/
|
|
185
|
+
deleteMessage(mailbox: string, id: string): Promise<void>;
|
|
186
|
+
/**
|
|
187
|
+
* Deletes every message in a mailbox via `DELETE /api/v1/mailbox/{name}`.
|
|
188
|
+
*/
|
|
189
|
+
purgeMailbox(mailbox: string): Promise<void>;
|
|
190
|
+
private encodeSegment;
|
|
191
|
+
private getSmtp;
|
|
192
|
+
private apiHeaders;
|
|
193
|
+
private apiFetch;
|
|
194
|
+
private apiJson;
|
|
195
|
+
private apiOk;
|
|
196
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{n as Y}from"../chunk-a057jzsy.js";import"../chunk-f5x8vesr.js";import"../chunk-yg3kj723.js";import"../chunk-w4c9kj70.js";import"../chunk-1ke3nmyg.js";import{M as V}from"../chunk-d5aw5vag.js";import"../chunk-8kpgbrba.js";import"../chunk-hnzmn4s4.js";import{ja as W,ka as X}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";var Z="localhost",$=2500,H="http://localhost:9000";class Q extends W{statusCode;apiError;constructor(q,w,z){super(q,w===0?"CONNECTION_FAILED":X(w),{...w>0?{statusCode:w}:{},provider:"inbucket",cause:z});this.statusCode=w;this.apiError=z;this.name="InbucketError"}}class M{provider="inbucket";host;port;secure;requireTLS;auth;tls;connectionTimeout;adapter;apiUrl;mailboxNaming;smtp=null;constructor(q={}){if(this.host=q.host??Z,this.port=q.port??$,this.secure=q.secure??!1,this.requireTLS=q.requireTLS??!1,q.auth!==void 0)this.auth=q.auth;if(q.tls!==void 0)this.tls=q.tls;if(q.connectionTimeout!==void 0)this.connectionTimeout=q.connectionTimeout;if(q.adapter!==void 0)this.adapter=q.adapter;this.apiUrl=(q.apiUrl??H).replace(/\/$/,""),this.mailboxNaming=q.mailboxNaming??"local"}get webUrl(){return this.apiUrl}async send(q){return(await this.getSmtp()).send(q)}async verify(){return{...await(await this.getSmtp()).verify(),provider:"inbucket"}}async close(){if(this.smtp)await this.smtp.close()}mailboxForAddress(q){let w=q.trim();if(!w)throw new Q("Email address is required",400,{code:"INVALID_CONFIG"});let z=w.lastIndexOf("@");if(z<=0||z===w.length-1)throw new Q(`Invalid email address: ${q}`,400,{code:"INVALID_CONFIG"});let G=w.slice(0,z),J=w.slice(z+1);switch(this.mailboxNaming){case"full":return w.toLowerCase();case"domain":return J.toLowerCase();default:return G.toLowerCase()}}async listMailbox(q){return this.apiJson("GET",`/api/v1/mailbox/${this.encodeSegment(q,"Mailbox name")}`)}async getMessage(q,w){return this.apiJson("GET",`/api/v1/mailbox/${this.encodeSegment(q,"Mailbox name")}/${this.encodeSegment(w,"Message id")}`)}async getSource(q,w){return(await this.apiFetch("GET",`/api/v1/mailbox/${this.encodeSegment(q,"Mailbox name")}/${this.encodeSegment(w,"Message id")}/source`)).text()}async markSeen(q,w){await this.apiOk("PATCH",`/api/v1/mailbox/${this.encodeSegment(q,"Mailbox name")}/${this.encodeSegment(w,"Message id")}`,{seen:!0})}async deleteMessage(q,w){await this.apiOk("DELETE",`/api/v1/mailbox/${this.encodeSegment(q,"Mailbox name")}/${this.encodeSegment(w,"Message id")}`)}async purgeMailbox(q){await this.apiOk("DELETE",`/api/v1/mailbox/${this.encodeSegment(q,"Mailbox name")}`)}encodeSegment(q,w){if(!q)throw new Q(`${w} is required`,400,{code:"INVALID_CONFIG"});return encodeURIComponent(q)}async getSmtp(){if(!this.smtp){let q=this.adapter??await V({secure:this.secure,...this.connectionTimeout!==void 0?{connectionTimeout:this.connectionTimeout}:{},...this.tls!==void 0?{tls:this.tls}:{}});this.smtp=new Y({host:this.host,port:this.port,secure:this.secure,requireTLS:this.requireTLS,adapter:q,...this.auth!==void 0?{auth:this.auth}:{},...this.tls!==void 0?{tls:this.tls}:{},...this.connectionTimeout!==void 0?{connectionTimeout:this.connectionTimeout}:{}})}return this.smtp}apiHeaders(){return{Accept:"application/json","Content-Type":"application/json"}}async apiFetch(q,w,z){let G;try{G=await fetch(`${this.apiUrl}${w}`,{method:q,headers:this.apiHeaders(),...z!==void 0?{body:JSON.stringify(z)}:{}})}catch(J){throw new Q(J instanceof Error?J.message:"Inbucket API request failed",0,J)}if(!G.ok){let J=await G.text().catch(()=>"");throw new Q(J||`Inbucket API error (${G.status})`,G.status,J)}return G}async apiJson(q,w,z){return await(await this.apiFetch(q,w,z)).json()}async apiOk(q,w,z){await this.apiFetch(q,w,z)}}export{M as InbucketTransport,Q as InbucketError,$ as INBUCKET_DEFAULT_PORT,Z as INBUCKET_DEFAULT_HOST,H as INBUCKET_DEFAULT_API_URL};
|
|
2
|
+
|
|
3
|
+
//# debugId=D6BB1EA239505BBC64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transports/inbucket.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * Inbucket development transport — SMTP to a local Inbucket catcher with\n * REST helpers for inspecting, reading, and purging mailbox messages.\n *\n * Defaults match a stock Inbucket install: SMTP `localhost:2500`,\n * UI/API `http://localhost:9000`.\n *\n * @example\n * ```ts\n * import { createMailer } from \"sently/mailer\";\n * import { InbucketTransport } from \"sently/transports/inbucket\";\n *\n * const inbucket = new InbucketTransport();\n * const mailer = await createMailer({ transport: inbucket });\n *\n * await mailer.send({\n * from: \"dev@example.com\",\n * to: \"you@example.com\",\n * subject: \"Hello\",\n * text: \"Captured by Inbucket\",\n * });\n *\n * const mailbox = inbucket.mailboxForAddress(\"you@example.com\");\n * const inbox = await inbucket.listMailbox(mailbox);\n * console.log(inbox[0]?.subject);\n * ```\n */\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPAuth,\n SocketAdapter,\n TLSOptions,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { createDefaultAdapter } from \"../detect.js\";\nimport { SMTPTransport } from \"./smtp.js\";\n\n/** Default SMTP host for a local Inbucket instance. */\nexport const INBUCKET_DEFAULT_HOST = \"localhost\";\n\n/** Default SMTP port for a local Inbucket instance. */\nexport const INBUCKET_DEFAULT_PORT = 2500;\n\n/** Default web UI / REST API base URL for a local Inbucket instance. */\nexport const INBUCKET_DEFAULT_API_URL = \"http://localhost:9000\";\n\n/**\n * How Inbucket maps an email address to a mailbox name.\n * Matches `INBUCKET_MAILBOXNAMING` on the catcher (`local` by default).\n */\nexport type InbucketMailboxNaming = \"local\" | \"full\" | \"domain\";\n\n/** Inbucket development transport configuration. */\nexport interface InbucketConfig {\n /** SMTP hostname. Default: `\"localhost\"`. */\n host?: string;\n /** SMTP port. Default: `2500`. */\n port?: number;\n /** Use implicit TLS on connect. Default: `false` (Inbucket is plain SMTP). */\n secure?: boolean;\n /**\n * Refuse AUTH over a non-TLS connection.\n * Default: `false` so optional local SMTP auth works without TLS.\n */\n requireTLS?: boolean;\n /** Optional SMTP authentication. */\n auth?: SMTPAuth;\n /** TLS options when STARTTLS or implicit TLS is enabled. */\n tls?: TLSOptions;\n /** Socket connect timeout in milliseconds. */\n connectionTimeout?: number;\n /** Runtime socket adapter. Auto-detected on first send when omitted. */\n adapter?: SocketAdapter;\n /**\n * Inbucket web UI / REST API base URL (no trailing slash).\n * Default: `\"http://localhost:9000\"`.\n */\n apiUrl?: string;\n /**\n * Mailbox naming strategy for {@link InbucketTransport.mailboxForAddress}.\n * Default: `\"local\"` (local-part only), matching stock Inbucket.\n */\n mailboxNaming?: InbucketMailboxNaming;\n}\n\n/** Summary row from `GET /api/v1/mailbox/{name}`. */\nexport interface InbucketMessageHeader {\n /** Mailbox name. */\n mailbox: string;\n /** Inbucket message id. */\n id: string;\n /** Sender address string. */\n from: string;\n /** Recipient address strings. */\n to: string[];\n /** Subject line. */\n subject: string;\n /** ISO created timestamp. */\n date: string;\n /** Unix epoch milliseconds. */\n \"posix-millis\": number;\n /** Message size in bytes. */\n size: number;\n /** Whether the message has been seen in the UI/API. */\n seen: boolean;\n}\n\n/** Attachment metadata from a full Inbucket message. */\nexport interface InbucketAttachment {\n /** Attachment filename. */\n filename: string;\n /** MIME content type. */\n \"content-type\": string;\n /** Absolute download URL served by Inbucket. */\n \"download-link\": string;\n /** Absolute view URL served by Inbucket. */\n \"view-link\": string;\n /** MD5 checksum of the attachment bytes. */\n md5: string;\n}\n\n/** Body parts from a full Inbucket message. */\nexport interface InbucketMessageBody {\n /** Plain-text body. */\n text: string;\n /** HTML body. */\n html: string;\n}\n\n/** Full message from `GET /api/v1/mailbox/{name}/{id}`. */\nexport interface InbucketMessage extends InbucketMessageHeader {\n /** Text and HTML bodies. */\n body: InbucketMessageBody;\n /** Parsed header map. */\n header: Record<string, string[]>;\n /** Attachments (may be empty). */\n attachments: InbucketAttachment[];\n}\n\n/** Error thrown when the Inbucket REST API returns a non-success response. */\nexport class InbucketError extends SentlyError {\n /** Creates an Inbucket API error with status code (`0` = network/connect failure). */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, statusCode === 0 ? \"CONNECTION_FAILED\" : httpStatusToSentlyCode(statusCode), {\n ...(statusCode > 0 ? { statusCode } : {}),\n provider: \"inbucket\",\n cause: apiError,\n });\n this.name = \"InbucketError\";\n }\n}\n\n/**\n * Development transport for [Inbucket](https://inbucket.org/).\n *\n * Sends via SMTP (defaults: `localhost:2500`) and exposes REST helpers for\n * listing, reading, marking seen, deleting, and purging mailbox messages.\n */\nexport class InbucketTransport implements Transport {\n readonly provider = \"inbucket\";\n\n private readonly host: string;\n private readonly port: number;\n private readonly secure: boolean;\n private readonly requireTLS: boolean;\n private readonly auth?: SMTPAuth;\n private readonly tls?: TLSOptions;\n private readonly connectionTimeout?: number;\n private readonly adapter?: SocketAdapter;\n private readonly apiUrl: string;\n private readonly mailboxNaming: InbucketMailboxNaming;\n\n private smtp: SMTPTransport | null = null;\n\n /** Creates an Inbucket transport with local-dev defaults. */\n constructor(config: InbucketConfig = {}) {\n this.host = config.host ?? INBUCKET_DEFAULT_HOST;\n this.port = config.port ?? INBUCKET_DEFAULT_PORT;\n this.secure = config.secure ?? false;\n this.requireTLS = config.requireTLS ?? false;\n if (config.auth !== undefined) {\n this.auth = config.auth;\n }\n if (config.tls !== undefined) {\n this.tls = config.tls;\n }\n if (config.connectionTimeout !== undefined) {\n this.connectionTimeout = config.connectionTimeout;\n }\n if (config.adapter !== undefined) {\n this.adapter = config.adapter;\n }\n this.apiUrl = (config.apiUrl ?? INBUCKET_DEFAULT_API_URL).replace(/\\/$/, \"\");\n this.mailboxNaming = config.mailboxNaming ?? \"local\";\n }\n\n /** Web UI base URL (same origin as the REST API). */\n get webUrl(): string {\n return this.apiUrl;\n }\n\n /** Sends an email to Inbucket over SMTP. */\n async send(options: MailOptions): Promise<SendResult> {\n return (await this.getSmtp()).send(options);\n }\n\n /** Verifies SMTP connectivity to Inbucket. */\n async verify(): Promise<VerifyResult> {\n const result = await (await this.getSmtp()).verify();\n return {\n ...result,\n provider: \"inbucket\",\n };\n }\n\n /** Closes the underlying SMTP adapter if connected. */\n async close(): Promise<void> {\n if (this.smtp) {\n await this.smtp.close();\n }\n }\n\n /**\n * Maps an email address to an Inbucket mailbox name using {@link mailboxNaming}.\n * Stock Inbucket uses `\"local\"` (the part before `@`).\n */\n mailboxForAddress(address: string): string {\n const trimmed = address.trim();\n if (!trimmed) {\n throw new InbucketError(\"Email address is required\", 400, { code: \"INVALID_CONFIG\" });\n }\n const at = trimmed.lastIndexOf(\"@\");\n if (at <= 0 || at === trimmed.length - 1) {\n throw new InbucketError(`Invalid email address: ${address}`, 400, {\n code: \"INVALID_CONFIG\",\n });\n }\n const local = trimmed.slice(0, at);\n const domain = trimmed.slice(at + 1);\n switch (this.mailboxNaming) {\n case \"full\":\n return trimmed.toLowerCase();\n case \"domain\":\n return domain.toLowerCase();\n default:\n return local.toLowerCase();\n }\n }\n\n /**\n * Lists messages in a mailbox via `GET /api/v1/mailbox/{name}`.\n * Newest messages appear last in a stock Inbucket response.\n */\n async listMailbox(mailbox: string): Promise<InbucketMessageHeader[]> {\n return this.apiJson<InbucketMessageHeader[]>(\n \"GET\",\n `/api/v1/mailbox/${this.encodeSegment(mailbox, \"Mailbox name\")}`,\n );\n }\n\n /**\n * Fetches a full message via `GET /api/v1/mailbox/{name}/{id}`.\n */\n async getMessage(mailbox: string, id: string): Promise<InbucketMessage> {\n return this.apiJson<InbucketMessage>(\n \"GET\",\n `/api/v1/mailbox/${this.encodeSegment(mailbox, \"Mailbox name\")}/${this.encodeSegment(id, \"Message id\")}`,\n );\n }\n\n /**\n * Fetches the raw message source via `GET /api/v1/mailbox/{name}/{id}/source`.\n */\n async getSource(mailbox: string, id: string): Promise<string> {\n const response = await this.apiFetch(\n \"GET\",\n `/api/v1/mailbox/${this.encodeSegment(mailbox, \"Mailbox name\")}/${this.encodeSegment(id, \"Message id\")}/source`,\n );\n return response.text();\n }\n\n /**\n * Marks a message as seen via `PATCH /api/v1/mailbox/{name}/{id}`.\n */\n async markSeen(mailbox: string, id: string): Promise<void> {\n await this.apiOk(\n \"PATCH\",\n `/api/v1/mailbox/${this.encodeSegment(mailbox, \"Mailbox name\")}/${this.encodeSegment(id, \"Message id\")}`,\n { seen: true },\n );\n }\n\n /**\n * Deletes one message via `DELETE /api/v1/mailbox/{name}/{id}`.\n */\n async deleteMessage(mailbox: string, id: string): Promise<void> {\n await this.apiOk(\n \"DELETE\",\n `/api/v1/mailbox/${this.encodeSegment(mailbox, \"Mailbox name\")}/${this.encodeSegment(id, \"Message id\")}`,\n );\n }\n\n /**\n * Deletes every message in a mailbox via `DELETE /api/v1/mailbox/{name}`.\n */\n async purgeMailbox(mailbox: string): Promise<void> {\n await this.apiOk(\"DELETE\", `/api/v1/mailbox/${this.encodeSegment(mailbox, \"Mailbox name\")}`);\n }\n\n private encodeSegment(value: string, label: string): string {\n if (!value) {\n throw new InbucketError(`${label} is required`, 400, { code: \"INVALID_CONFIG\" });\n }\n return encodeURIComponent(value);\n }\n\n private async getSmtp(): Promise<SMTPTransport> {\n if (!this.smtp) {\n const adapter =\n this.adapter ??\n (await createDefaultAdapter({\n secure: this.secure,\n ...(this.connectionTimeout !== undefined\n ? { connectionTimeout: this.connectionTimeout }\n : {}),\n ...(this.tls !== undefined ? { tls: this.tls } : {}),\n }));\n\n this.smtp = new SMTPTransport({\n host: this.host,\n port: this.port,\n secure: this.secure,\n requireTLS: this.requireTLS,\n adapter,\n ...(this.auth !== undefined ? { auth: this.auth } : {}),\n ...(this.tls !== undefined ? { tls: this.tls } : {}),\n ...(this.connectionTimeout !== undefined\n ? { connectionTimeout: this.connectionTimeout }\n : {}),\n });\n }\n return this.smtp;\n }\n\n private apiHeaders(): HeadersInit {\n return {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n };\n }\n\n private async apiFetch(method: string, path: string, body?: unknown): Promise<Response> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}${path}`, {\n method,\n headers: this.apiHeaders(),\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n } catch (err) {\n throw new InbucketError(\n err instanceof Error ? err.message : \"Inbucket API request failed\",\n 0,\n err,\n );\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new InbucketError(\n text || `Inbucket API error (${response.status})`,\n response.status,\n text,\n );\n }\n\n return response;\n }\n\n private async apiJson<T>(method: string, path: string, body?: unknown): Promise<T> {\n const response = await this.apiFetch(method, path, body);\n return (await response.json()) as T;\n }\n\n private async apiOk(method: string, path: string, body?: unknown): Promise<void> {\n await this.apiFetch(method, path, body);\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "4XA0CO,DAAM,HAAwB,OAGxB,HAAwB,AAGxB,HAA2B,mBAgGjC,CAAM,KAAsB,CAAY,CAI3B,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MAAM,EAAS,IAAe,EAAI,oBAAsB,EAAuB,CAAU,EAAG,IACtF,EAAa,EAAI,CAAE,YAAW,EAAI,CAAC,EACvC,SAAU,WACV,MAAO,CACT,CAAC,EAPe,kBACA,gBAOhB,KAAK,KAAO,gBAEhB,CAQO,MAAM,CAAuC,CACzC,SAAW,WAEH,KACA,KACA,OACA,WACA,KACA,IACA,kBACA,QACA,OACA,cAET,KAA6B,KAGrC,WAAW,CAAC,EAAyB,CAAC,EAAG,CAKvC,GAJA,KAAK,KAAO,EAAO,MAAQ,EAC3B,KAAK,KAAO,EAAO,MAAQ,EAC3B,KAAK,OAAS,EAAO,QAAU,GAC/B,KAAK,WAAa,EAAO,YAAc,GACnC,EAAO,OAAS,OAClB,KAAK,KAAO,EAAO,KAErB,GAAI,EAAO,MAAQ,OACjB,KAAK,IAAM,EAAO,IAEpB,GAAI,EAAO,oBAAsB,OAC/B,KAAK,kBAAoB,EAAO,kBAElC,GAAI,EAAO,UAAY,OACrB,KAAK,QAAU,EAAO,QAExB,KAAK,QAAU,EAAO,QAAU,GAA0B,QAAQ,MAAO,EAAE,EAC3E,KAAK,cAAgB,EAAO,eAAiB,WAI3C,OAAM,EAAW,CACnB,OAAO,KAAK,YAIR,KAAI,CAAC,EAA2C,CACpD,OAAQ,MAAM,KAAK,QAAQ,GAAG,KAAK,CAAO,OAItC,OAAM,EAA0B,CAEpC,MAAO,IADQ,MAAO,MAAM,KAAK,QAAQ,GAAG,OAAO,EAGjD,SAAU,UACZ,OAII,MAAK,EAAkB,CAC3B,GAAI,KAAK,KACP,MAAM,KAAK,KAAK,MAAM,EAQ1B,iBAAiB,CAAC,EAAyB,CACzC,IAAM,EAAU,EAAQ,KAAK,EAC7B,GAAI,CAAC,EACH,MAAM,IAAI,EAAc,4BAA6B,IAAK,CAAE,KAAM,gBAAiB,CAAC,EAEtF,IAAM,EAAK,EAAQ,YAAY,GAAG,EAClC,GAAI,GAAM,GAAK,IAAO,EAAQ,OAAS,EACrC,MAAM,IAAI,EAAc,0BAA0B,IAAW,IAAK,CAChE,KAAM,gBACR,CAAC,EAEH,IAAM,EAAQ,EAAQ,MAAM,EAAG,CAAE,EAC3B,EAAS,EAAQ,MAAM,EAAK,CAAC,EACnC,OAAQ,KAAK,mBACN,OACH,OAAO,EAAQ,YAAY,MACxB,SACH,OAAO,EAAO,YAAY,UAE1B,OAAO,EAAM,YAAY,QAQzB,YAAW,CAAC,EAAmD,CACnE,OAAO,KAAK,QACV,MACA,mBAAmB,KAAK,cAAc,EAAS,cAAc,GAC/D,OAMI,WAAU,CAAC,EAAiB,EAAsC,CACtE,OAAO,KAAK,QACV,MACA,mBAAmB,KAAK,cAAc,EAAS,cAAc,KAAK,KAAK,cAAc,EAAI,YAAY,GACvG,OAMI,UAAS,CAAC,EAAiB,EAA6B,CAK5D,OAJiB,MAAM,KAAK,SAC1B,MACA,mBAAmB,KAAK,cAAc,EAAS,cAAc,KAAK,KAAK,cAAc,EAAI,YAAY,UACvG,GACgB,KAAK,OAMjB,SAAQ,CAAC,EAAiB,EAA2B,CACzD,MAAM,KAAK,MACT,QACA,mBAAmB,KAAK,cAAc,EAAS,cAAc,KAAK,KAAK,cAAc,EAAI,YAAY,IACrG,CAAE,KAAM,EAAK,CACf,OAMI,cAAa,CAAC,EAAiB,EAA2B,CAC9D,MAAM,KAAK,MACT,SACA,mBAAmB,KAAK,cAAc,EAAS,cAAc,KAAK,KAAK,cAAc,EAAI,YAAY,GACvG,OAMI,aAAY,CAAC,EAAgC,CACjD,MAAM,KAAK,MAAM,SAAU,mBAAmB,KAAK,cAAc,EAAS,cAAc,GAAG,EAGrF,aAAa,CAAC,EAAe,EAAuB,CAC1D,GAAI,CAAC,EACH,MAAM,IAAI,EAAc,GAAG,gBAAqB,IAAK,CAAE,KAAM,gBAAiB,CAAC,EAEjF,OAAO,mBAAmB,CAAK,OAGnB,QAAO,EAA2B,CAC9C,GAAI,CAAC,KAAK,KAAM,CACd,IAAM,EACJ,KAAK,SACJ,MAAM,EAAqB,CAC1B,OAAQ,KAAK,UACT,KAAK,oBAAsB,OAC3B,CAAE,kBAAmB,KAAK,iBAAkB,EAC5C,CAAC,KACD,KAAK,MAAQ,OAAY,CAAE,IAAK,KAAK,GAAI,EAAI,CAAC,CACpD,CAAC,EAEH,KAAK,KAAO,IAAI,EAAc,CAC5B,KAAM,KAAK,KACX,KAAM,KAAK,KACX,OAAQ,KAAK,OACb,WAAY,KAAK,WACjB,aACI,KAAK,OAAS,OAAY,CAAE,KAAM,KAAK,IAAK,EAAI,CAAC,KACjD,KAAK,MAAQ,OAAY,CAAE,IAAK,KAAK,GAAI,EAAI,CAAC,KAC9C,KAAK,oBAAsB,OAC3B,CAAE,kBAAmB,KAAK,iBAAkB,EAC5C,CAAC,CACP,CAAC,EAEH,OAAO,KAAK,KAGN,UAAU,EAAgB,CAChC,MAAO,CACL,OAAQ,mBACR,eAAgB,kBAClB,OAGY,SAAQ,CAAC,EAAgB,EAAc,EAAmC,CACtF,IAAI,EACJ,GAAI,CACF,EAAW,MAAM,MAAM,GAAG,KAAK,SAAS,IAAQ,CAC9C,SACA,QAAS,KAAK,WAAW,KACrB,IAAS,OAAY,CAAE,KAAM,KAAK,UAAU,CAAI,CAAE,EAAI,CAAC,CAC7D,CAAC,EACD,MAAO,EAAK,CACZ,MAAM,IAAI,EACR,aAAe,MAAQ,EAAI,QAAU,8BACrC,EACA,CACF,EAGF,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,EAAO,MAAM,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EACjD,MAAM,IAAI,EACR,GAAQ,uBAAuB,EAAS,UACxC,EAAS,OACT,CACF,EAGF,OAAO,OAGK,QAAU,CAAC,EAAgB,EAAc,EAA4B,CAEjF,OAAQ,MADS,MAAM,KAAK,SAAS,EAAQ,EAAM,CAAI,GAChC,KAAK,OAGhB,MAAK,CAAC,EAAgB,EAAc,EAA+B,CAC/E,MAAM,KAAK,SAAS,EAAQ,EAAM,CAAI,EAE1C",
|
|
8
|
+
"debugId": "D6BB1EA239505BBC64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -45,7 +45,7 @@ export interface MailpitAddress {
|
|
|
45
45
|
/** Email address. */
|
|
46
46
|
Address: string;
|
|
47
47
|
}
|
|
48
|
-
/** Summary row from `GET /api/v1/messages`. */
|
|
48
|
+
/** Summary row from `GET /api/v1/messages` / `GET /api/v1/search`. */
|
|
49
49
|
export interface MailpitMessageSummary {
|
|
50
50
|
/** Mailpit message id. */
|
|
51
51
|
ID: string;
|
|
@@ -66,9 +66,9 @@ export interface MailpitMessageSummary {
|
|
|
66
66
|
/** Short plain-text snippet. */
|
|
67
67
|
Snippet: string;
|
|
68
68
|
}
|
|
69
|
-
/** Response from `GET /api/v1/messages`. */
|
|
69
|
+
/** Response from `GET /api/v1/messages` / `GET /api/v1/search`. */
|
|
70
70
|
export interface MailpitMessageList {
|
|
71
|
-
/** Total messages stored. */
|
|
71
|
+
/** Total messages stored (or matching the search). */
|
|
72
72
|
total: number;
|
|
73
73
|
/** Unread message count. */
|
|
74
74
|
unread: number;
|
|
@@ -102,13 +102,81 @@ export interface MailpitMessage {
|
|
|
102
102
|
/** Attachment count. */
|
|
103
103
|
Attachments: number;
|
|
104
104
|
}
|
|
105
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Message headers from `GET /api/v1/message/{id}/headers`.
|
|
107
|
+
* Keys are header names; values are one or more header lines.
|
|
108
|
+
*/
|
|
109
|
+
export type MailpitHeaders = Record<string, string[]>;
|
|
110
|
+
/** Aggregate counts from {@link MailpitHtmlCheck}. */
|
|
111
|
+
export interface MailpitHtmlCheckTotal {
|
|
112
|
+
/** Node count in the HTML. */
|
|
113
|
+
Nodes: number;
|
|
114
|
+
/** Partially supported checks. */
|
|
115
|
+
Partial: number;
|
|
116
|
+
/** Fully supported checks. */
|
|
117
|
+
Supported: number;
|
|
118
|
+
/** Total checks run. */
|
|
119
|
+
Tests: number;
|
|
120
|
+
/** Unsupported checks. */
|
|
121
|
+
Unsupported: number;
|
|
122
|
+
}
|
|
123
|
+
/** One warning row from Mailpit’s HTML checker. */
|
|
124
|
+
export interface MailpitHtmlCheckWarning {
|
|
125
|
+
/** Warning category. */
|
|
126
|
+
Category: string;
|
|
127
|
+
/** Human-readable description. */
|
|
128
|
+
Description: string;
|
|
129
|
+
/** Keyword summary. */
|
|
130
|
+
Keywords: string;
|
|
131
|
+
/** Title. */
|
|
132
|
+
Title: string;
|
|
133
|
+
/** caniemail.com URL when present. */
|
|
134
|
+
URL: string;
|
|
135
|
+
/** Per-platform support breakdown. */
|
|
136
|
+
Score: {
|
|
137
|
+
Found: number;
|
|
138
|
+
Partial: number;
|
|
139
|
+
Supported: number;
|
|
140
|
+
Unsupported: number;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** Response from `GET /api/v1/message/{id}/html-check`. */
|
|
144
|
+
export interface MailpitHtmlCheck {
|
|
145
|
+
/** Platforms covered by the check. */
|
|
146
|
+
Platforms: Record<string, string[]>;
|
|
147
|
+
/** Aggregate support totals. */
|
|
148
|
+
Total: MailpitHtmlCheckTotal;
|
|
149
|
+
/** Individual warnings. */
|
|
150
|
+
Warnings: MailpitHtmlCheckWarning[];
|
|
151
|
+
}
|
|
152
|
+
/** One link result from Mailpit’s link checker. */
|
|
153
|
+
export interface MailpitLinkCheckItem {
|
|
154
|
+
/** Status label (e.g. `"OK"`, `"Error"`). */
|
|
155
|
+
Status: string;
|
|
156
|
+
/** HTTP status code when available. */
|
|
157
|
+
StatusCode: number;
|
|
158
|
+
/** Checked URL. */
|
|
159
|
+
URL: string;
|
|
160
|
+
}
|
|
161
|
+
/** Response from `GET /api/v1/message/{id}/link-check`. */
|
|
162
|
+
export interface MailpitLinkCheck {
|
|
163
|
+
/** Number of failing links. */
|
|
164
|
+
Errors: number;
|
|
165
|
+
/** Per-URL results. */
|
|
166
|
+
Links: MailpitLinkCheckItem[];
|
|
167
|
+
}
|
|
168
|
+
/** Options for {@link MailpitTransport.messages} / {@link MailpitTransport.search}. */
|
|
106
169
|
export interface MailpitMessagesOptions {
|
|
107
170
|
/** Max messages to return. */
|
|
108
171
|
limit?: number;
|
|
109
172
|
/** Pagination offset. */
|
|
110
173
|
start?: number;
|
|
111
174
|
}
|
|
175
|
+
/** Options for {@link MailpitTransport.linkCheck}. */
|
|
176
|
+
export interface MailpitLinkCheckOptions {
|
|
177
|
+
/** Follow HTTP redirects when checking links. Default: `false`. */
|
|
178
|
+
follow?: boolean;
|
|
179
|
+
}
|
|
112
180
|
/** Error thrown when the Mailpit REST API returns a non-success response. */
|
|
113
181
|
export declare class MailpitError extends SentlyError {
|
|
114
182
|
readonly statusCode: number;
|
|
@@ -120,7 +188,7 @@ export declare class MailpitError extends SentlyError {
|
|
|
120
188
|
* Development transport for [Mailpit](https://github.com/axllent/mailpit).
|
|
121
189
|
*
|
|
122
190
|
* Sends via SMTP (defaults: `localhost:1025`) and exposes REST helpers for
|
|
123
|
-
* listing, reading, and deleting captured messages.
|
|
191
|
+
* listing, searching, reading, checking, and deleting captured messages.
|
|
124
192
|
*/
|
|
125
193
|
export declare class MailpitTransport implements Transport {
|
|
126
194
|
readonly provider = "mailpit";
|
|
@@ -150,8 +218,37 @@ export declare class MailpitTransport implements Transport {
|
|
|
150
218
|
* Newest messages appear first.
|
|
151
219
|
*/
|
|
152
220
|
messages(options?: MailpitMessagesOptions): Promise<MailpitMessageList>;
|
|
153
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Searches captured messages via `GET /api/v1/search`.
|
|
223
|
+
* Query syntax matches the Mailpit UI (`subject:`, `to:`, `tag:`, …).
|
|
224
|
+
*/
|
|
225
|
+
search(query: string, options?: MailpitMessagesOptions): Promise<MailpitMessageList>;
|
|
226
|
+
/**
|
|
227
|
+
* Fetches a full message via `GET /api/v1/message/{id}`.
|
|
228
|
+
* Pass `"latest"` for the newest message.
|
|
229
|
+
*/
|
|
154
230
|
getMessage(id: string): Promise<MailpitMessage>;
|
|
231
|
+
/**
|
|
232
|
+
* Fetches message headers via `GET /api/v1/message/{id}/headers`.
|
|
233
|
+
* Pass `"latest"` for the newest message.
|
|
234
|
+
*/
|
|
235
|
+
getHeaders(id: string): Promise<MailpitHeaders>;
|
|
236
|
+
/**
|
|
237
|
+
* Runs Mailpit’s HTML/CSS client-compatibility check via
|
|
238
|
+
* `GET /api/v1/message/{id}/html-check`.
|
|
239
|
+
* Pass `"latest"` for the newest message.
|
|
240
|
+
*/
|
|
241
|
+
htmlCheck(id: string): Promise<MailpitHtmlCheck>;
|
|
242
|
+
/**
|
|
243
|
+
* Runs Mailpit’s link checker via `GET /api/v1/message/{id}/link-check`.
|
|
244
|
+
* Pass `"latest"` for the newest message.
|
|
245
|
+
*/
|
|
246
|
+
linkCheck(id: string, options?: MailpitLinkCheckOptions): Promise<MailpitLinkCheck>;
|
|
247
|
+
/**
|
|
248
|
+
* Sets read status via `PUT /api/v1/messages`.
|
|
249
|
+
* Pass an empty `ids` array to update every message in the mailbox.
|
|
250
|
+
*/
|
|
251
|
+
setRead(ids: string[], read: boolean): Promise<void>;
|
|
155
252
|
/**
|
|
156
253
|
* Deletes messages by id via `DELETE /api/v1/messages`.
|
|
157
254
|
* Pass an empty array (or call {@link deleteAll}) to clear the inbox.
|
|
@@ -159,8 +256,11 @@ export declare class MailpitTransport implements Transport {
|
|
|
159
256
|
deleteMessages(ids: string[]): Promise<void>;
|
|
160
257
|
/** Deletes every captured message. */
|
|
161
258
|
deleteAll(): Promise<void>;
|
|
259
|
+
private listPath;
|
|
260
|
+
private encodeId;
|
|
162
261
|
private getSmtp;
|
|
163
262
|
private apiHeaders;
|
|
164
|
-
private
|
|
165
|
-
private
|
|
263
|
+
private apiFetch;
|
|
264
|
+
private apiJson;
|
|
265
|
+
private apiOk;
|
|
166
266
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{n as
|
|
1
|
+
import{n as Y}from"../chunk-a057jzsy.js";import"../chunk-f5x8vesr.js";import"../chunk-yg3kj723.js";import"../chunk-w4c9kj70.js";import"../chunk-1ke3nmyg.js";import{M as Q}from"../chunk-d5aw5vag.js";import"../chunk-8kpgbrba.js";import{da as V}from"../chunk-hnzmn4s4.js";import{ja as W,ka as X}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";var Z="localhost",$=1025,k="http://localhost:8025";class N extends W{statusCode;apiError;constructor(w,z,G){super(w,z===0?"CONNECTION_FAILED":X(z),{...z>0?{statusCode:z}:{},provider:"mailpit",cause:G});this.statusCode=z;this.apiError=G;this.name="MailpitError"}}class v{provider="mailpit";host;port;secure;requireTLS;auth;tls;connectionTimeout;adapter;apiUrl;apiAuth;smtp=null;constructor(w={}){if(this.host=w.host??Z,this.port=w.port??$,this.secure=w.secure??!1,this.requireTLS=w.requireTLS??!1,w.auth!==void 0)this.auth=w.auth;if(w.tls!==void 0)this.tls=w.tls;if(w.connectionTimeout!==void 0)this.connectionTimeout=w.connectionTimeout;if(w.adapter!==void 0)this.adapter=w.adapter;if(this.apiUrl=(w.apiUrl??k).replace(/\/$/,""),w.apiAuth!==void 0)this.apiAuth=w.apiAuth}get webUrl(){return this.apiUrl}async send(w){return(await this.getSmtp()).send(w)}async verify(){return{...await(await this.getSmtp()).verify(),provider:"mailpit"}}async close(){if(this.smtp)await this.smtp.close()}async messages(w={}){return this.apiJson("GET",this.listPath("/api/v1/messages",w))}async search(w,z={}){if(!w)throw new N("Search query is required",400,{code:"INVALID_CONFIG"});let G=new URLSearchParams;if(G.set("query",w),z.limit!==void 0)G.set("limit",String(z.limit));if(z.start!==void 0)G.set("start",String(z.start));return this.apiJson("GET",`/api/v1/search?${G.toString()}`)}async getMessage(w){return this.apiJson("GET",`/api/v1/message/${this.encodeId(w)}`)}async getHeaders(w){return this.apiJson("GET",`/api/v1/message/${this.encodeId(w)}/headers`)}async htmlCheck(w){return this.apiJson("GET",`/api/v1/message/${this.encodeId(w)}/html-check`)}async linkCheck(w,z={}){let G=new URLSearchParams;if(z.follow===!0)G.set("follow","true");let J=G.toString(),K=`/api/v1/message/${this.encodeId(w)}/link-check${J?`?${J}`:""}`;return this.apiJson("GET",K)}async setRead(w,z){await this.apiOk("PUT","/api/v1/messages",{IDs:w,Read:z})}async deleteMessages(w){await this.apiOk("DELETE","/api/v1/messages",{IDs:w})}async deleteAll(){await this.deleteMessages([])}listPath(w,z){let G=new URLSearchParams;if(z.limit!==void 0)G.set("limit",String(z.limit));if(z.start!==void 0)G.set("start",String(z.start));let J=G.toString();return J?`${w}?${J}`:w}encodeId(w){if(!w)throw new N("Message id is required",400,{code:"INVALID_CONFIG"});return encodeURIComponent(w)}async getSmtp(){if(!this.smtp){let w=this.adapter??await Q({secure:this.secure,...this.connectionTimeout!==void 0?{connectionTimeout:this.connectionTimeout}:{},...this.tls!==void 0?{tls:this.tls}:{}});this.smtp=new Y({host:this.host,port:this.port,secure:this.secure,requireTLS:this.requireTLS,adapter:w,...this.auth!==void 0?{auth:this.auth}:{},...this.tls!==void 0?{tls:this.tls}:{},...this.connectionTimeout!==void 0?{connectionTimeout:this.connectionTimeout}:{}})}return this.smtp}apiHeaders(){let w={Accept:"application/json","Content-Type":"application/json"};if(this.apiAuth){let z=V(`${this.apiAuth.user}:${this.apiAuth.pass}`).replace(/\r\n/g,"");w.Authorization=`Basic ${z}`}return w}async apiFetch(w,z,G){let J;try{J=await fetch(`${this.apiUrl}${z}`,{method:w,headers:this.apiHeaders(),...G!==void 0?{body:JSON.stringify(G)}:{}})}catch(K){throw new N(K instanceof Error?K.message:"Mailpit API request failed",0,K)}if(!J.ok){let K=await J.text().catch(()=>"");throw new N(K||`Mailpit API error (${J.status})`,J.status,K)}return J}async apiJson(w,z,G){return await(await this.apiFetch(w,z,G)).json()}async apiOk(w,z,G){await this.apiFetch(w,z,G)}}export{v as MailpitTransport,N as MailpitError,$ as MAILPIT_DEFAULT_PORT,Z as MAILPIT_DEFAULT_HOST,k as MAILPIT_DEFAULT_API_URL};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=051518FA2E8DE88C64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/mailpit.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Mailpit development transport — SMTP to a local Mailpit catcher with\n * REST helpers for inspecting and clearing captured messages.\n *\n * Defaults match a stock Mailpit install: SMTP `localhost:1025`,\n * UI/API `http://localhost:8025`.\n *\n * @example\n * ```ts\n * import { createMailer } from \"sently/mailer\";\n * import { MailpitTransport } from \"sently/transports/mailpit\";\n *\n * const mailpit = new MailpitTransport();\n * const mailer = await createMailer({ transport: mailpit });\n *\n * await mailer.send({\n * from: \"dev@example.com\",\n * to: \"you@example.com\",\n * subject: \"Hello\",\n * text: \"Captured by Mailpit\",\n * });\n *\n * const inbox = await mailpit.messages();\n * console.log(inbox.messages[0]?.Subject);\n * ```\n */\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPAuth,\n SocketAdapter,\n TLSOptions,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { createDefaultAdapter } from \"../detect.js\";\nimport { SMTPTransport } from \"./smtp.js\";\n\n/** Default SMTP host for a local Mailpit instance. */\nexport const MAILPIT_DEFAULT_HOST = \"localhost\";\n\n/** Default SMTP port for a local Mailpit instance. */\nexport const MAILPIT_DEFAULT_PORT = 1025;\n\n/** Default web UI / REST API base URL for a local Mailpit instance. */\nexport const MAILPIT_DEFAULT_API_URL = \"http://localhost:8025\";\n\n/** Mailpit development transport configuration. */\nexport interface MailpitConfig {\n /** SMTP hostname. Default: `\"localhost\"`. */\n host?: string;\n /** SMTP port. Default: `1025`. */\n port?: number;\n /** Use implicit TLS on connect. Default: `false` (Mailpit is plain SMTP). */\n secure?: boolean;\n /**\n * Refuse AUTH over a non-TLS connection.\n * Default: `false` so optional local SMTP auth works without TLS.\n */\n requireTLS?: boolean;\n /** Optional SMTP authentication (Mailpit can accept any credentials). */\n auth?: SMTPAuth;\n /** TLS options when STARTTLS or implicit TLS is enabled. */\n tls?: TLSOptions;\n /** Socket connect timeout in milliseconds. */\n connectionTimeout?: number;\n /** Runtime socket adapter. Auto-detected on first send when omitted. */\n adapter?: SocketAdapter;\n /**\n * Mailpit web UI / REST API base URL (no trailing slash).\n * Default: `\"http://localhost:8025\"`.\n */\n apiUrl?: string;\n /** Basic auth for the Mailpit UI/API when the instance requires it. */\n apiAuth?: { user: string; pass: string };\n}\n\n/** Address object returned by the Mailpit REST API. */\nexport interface MailpitAddress {\n /** Display name, may be empty. */\n Name: string;\n /** Email address. */\n Address: string;\n}\n\n/** Summary row from `GET /api/v1/messages`. */\nexport interface MailpitMessageSummary {\n /** Mailpit message id. */\n ID: string;\n /** MIME Message-ID. */\n MessageID: string;\n /** Sender. */\n From: MailpitAddress;\n /** Recipients. */\n To: MailpitAddress[];\n /** Subject line. */\n Subject: string;\n /** ISO created timestamp. */\n Created: string;\n /** Attachment count. */\n Attachments: number;\n /** Whether the message has been read in the UI. */\n Read: boolean;\n /** Short plain-text snippet. */\n Snippet: string;\n}\n\n/** Response from `GET /api/v1/messages`. */\nexport interface MailpitMessageList {\n /** Total messages stored. */\n total: number;\n /** Unread message count. */\n unread: number;\n /** Messages returned in this page. */\n count: number;\n /** Message summaries (newest first). */\n messages: MailpitMessageSummary[];\n}\n\n/** Full message from `GET /api/v1/message/{id}`. */\nexport interface MailpitMessage {\n /** Mailpit message id. */\n ID: string;\n /** MIME Message-ID. */\n MessageID: string;\n /** Sender. */\n From: MailpitAddress;\n /** Recipients. */\n To: MailpitAddress[];\n /** CC recipients. */\n Cc?: MailpitAddress[];\n /** BCC recipients. */\n Bcc?: MailpitAddress[];\n /** Subject line. */\n Subject: string;\n /** Plain-text body. */\n Text: string;\n /** HTML body. */\n HTML: string;\n /** ISO created timestamp. */\n Date: string;\n /** Attachment count. */\n Attachments: number;\n}\n\n/** Options for {@link MailpitTransport.messages}. */\nexport interface MailpitMessagesOptions {\n /** Max messages to return. */\n limit?: number;\n /** Pagination offset. */\n start?: number;\n}\n\n/** Error thrown when the Mailpit REST API returns a non-success response. */\nexport class MailpitError extends SentlyError {\n /** Creates a Mailpit API error with status code (`0` = network/connect failure). */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, statusCode === 0 ? \"CONNECTION_FAILED\" : httpStatusToSentlyCode(statusCode), {\n ...(statusCode > 0 ? { statusCode } : {}),\n provider: \"mailpit\",\n cause: apiError,\n });\n this.name = \"MailpitError\";\n }\n}\n\n/**\n * Development transport for [Mailpit](https://github.com/axllent/mailpit).\n *\n * Sends via SMTP (defaults: `localhost:1025`) and exposes REST helpers for\n * listing, reading, and deleting captured messages.\n */\nexport class MailpitTransport implements Transport {\n readonly provider = \"mailpit\";\n\n private readonly host: string;\n private readonly port: number;\n private readonly secure: boolean;\n private readonly requireTLS: boolean;\n private readonly auth?: SMTPAuth;\n private readonly tls?: TLSOptions;\n private readonly connectionTimeout?: number;\n private readonly adapter?: SocketAdapter;\n private readonly apiUrl: string;\n private readonly apiAuth?: { user: string; pass: string };\n\n private smtp: SMTPTransport | null = null;\n\n /** Creates a Mailpit transport with local-dev defaults. */\n constructor(config: MailpitConfig = {}) {\n this.host = config.host ?? MAILPIT_DEFAULT_HOST;\n this.port = config.port ?? MAILPIT_DEFAULT_PORT;\n this.secure = config.secure ?? false;\n this.requireTLS = config.requireTLS ?? false;\n if (config.auth !== undefined) {\n this.auth = config.auth;\n }\n if (config.tls !== undefined) {\n this.tls = config.tls;\n }\n if (config.connectionTimeout !== undefined) {\n this.connectionTimeout = config.connectionTimeout;\n }\n if (config.adapter !== undefined) {\n this.adapter = config.adapter;\n }\n this.apiUrl = (config.apiUrl ?? MAILPIT_DEFAULT_API_URL).replace(/\\/$/, \"\");\n if (config.apiAuth !== undefined) {\n this.apiAuth = config.apiAuth;\n }\n }\n\n /** Web UI base URL (same origin as the REST API). */\n get webUrl(): string {\n return this.apiUrl;\n }\n\n /** Sends an email to Mailpit over SMTP. */\n async send(options: MailOptions): Promise<SendResult> {\n return (await this.getSmtp()).send(options);\n }\n\n /** Verifies SMTP connectivity to Mailpit. */\n async verify(): Promise<VerifyResult> {\n const result = await (await this.getSmtp()).verify();\n return {\n ...result,\n provider: \"mailpit\",\n };\n }\n\n /** Closes the underlying SMTP adapter if connected. */\n async close(): Promise<void> {\n if (this.smtp) {\n await this.smtp.close();\n }\n }\n\n /**\n * Lists captured messages via `GET /api/v1/messages`.\n * Newest messages appear first.\n */\n async messages(options: MailpitMessagesOptions = {}): Promise<MailpitMessageList> {\n const params = new URLSearchParams();\n if (options.limit !== undefined) {\n params.set(\"limit\", String(options.limit));\n }\n if (options.start !== undefined) {\n params.set(\"start\", String(options.start));\n }\n const query = params.toString();\n const path = query ? `/api/v1/messages?${query}` : \"/api/v1/messages\";\n return this.apiGet<MailpitMessageList>(path);\n }\n\n /** Fetches a full message via `GET /api/v1/message/{id}`. */\n async getMessage(id: string): Promise<MailpitMessage> {\n if (!id) {\n throw new MailpitError(\"Message id is required\", 400, { code: \"INVALID_CONFIG\" });\n }\n return this.apiGet<MailpitMessage>(`/api/v1/message/${encodeURIComponent(id)}`);\n }\n\n /**\n * Deletes messages by id via `DELETE /api/v1/messages`.\n * Pass an empty array (or call {@link deleteAll}) to clear the inbox.\n */\n async deleteMessages(ids: string[]): Promise<void> {\n await this.apiDelete(\"/api/v1/messages\", { IDs: ids });\n }\n\n /** Deletes every captured message. */\n async deleteAll(): Promise<void> {\n await this.deleteMessages([]);\n }\n\n private async getSmtp(): Promise<SMTPTransport> {\n if (!this.smtp) {\n const adapter =\n this.adapter ??\n (await createDefaultAdapter({\n secure: this.secure,\n ...(this.connectionTimeout !== undefined\n ? { connectionTimeout: this.connectionTimeout }\n : {}),\n ...(this.tls !== undefined ? { tls: this.tls } : {}),\n }));\n\n this.smtp = new SMTPTransport({\n host: this.host,\n port: this.port,\n secure: this.secure,\n requireTLS: this.requireTLS,\n adapter,\n ...(this.auth !== undefined ? { auth: this.auth } : {}),\n ...(this.tls !== undefined ? { tls: this.tls } : {}),\n ...(this.connectionTimeout !== undefined\n ? { connectionTimeout: this.connectionTimeout }\n : {}),\n });\n }\n return this.smtp;\n }\n\n private apiHeaders(): HeadersInit {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n };\n if (this.apiAuth) {\n const token = encodeBase64(`${this.apiAuth.user}:${this.apiAuth.pass}`).replace(/\\r\\n/g, \"\");\n headers.Authorization = `Basic ${token}`;\n }\n return headers;\n }\n\n private async apiGet<T>(path: string): Promise<T> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}${path}`, {\n method: \"GET\",\n headers: this.apiHeaders(),\n });\n } catch (err) {\n throw new MailpitError(\n err instanceof Error ? err.message : \"Mailpit API request failed\",\n 0,\n err,\n );\n }\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new MailpitError(\n body || `Mailpit API error (${response.status})`,\n response.status,\n body,\n );\n }\n\n return (await response.json()) as T;\n }\n\n private async apiDelete(path: string, body: unknown): Promise<void> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}${path}`, {\n method: \"DELETE\",\n headers: this.apiHeaders(),\n body: JSON.stringify(body),\n });\n } catch (err) {\n throw new MailpitError(\n err instanceof Error ? err.message : \"Mailpit API request failed\",\n 0,\n err,\n );\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new MailpitError(\n text || `Mailpit API error (${response.status})`,\n response.status,\n text,\n );\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Mailpit development transport — SMTP to a local Mailpit catcher with\n * REST helpers for inspecting, searching, and checking captured messages.\n *\n * Defaults match a stock Mailpit install: SMTP `localhost:1025`,\n * UI/API `http://localhost:8025`.\n *\n * @example\n * ```ts\n * import { createMailer } from \"sently/mailer\";\n * import { MailpitTransport } from \"sently/transports/mailpit\";\n *\n * const mailpit = new MailpitTransport();\n * const mailer = await createMailer({ transport: mailpit });\n *\n * await mailer.send({\n * from: \"dev@example.com\",\n * to: \"you@example.com\",\n * subject: \"Hello\",\n * text: \"Captured by Mailpit\",\n * });\n *\n * const inbox = await mailpit.messages();\n * console.log(inbox.messages[0]?.Subject);\n * ```\n */\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPAuth,\n SocketAdapter,\n TLSOptions,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { createDefaultAdapter } from \"../detect.js\";\nimport { SMTPTransport } from \"./smtp.js\";\n\n/** Default SMTP host for a local Mailpit instance. */\nexport const MAILPIT_DEFAULT_HOST = \"localhost\";\n\n/** Default SMTP port for a local Mailpit instance. */\nexport const MAILPIT_DEFAULT_PORT = 1025;\n\n/** Default web UI / REST API base URL for a local Mailpit instance. */\nexport const MAILPIT_DEFAULT_API_URL = \"http://localhost:8025\";\n\n/** Mailpit development transport configuration. */\nexport interface MailpitConfig {\n /** SMTP hostname. Default: `\"localhost\"`. */\n host?: string;\n /** SMTP port. Default: `1025`. */\n port?: number;\n /** Use implicit TLS on connect. Default: `false` (Mailpit is plain SMTP). */\n secure?: boolean;\n /**\n * Refuse AUTH over a non-TLS connection.\n * Default: `false` so optional local SMTP auth works without TLS.\n */\n requireTLS?: boolean;\n /** Optional SMTP authentication (Mailpit can accept any credentials). */\n auth?: SMTPAuth;\n /** TLS options when STARTTLS or implicit TLS is enabled. */\n tls?: TLSOptions;\n /** Socket connect timeout in milliseconds. */\n connectionTimeout?: number;\n /** Runtime socket adapter. Auto-detected on first send when omitted. */\n adapter?: SocketAdapter;\n /**\n * Mailpit web UI / REST API base URL (no trailing slash).\n * Default: `\"http://localhost:8025\"`.\n */\n apiUrl?: string;\n /** Basic auth for the Mailpit UI/API when the instance requires it. */\n apiAuth?: { user: string; pass: string };\n}\n\n/** Address object returned by the Mailpit REST API. */\nexport interface MailpitAddress {\n /** Display name, may be empty. */\n Name: string;\n /** Email address. */\n Address: string;\n}\n\n/** Summary row from `GET /api/v1/messages` / `GET /api/v1/search`. */\nexport interface MailpitMessageSummary {\n /** Mailpit message id. */\n ID: string;\n /** MIME Message-ID. */\n MessageID: string;\n /** Sender. */\n From: MailpitAddress;\n /** Recipients. */\n To: MailpitAddress[];\n /** Subject line. */\n Subject: string;\n /** ISO created timestamp. */\n Created: string;\n /** Attachment count. */\n Attachments: number;\n /** Whether the message has been read in the UI. */\n Read: boolean;\n /** Short plain-text snippet. */\n Snippet: string;\n}\n\n/** Response from `GET /api/v1/messages` / `GET /api/v1/search`. */\nexport interface MailpitMessageList {\n /** Total messages stored (or matching the search). */\n total: number;\n /** Unread message count. */\n unread: number;\n /** Messages returned in this page. */\n count: number;\n /** Message summaries (newest first). */\n messages: MailpitMessageSummary[];\n}\n\n/** Full message from `GET /api/v1/message/{id}`. */\nexport interface MailpitMessage {\n /** Mailpit message id. */\n ID: string;\n /** MIME Message-ID. */\n MessageID: string;\n /** Sender. */\n From: MailpitAddress;\n /** Recipients. */\n To: MailpitAddress[];\n /** CC recipients. */\n Cc?: MailpitAddress[];\n /** BCC recipients. */\n Bcc?: MailpitAddress[];\n /** Subject line. */\n Subject: string;\n /** Plain-text body. */\n Text: string;\n /** HTML body. */\n HTML: string;\n /** ISO created timestamp. */\n Date: string;\n /** Attachment count. */\n Attachments: number;\n}\n\n/**\n * Message headers from `GET /api/v1/message/{id}/headers`.\n * Keys are header names; values are one or more header lines.\n */\nexport type MailpitHeaders = Record<string, string[]>;\n\n/** Aggregate counts from {@link MailpitHtmlCheck}. */\nexport interface MailpitHtmlCheckTotal {\n /** Node count in the HTML. */\n Nodes: number;\n /** Partially supported checks. */\n Partial: number;\n /** Fully supported checks. */\n Supported: number;\n /** Total checks run. */\n Tests: number;\n /** Unsupported checks. */\n Unsupported: number;\n}\n\n/** One warning row from Mailpit’s HTML checker. */\nexport interface MailpitHtmlCheckWarning {\n /** Warning category. */\n Category: string;\n /** Human-readable description. */\n Description: string;\n /** Keyword summary. */\n Keywords: string;\n /** Title. */\n Title: string;\n /** caniemail.com URL when present. */\n URL: string;\n /** Per-platform support breakdown. */\n Score: {\n Found: number;\n Partial: number;\n Supported: number;\n Unsupported: number;\n };\n}\n\n/** Response from `GET /api/v1/message/{id}/html-check`. */\nexport interface MailpitHtmlCheck {\n /** Platforms covered by the check. */\n Platforms: Record<string, string[]>;\n /** Aggregate support totals. */\n Total: MailpitHtmlCheckTotal;\n /** Individual warnings. */\n Warnings: MailpitHtmlCheckWarning[];\n}\n\n/** One link result from Mailpit’s link checker. */\nexport interface MailpitLinkCheckItem {\n /** Status label (e.g. `\"OK\"`, `\"Error\"`). */\n Status: string;\n /** HTTP status code when available. */\n StatusCode: number;\n /** Checked URL. */\n URL: string;\n}\n\n/** Response from `GET /api/v1/message/{id}/link-check`. */\nexport interface MailpitLinkCheck {\n /** Number of failing links. */\n Errors: number;\n /** Per-URL results. */\n Links: MailpitLinkCheckItem[];\n}\n\n/** Options for {@link MailpitTransport.messages} / {@link MailpitTransport.search}. */\nexport interface MailpitMessagesOptions {\n /** Max messages to return. */\n limit?: number;\n /** Pagination offset. */\n start?: number;\n}\n\n/** Options for {@link MailpitTransport.linkCheck}. */\nexport interface MailpitLinkCheckOptions {\n /** Follow HTTP redirects when checking links. Default: `false`. */\n follow?: boolean;\n}\n\n/** Error thrown when the Mailpit REST API returns a non-success response. */\nexport class MailpitError extends SentlyError {\n /** Creates a Mailpit API error with status code (`0` = network/connect failure). */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, statusCode === 0 ? \"CONNECTION_FAILED\" : httpStatusToSentlyCode(statusCode), {\n ...(statusCode > 0 ? { statusCode } : {}),\n provider: \"mailpit\",\n cause: apiError,\n });\n this.name = \"MailpitError\";\n }\n}\n\n/**\n * Development transport for [Mailpit](https://github.com/axllent/mailpit).\n *\n * Sends via SMTP (defaults: `localhost:1025`) and exposes REST helpers for\n * listing, searching, reading, checking, and deleting captured messages.\n */\nexport class MailpitTransport implements Transport {\n readonly provider = \"mailpit\";\n\n private readonly host: string;\n private readonly port: number;\n private readonly secure: boolean;\n private readonly requireTLS: boolean;\n private readonly auth?: SMTPAuth;\n private readonly tls?: TLSOptions;\n private readonly connectionTimeout?: number;\n private readonly adapter?: SocketAdapter;\n private readonly apiUrl: string;\n private readonly apiAuth?: { user: string; pass: string };\n\n private smtp: SMTPTransport | null = null;\n\n /** Creates a Mailpit transport with local-dev defaults. */\n constructor(config: MailpitConfig = {}) {\n this.host = config.host ?? MAILPIT_DEFAULT_HOST;\n this.port = config.port ?? MAILPIT_DEFAULT_PORT;\n this.secure = config.secure ?? false;\n this.requireTLS = config.requireTLS ?? false;\n if (config.auth !== undefined) {\n this.auth = config.auth;\n }\n if (config.tls !== undefined) {\n this.tls = config.tls;\n }\n if (config.connectionTimeout !== undefined) {\n this.connectionTimeout = config.connectionTimeout;\n }\n if (config.adapter !== undefined) {\n this.adapter = config.adapter;\n }\n this.apiUrl = (config.apiUrl ?? MAILPIT_DEFAULT_API_URL).replace(/\\/$/, \"\");\n if (config.apiAuth !== undefined) {\n this.apiAuth = config.apiAuth;\n }\n }\n\n /** Web UI base URL (same origin as the REST API). */\n get webUrl(): string {\n return this.apiUrl;\n }\n\n /** Sends an email to Mailpit over SMTP. */\n async send(options: MailOptions): Promise<SendResult> {\n return (await this.getSmtp()).send(options);\n }\n\n /** Verifies SMTP connectivity to Mailpit. */\n async verify(): Promise<VerifyResult> {\n const result = await (await this.getSmtp()).verify();\n return {\n ...result,\n provider: \"mailpit\",\n };\n }\n\n /** Closes the underlying SMTP adapter if connected. */\n async close(): Promise<void> {\n if (this.smtp) {\n await this.smtp.close();\n }\n }\n\n /**\n * Lists captured messages via `GET /api/v1/messages`.\n * Newest messages appear first.\n */\n async messages(options: MailpitMessagesOptions = {}): Promise<MailpitMessageList> {\n return this.apiJson<MailpitMessageList>(\"GET\", this.listPath(\"/api/v1/messages\", options));\n }\n\n /**\n * Searches captured messages via `GET /api/v1/search`.\n * Query syntax matches the Mailpit UI (`subject:`, `to:`, `tag:`, …).\n */\n async search(query: string, options: MailpitMessagesOptions = {}): Promise<MailpitMessageList> {\n if (!query) {\n throw new MailpitError(\"Search query is required\", 400, { code: \"INVALID_CONFIG\" });\n }\n const params = new URLSearchParams();\n params.set(\"query\", query);\n if (options.limit !== undefined) {\n params.set(\"limit\", String(options.limit));\n }\n if (options.start !== undefined) {\n params.set(\"start\", String(options.start));\n }\n return this.apiJson<MailpitMessageList>(\"GET\", `/api/v1/search?${params.toString()}`);\n }\n\n /**\n * Fetches a full message via `GET /api/v1/message/{id}`.\n * Pass `\"latest\"` for the newest message.\n */\n async getMessage(id: string): Promise<MailpitMessage> {\n return this.apiJson<MailpitMessage>(\"GET\", `/api/v1/message/${this.encodeId(id)}`);\n }\n\n /**\n * Fetches message headers via `GET /api/v1/message/{id}/headers`.\n * Pass `\"latest\"` for the newest message.\n */\n async getHeaders(id: string): Promise<MailpitHeaders> {\n return this.apiJson<MailpitHeaders>(\"GET\", `/api/v1/message/${this.encodeId(id)}/headers`);\n }\n\n /**\n * Runs Mailpit’s HTML/CSS client-compatibility check via\n * `GET /api/v1/message/{id}/html-check`.\n * Pass `\"latest\"` for the newest message.\n */\n async htmlCheck(id: string): Promise<MailpitHtmlCheck> {\n return this.apiJson<MailpitHtmlCheck>(\"GET\", `/api/v1/message/${this.encodeId(id)}/html-check`);\n }\n\n /**\n * Runs Mailpit’s link checker via `GET /api/v1/message/{id}/link-check`.\n * Pass `\"latest\"` for the newest message.\n */\n async linkCheck(id: string, options: MailpitLinkCheckOptions = {}): Promise<MailpitLinkCheck> {\n const params = new URLSearchParams();\n if (options.follow === true) {\n params.set(\"follow\", \"true\");\n }\n const query = params.toString();\n const path = `/api/v1/message/${this.encodeId(id)}/link-check${query ? `?${query}` : \"\"}`;\n return this.apiJson<MailpitLinkCheck>(\"GET\", path);\n }\n\n /**\n * Sets read status via `PUT /api/v1/messages`.\n * Pass an empty `ids` array to update every message in the mailbox.\n */\n async setRead(ids: string[], read: boolean): Promise<void> {\n await this.apiOk(\"PUT\", \"/api/v1/messages\", { IDs: ids, Read: read });\n }\n\n /**\n * Deletes messages by id via `DELETE /api/v1/messages`.\n * Pass an empty array (or call {@link deleteAll}) to clear the inbox.\n */\n async deleteMessages(ids: string[]): Promise<void> {\n await this.apiOk(\"DELETE\", \"/api/v1/messages\", { IDs: ids });\n }\n\n /** Deletes every captured message. */\n async deleteAll(): Promise<void> {\n await this.deleteMessages([]);\n }\n\n private listPath(base: string, options: MailpitMessagesOptions): string {\n const params = new URLSearchParams();\n if (options.limit !== undefined) {\n params.set(\"limit\", String(options.limit));\n }\n if (options.start !== undefined) {\n params.set(\"start\", String(options.start));\n }\n const query = params.toString();\n return query ? `${base}?${query}` : base;\n }\n\n private encodeId(id: string): string {\n if (!id) {\n throw new MailpitError(\"Message id is required\", 400, { code: \"INVALID_CONFIG\" });\n }\n return encodeURIComponent(id);\n }\n\n private async getSmtp(): Promise<SMTPTransport> {\n if (!this.smtp) {\n const adapter =\n this.adapter ??\n (await createDefaultAdapter({\n secure: this.secure,\n ...(this.connectionTimeout !== undefined\n ? { connectionTimeout: this.connectionTimeout }\n : {}),\n ...(this.tls !== undefined ? { tls: this.tls } : {}),\n }));\n\n this.smtp = new SMTPTransport({\n host: this.host,\n port: this.port,\n secure: this.secure,\n requireTLS: this.requireTLS,\n adapter,\n ...(this.auth !== undefined ? { auth: this.auth } : {}),\n ...(this.tls !== undefined ? { tls: this.tls } : {}),\n ...(this.connectionTimeout !== undefined\n ? { connectionTimeout: this.connectionTimeout }\n : {}),\n });\n }\n return this.smtp;\n }\n\n private apiHeaders(): HeadersInit {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n };\n if (this.apiAuth) {\n const token = encodeBase64(`${this.apiAuth.user}:${this.apiAuth.pass}`).replace(/\\r\\n/g, \"\");\n headers.Authorization = `Basic ${token}`;\n }\n return headers;\n }\n\n private async apiFetch(method: string, path: string, body?: unknown): Promise<Response> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}${path}`, {\n method,\n headers: this.apiHeaders(),\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n } catch (err) {\n throw new MailpitError(\n err instanceof Error ? err.message : \"Mailpit API request failed\",\n 0,\n err,\n );\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new MailpitError(\n text || `Mailpit API error (${response.status})`,\n response.status,\n text,\n );\n }\n\n return response;\n }\n\n private async apiJson<T>(method: string, path: string, body?: unknown): Promise<T> {\n const response = await this.apiFetch(method, path, body);\n return (await response.json()) as T;\n }\n\n private async apiOk(method: string, path: string, body?: unknown): Promise<void> {\n await this.apiFetch(method, path, body);\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "yYA0CO,DAAM,HAAuB,OAGvB,HAAuB,AAGvB,HAA0B,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "051518FA2E8DE88C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|