edge-mailer 0.6.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/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # edge-mailer
2
+
3
+ ## 0.6.0
4
+
5
+ - Start public prerelease versioning at `v0.6.0`.
6
+ - Publish the Cloudflare Workers SMTP toolkit as `edge-mailer` on npm.
7
+ - Publish the Deno-compatible source package as `@sparticle9/edge-mailer` on JSR.
8
+ - Include Cloudflare Workers as the production baseline and Deno CLI / Deno Deploy v2 as experimental runtime support.
@@ -0,0 +1,347 @@
1
+ # Client Integration
2
+
3
+ Edge Mailer exposes one SMTP client API with runtime-specific entrypoints.
4
+ Cloudflare Workers and Deno are both first-class edge runtimes. Cloudflare
5
+ Workers was implemented first; it is not a priority tier over Deno.
6
+
7
+ ## Runtime Imports
8
+
9
+ Cloudflare Workers:
10
+
11
+ ```ts
12
+ import { EdgeMailer } from 'edge-mailer/cloudflare'
13
+ ```
14
+
15
+ Deno:
16
+
17
+ ```ts
18
+ import { DenoMailer } from 'edge-mailer/deno'
19
+ ```
20
+
21
+ The default package entrypoint remains Cloudflare-compatible:
22
+
23
+ ```ts
24
+ import { EdgeMailer } from 'edge-mailer'
25
+ ```
26
+
27
+ ## Minimal Send
28
+
29
+ ```ts
30
+ const receipt = await EdgeMailer.send(
31
+ {
32
+ host: 'smtp.example.com',
33
+ port: 587,
34
+ secure: false,
35
+ startTls: true,
36
+ credentials: {
37
+ username: 'sender@example.com',
38
+ password: env.SMTP_PASSWORD,
39
+ },
40
+ authType: ['plain', 'login'],
41
+ },
42
+ {
43
+ from: 'sender@example.com',
44
+ to: 'recipient@example.net',
45
+ subject: 'Hello',
46
+ text: 'Hello from Edge Mailer.',
47
+ },
48
+ )
49
+ ```
50
+
51
+ `send()` resolves to a structured receipt:
52
+
53
+ ```ts
54
+ console.log(receipt.messageId)
55
+ console.log(receipt.accepted)
56
+ console.log(receipt.responseCode)
57
+ ```
58
+
59
+ Use `secure: true` with port `465` for implicit TLS. Use `secure: false` and
60
+ `startTls: true` with port `587` for STARTTLS. If `credentials` are omitted,
61
+ the client does not attempt `AUTH`; servers that require auth will reject the
62
+ envelope command.
63
+
64
+ ## Reusing A Session
65
+
66
+ Use `connect()` when one request, queue batch, or job sends more than one
67
+ message:
68
+
69
+ ```ts
70
+ const mailer = await EdgeMailer.connect(config)
71
+ try {
72
+ await mailer.send(firstEmail)
73
+ await mailer.send(secondEmail)
74
+ } finally {
75
+ await mailer.close()
76
+ }
77
+ ```
78
+
79
+ For ordered batch results:
80
+
81
+ ```ts
82
+ const results = await EdgeMailer.sendBatch(config, emails, {
83
+ continueOnError: true,
84
+ })
85
+ ```
86
+
87
+ When `continueOnError` is true, a failed message is followed by `RSET` before
88
+ the next message is attempted on the same SMTP session.
89
+
90
+ Use a bounded pool when one invocation needs concurrent or repeated SMTP sends:
91
+
92
+ ```ts
93
+ const pool = EdgeMailer.createPool({
94
+ ...config,
95
+ pool: {
96
+ maxConnections: 2,
97
+ maxMessagesPerConnection: 50,
98
+ idleTimeoutMs: 30_000,
99
+ },
100
+ })
101
+
102
+ try {
103
+ const receipt = await pool.send(email)
104
+ console.log(receipt.messageId)
105
+ } finally {
106
+ await pool.close()
107
+ }
108
+ ```
109
+
110
+ For Cloudflare Workers, create and close pools inside a request, queue, or
111
+ scheduled handler. Do not rely on a global SMTP socket surviving across Worker
112
+ invocations.
113
+
114
+ ## DKIM
115
+
116
+ Set `config.dkim` only when Edge Mailer should sign outbound messages before
117
+ SMTP `DATA`:
118
+
119
+ ```ts
120
+ await EdgeMailer.send(
121
+ {
122
+ ...config,
123
+ dkim: {
124
+ domainName: 'example.com',
125
+ keySelector: 'mail',
126
+ privateKey: env.DKIM_PRIVATE_KEY,
127
+ },
128
+ },
129
+ email,
130
+ )
131
+ ```
132
+
133
+ If your SMTP provider manages DKIM for a verified sending domain, leave
134
+ `config.dkim` unset. Provider-managed DKIM means the provider stores or
135
+ generates the private key and asks you to publish only the DNS TXT public key,
136
+ usually at `<selector>._domainkey.<domain>` with `p=<base64-public-key>`.
137
+
138
+ If Edge Mailer signs, `privateKey` accepts PEM PKCS#8 private keys and RSA
139
+ private keys. That private key must match the DNS TXT public key for
140
+ `dkim.keySelector` and `dkim.domainName`. The DKIM `d=` domain should align with
141
+ the visible `From:` domain when DKIM is used for DMARC. Multiple DKIM signatures
142
+ are valid, but avoid double-signing unless you intentionally need both Edge
143
+ Mailer and the provider to sign.
144
+
145
+ The default signed header list is `from`, `to`, `subject`, `date`,
146
+ `message-id`, `mime-version`, and `content-type`; override it with
147
+ `dkim.headerFieldNames`.
148
+
149
+ ## MIME Attachments
150
+
151
+ Attachments from strings default to the previous base64-string behavior:
152
+
153
+ ```ts
154
+ attachments: [
155
+ {
156
+ filename: 'report.txt',
157
+ content: btoa('report body'),
158
+ mimeType: 'text/plain',
159
+ },
160
+ ]
161
+ ```
162
+
163
+ For new binary attachments, pass raw bytes directly. `Uint8Array`,
164
+ `ArrayBuffer`, typed-array views, and `Blob` content are encoded as MIME base64
165
+ by the library; `Blob.type` is used as the content type when no `mimeType` or
166
+ `contentType` is provided. Mailer sends handle `Blob` asynchronously. Direct
167
+ `Email` callers should use `getEmailDataAsync()` for `Blob` attachments.
168
+
169
+ ```ts
170
+ attachments: [
171
+ {
172
+ filename: 'report.pdf',
173
+ content: reportBytes, // Uint8Array or ArrayBuffer
174
+ mimeType: 'application/pdf',
175
+ },
176
+ {
177
+ filename: 'snapshot.json',
178
+ content: new Blob([JSON.stringify(snapshot)], {
179
+ type: 'application/json',
180
+ }),
181
+ },
182
+ ]
183
+ ```
184
+
185
+ Use `encoding` when the attachment content is raw text:
186
+
187
+ ```ts
188
+ attachments: [
189
+ {
190
+ filename: 'plain.txt',
191
+ content: 'plain ascii body',
192
+ mimeType: 'text/plain',
193
+ encoding: '7bit',
194
+ },
195
+ {
196
+ filename: 'utf8.txt',
197
+ content: 'ümlaut body',
198
+ mimeType: 'text/plain',
199
+ encoding: 'quoted-printable',
200
+ },
201
+ ]
202
+ ```
203
+
204
+ Inline parts use `contentId` and are wrapped in `multipart/related`:
205
+
206
+ ```ts
207
+ await EdgeMailer.send(config, {
208
+ from: 'sender@example.com',
209
+ to: 'recipient@example.net',
210
+ subject: 'Inline image',
211
+ html: '<img src="cid:logo">',
212
+ attachments: [
213
+ {
214
+ filename: 'logo.png',
215
+ content: logoBase64,
216
+ mimeType: 'image/png',
217
+ contentId: 'logo',
218
+ disposition: 'inline',
219
+ },
220
+ ],
221
+ })
222
+ ```
223
+
224
+ ## SMTP Feature Mapping
225
+
226
+ The client uses server-advertised EHLO capabilities and only sends extension
227
+ parameters when the server advertises support or the feature is required by the
228
+ message.
229
+
230
+ Supported SMTP features:
231
+
232
+ - `AUTH PLAIN`, `AUTH LOGIN`, and `AUTH CRAM-MD5`
233
+ - `STARTTLS` and implicit TLS
234
+ - `PIPELINING`
235
+ - Enhanced status code parsing from server replies
236
+ - `SIZE`
237
+ - `8BITMIME` through `envelope.body`
238
+ - `SMTPUTF8` through `envelope.smtpUtf8` or non-ASCII envelope addresses
239
+ - `REQUIRETLS` through `envelope.requireTls`
240
+ - `DSN` through `dsn` defaults and per-message `dsnOverride`
241
+ - DKIM signing before `DATA`
242
+ - Multipart alternative, related, and mixed MIME composition
243
+ - Attachments from base64 strings, raw bytes, or `Blob` content with `base64`,
244
+ `7bit`, and `quoted-printable` transfer encodings
245
+
246
+ ## Envelope Options
247
+
248
+ The visible message headers and SMTP envelope can differ. Use `envelope` when a
249
+ bounce address, delivery recipient set, or SMTP extension parameter should not
250
+ be inferred from headers.
251
+
252
+ ```ts
253
+ await EdgeMailer.send(config, {
254
+ from: { name: 'Product', email: 'sender@example.com' },
255
+ to: 'visible@example.net',
256
+ bcc: 'audit@example.net',
257
+ envelope: {
258
+ from: 'bounce@example.com',
259
+ to: ['visible@example.net', 'audit@example.net'],
260
+ body: '8BITMIME',
261
+ smtpUtf8: true,
262
+ requireTls: true,
263
+ },
264
+ subject: 'Delivery',
265
+ text: 'Message body.',
266
+ })
267
+ ```
268
+
269
+ `envelope.size` can be supplied when an upstream system already knows the DATA
270
+ size. Otherwise the client computes `SIZE` from the encoded message it sends.
271
+
272
+ ## DSN Options
273
+
274
+ Set session defaults with `config.dsn` and override per message with
275
+ `dsnOverride`.
276
+
277
+ ```ts
278
+ const config = {
279
+ host: 'smtp.example.com',
280
+ port: 587,
281
+ startTls: true,
282
+ credentials,
283
+ dsn: {
284
+ RET: { HEADERS: true },
285
+ NOTIFY: { FAILURE: true, DELAY: true },
286
+ },
287
+ }
288
+
289
+ await EdgeMailer.send(config, {
290
+ from: 'sender@example.com',
291
+ to: 'recipient@example.net',
292
+ dsnOverride: {
293
+ envelopeId: 'order-123',
294
+ RET: { FULL: true },
295
+ NOTIFY: { SUCCESS: true, FAILURE: true },
296
+ ORCPT: 'original@example.net',
297
+ },
298
+ subject: 'DSN request',
299
+ text: 'Message body.',
300
+ })
301
+ ```
302
+
303
+ `ORCPT` values without an address type are sent as `rfc822;<address>`.
304
+
305
+ ## Error Handling
306
+
307
+ SMTP failures throw `SMTPError` when the failure is known to come from the SMTP
308
+ conversation.
309
+
310
+ ```ts
311
+ try {
312
+ await EdgeMailer.send(config, email)
313
+ } catch (error) {
314
+ if (error instanceof SMTPError) {
315
+ console.log(error.stage)
316
+ console.log(error.command)
317
+ console.log(error.responseCode)
318
+ console.log(error.enhancedStatusCode)
319
+ console.log(error.transient)
320
+ }
321
+ throw error
322
+ }
323
+ ```
324
+
325
+ Use `responseCode` and `enhancedStatusCode` for provider-specific routing, and
326
+ use `transient` for coarse retry decisions.
327
+
328
+ ## SMTP Core Verification
329
+
330
+ The SMTP core suite uses Nodemailer's `smtp-server` package as a real local
331
+ SMTP server. It validates the shared SMTP session logic against server-side
332
+ SMTP parsing for AUTH, TLS, STARTTLS, PIPELINING, SIZE, 8BITMIME, SMTPUTF8,
333
+ REQUIRETLS, DSN, and RSET recovery.
334
+
335
+ ```sh
336
+ pnpm run test:smtp-core
337
+ ```
338
+
339
+ Run the full local verification set:
340
+
341
+ ```sh
342
+ pnpm test -- --run
343
+ pnpm run test:smtp-core
344
+ pnpm run build
345
+ pnpm run check:deno
346
+ pnpm run test:deno
347
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sparticle9
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # Edge Mailer
2
+
3
+ Edge Mailer is a serverless SMTP submission toolkit for applications that need
4
+ to send through existing SMTP infrastructure from modern edge runtimes.
5
+
6
+ The current implementation supports Cloudflare Workers and Deno as first-class
7
+ edge runtimes. Cloudflare Workers was the first runtime implemented, but it is
8
+ not a priority tier over Deno. The public API is versioned as a `0.x`
9
+ prerelease surface.
10
+
11
+ Install from npm for Cloudflare Workers and Node-compatible build pipelines:
12
+
13
+ ```sh
14
+ pnpm add edge-mailer
15
+ ```
16
+
17
+ Use the JSR package for Deno:
18
+
19
+ ```sh
20
+ deno add jsr:@sparticle9/edge-mailer
21
+ ```
22
+
23
+ ## Scope
24
+
25
+ Supported today:
26
+
27
+ - Cloudflare Workers outbound TCP sockets through `edge-mailer/cloudflare`.
28
+ - Deno CLI and Deno Deploy v2 direct SMTP through `edge-mailer/deno`.
29
+ - SMTP over implicit TLS on port `465`.
30
+ - SMTP with STARTTLS on port `587`.
31
+ - `PLAIN`, `LOGIN`, and legacy `CRAM-MD5` authentication.
32
+ - SMTP extensions: `PIPELINING`, `SIZE`, `8BITMIME`, `SMTPUTF8`,
33
+ `REQUIRETLS`, and `DSN` when advertised by the server.
34
+ - Plain text, HTML, custom headers, CC, BCC, reply-to, inline/CID
35
+ attachments, raw `Uint8Array`/`ArrayBuffer`/`Blob` attachment content, and
36
+ attachment transfer encodings `base64`, `7bit`, and `quoted-printable`.
37
+ - Batch sending over one SMTP session and bounded connection pools.
38
+ - DKIM signing with RSA private keys.
39
+ - Structured send receipts with message id, envelope, accepted recipients, final
40
+ SMTP response, response code, and message size.
41
+ - Structured SMTP errors with stage, command, response code, enhanced status
42
+ code, and transient classification.
43
+
44
+ Not supported yet:
45
+
46
+ - Direct SMTP from Vercel Edge or other runtimes without outbound TCP sockets.
47
+ - XOAUTH2.
48
+ - True streaming SMTP `DATA` for large attachments.
49
+ - ICS/calendar invite helpers.
50
+ - HTTP provider SDK wrappers.
51
+
52
+ See [CLIENT-INTEGRATION.md](CLIENT-INTEGRATION.md) for client-side runtime
53
+ imports, SMTP options, envelope/DSN usage, and real-server functional
54
+ verification. See [DEVELOPMENT.md](DEVELOPMENT.md) for local checks, smoke
55
+ testing, runtime samples, and reporting guidance.
56
+
57
+ ## Runtime Entrypoints
58
+
59
+ Use the default import or Cloudflare subpath for Cloudflare Workers:
60
+
61
+ ```ts
62
+ import { EdgeMailer } from 'edge-mailer/cloudflare'
63
+ ```
64
+
65
+ Use the Deno subpath for Deno:
66
+
67
+ ```ts
68
+ import { DenoMailer } from 'jsr:@sparticle9/edge-mailer/deno'
69
+ ```
70
+
71
+ Runnable samples and deploy quickstarts live in [sample](sample):
72
+
73
+ - [sample/cloudflare-worker-smtp](sample/cloudflare-worker-smtp)
74
+ - [sample/deno-smtp](sample/deno-smtp)
75
+
76
+ ## Runtime Matrix
77
+
78
+ | Capability | Cloudflare Workers | Deno CLI / Deno Deploy v2 |
79
+ | ----------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
80
+ | Import path | `edge-mailer/cloudflare` | `edge-mailer/deno` |
81
+ | Runtime class | `EdgeMailer` | `DenoMailer` |
82
+ | Socket backend | `cloudflare:sockets` | `Deno.connect`, `Deno.connectTls`, `Deno.startTls` |
83
+ | Direct SMTP | Yes, outbound TCP sockets | Yes, Deno TCP/TLS sockets |
84
+ | Port `587` STARTTLS | Yes | Yes |
85
+ | Port `465` implicit TLS | Yes | Yes |
86
+ | Port `25` | Not supported by Cloudflare Workers | Not recommended; provider/runtime policy may vary |
87
+ | Auth | `PLAIN`, `LOGIN`, `CRAM-MD5` | `PLAIN`, `LOGIN`, `CRAM-MD5` |
88
+ | SMTP extensions | `PIPELINING`, `SIZE`, `8BITMIME`, `SMTPUTF8`, `REQUIRETLS`, `DSN` | Same shared SMTP core |
89
+ | Message features | Text, HTML, custom headers, CC/BCC, reply-to, inline/CID attachments, raw byte/Blob attachment inputs | Same shared MIME/message builder |
90
+ | Pooling and batch | Bounded pool, `send`, `sendBatch`, `sendMany` | Same API and behavior |
91
+ | DKIM | RSA DKIM signing before `DATA` | Same DKIM implementation |
92
+ | Smoke status | Local Wrangler smoke and live `workers.dev` SMTP acceptance passed | Local Deno SMTP tests and live Deno Deploy v2 SMTP acceptance passed |
93
+ | Status | First-class edge runtime support | First-class edge runtime support |
94
+
95
+ ## Roadmap
96
+
97
+ Cloudflare Workers and Deno are both first-class edge runtimes. Cloudflare
98
+ Workers was implemented first; future work should keep both runtimes aligned
99
+ unless a runtime-specific platform limit is documented. The next useful work is
100
+ grouped by product risk:
101
+
102
+ - Stabilization: publish a v0 package surface, keep runtime subpaths stable,
103
+ expand live smokes across at least two SMTP providers, and keep package
104
+ contents free of local reference material and secrets.
105
+ - Runtime coverage: continue hardening Deno Deploy v2, add CI-friendly sample
106
+ deployment checks, and document any runtime-specific socket limitations before
107
+ adding another runtime.
108
+ - Observability: add structured SMTP lifecycle events, redacted debug logging,
109
+ per-send timing, pool metrics, and smoke-test output that separates SMTP
110
+ acceptance from final inbox placement.
111
+ - Deliverability and operations: add clearer retry guidance from structured SMTP
112
+ errors, DKIM verification examples, and mailbox delivery caveats.
113
+ - Message features: add XOAUTH2, calendar invite helpers, richer MIME fixtures,
114
+ and safer large-attachment guidance around provider size limits.
115
+ - No-direct-SMTP runtimes: design a Worker relay path for environments that
116
+ cannot open TCP sockets directly, such as Vercel Edge.
117
+
118
+ Attachment ergonomics now favor raw `Uint8Array`, `ArrayBuffer`, and `Blob`
119
+ inputs while reducing avoidable base64 wrapping copies. True one-pass streaming
120
+ SMTP `DATA` remains deferred: DKIM needs the canonicalized body hash before the
121
+ signed headers are sent, SMTP `SIZE` and retries need repeatable byte sources,
122
+ and both Cloudflare Workers and Deno Deploy are more likely to benefit first
123
+ from predictable memory use and linked large assets than from a complex
124
+ streaming/spooling path. Revisit true streaming only if real users need large
125
+ in-message attachments rather than signed download links or small transactional
126
+ attachments.
@@ -0,0 +1 @@
1
+ import{g as a,h as i}from"./chunk-U6ERJXK6.mjs";import{connect as s}from"cloudflare:sockets";function l(t){return t==="on"?"on":t==="starttls"?"starttls":"off"}var p={connect(t){return s({hostname:t.hostname,port:t.port},{secureTransport:l(t.tls),allowHalfOpen:!1})}},c=class t extends a{constructor(e){super(e,p,"EdgeMailer")}static async connect(e){let o=new t(e);try{return await o.initializeSmtpSession(),o}catch(n){throw await o.abortConnection(n),n}}static async send(e,o){let n=await t.connect(e);try{return await n.send(o)}finally{await n.close()}}static async sendBatch(e,o,n={}){let r=await t.connect(e);try{return await r.sendMany(o,n)}finally{await r.close()}}static createPool(e){return new i(e,t.connect)}};export{p as a,c as b};
@@ -0,0 +1,67 @@
1
+ var H=new TextEncoder;function g(s){return H.encode(s)}var q=new TextDecoder("utf-8");function P(s){return q.decode(s)}function x(s,e=76){let t=g(s),r="",i=0,n=0;for(;n<t.length;){let a=t[n],o;if(a===10){r+=`\r
2
+ `,i=0,n++;continue}else if(a===13)if(n+1<t.length&&t[n+1]===10){r+=`\r
3
+ `,i=0,n+=2;continue}else o="=0D";if(o===void 0){let d=a===32||a===9,h=n+1>=t.length||t[n+1]===10||t[n+1]===13;a<32&&!d||a>126||a===61||d&&h?o=`=${a.toString(16).toUpperCase().padStart(2,"0")}`:o=String.fromCharCode(a)}i+o.length>e-3&&(r+=`=\r
4
+ `,i=0),r+=o,i+=o.length,n++}return r}function A(s){if(!/[^\x00-\x7F]/.test(s))return s;let e=g(s),t="";for(let r of e)r>=33&&r<=126&&r!==63&&r!==61&&r!==95?t+=String.fromCharCode(r):r===32?t+="_":t+=`=${r.toString(16).toUpperCase().padStart(2,"0")}`;return`=?UTF-8?Q?${t}?=`}var M=class s{from;to;reply;cc;bcc;subject;text;html;envelope;dsnOverride;attachments;headers;setSent;setSentError;sent=new Promise((e,t)=>{this.setSent=e,this.setSentError=t});constructor(e){if(!e.text&&!e.html)throw new Error("At least one of text or html must be provided");typeof e.from=="string"?this.from={email:e.from}:this.from=e.from,typeof e.reply=="string"?this.reply={email:e.reply}:this.reply=e.reply,this.to=s.toUsers(e.to),this.cc=s.toUsers(e.cc),this.bcc=s.toUsers(e.bcc),this.subject=e.subject,this.text=e.text,this.html=e.html,this.attachments=e.attachments,this.envelope=e.envelope?{...e.envelope,to:s.toEnvelopeRecipients(e.envelope.to)}:void 0,this.dsnOverride=e.dsnOverride,this.headers=e.headers||{}}static toUsers(e){if(e)return typeof e=="string"?[{email:e}]:Array.isArray(e)?e.map(t=>typeof t=="string"?{email:t}:t):[e]}static toEnvelopeRecipients(e){if(e)return Array.isArray(e)?e:[e]}getMessageData(){return this.buildMessageData(this.resolveAttachmentsSync())}async getMessageDataAsync(){return this.buildMessageData(await this.resolveAttachments())}buildMessageData(e){this.resolveHeader();let t=["MIME-Version: 1.0"];for(let[l,f]of Object.entries(this.headers))l.toLowerCase()!=="bcc"&&t.push(`${l}: ${f}`);let r=this.generateSafeBoundary("mixed_"),i=this.generateSafeBoundary("alternative_");t.push(`Content-Type: multipart/mixed; boundary="${r}"`);let a=`${t.join(`\r
5
+ `)}\r
6
+ \r
7
+ `,o=(e||[]).filter(l=>this.attachmentDisposition(l)==="inline"),d=(e||[]).filter(l=>this.attachmentDisposition(l)!=="inline"),h=this.generateSafeBoundary("related_");if(o.length?(a+=`--${r}\r
8
+ `,a+=`Content-Type: multipart/related; boundary="${h}"\r
9
+ \r
10
+ `,a+=`--${h}\r
11
+ `,a+=`Content-Type: multipart/alternative; boundary="${i}"\r
12
+ \r
13
+ `):(a+=`--${r}\r
14
+ `,a+=`Content-Type: multipart/alternative; boundary="${i}"\r
15
+ \r
16
+ `),this.text){a+=`--${i}\r
17
+ `,a+=`Content-Type: text/plain; charset="UTF-8"\r
18
+ `,a+=`Content-Transfer-Encoding: quoted-printable\r
19
+ \r
20
+ `;let l=x(this.text);a+=`${l}\r
21
+ \r
22
+ `}if(this.html){a+=`--${i}\r
23
+ `,a+=`Content-Type: text/html; charset="UTF-8"\r
24
+ `,a+=`Content-Transfer-Encoding: quoted-printable\r
25
+ \r
26
+ `;let l=x(this.html);a+=`${l}\r
27
+ \r
28
+ `}if(a+=`--${i}--\r
29
+ `,o.length){for(let l of o)a+=this.attachmentPart(h,l);a+=`--${h}--\r
30
+ `}for(let l of d)a+=this.attachmentPart(r,l);return a+=`--${r}--\r
31
+ `,a.endsWith(`\r
32
+ `)?a:`${a}\r
33
+ `}async resolveAttachments(){if(!this.attachments?.length)return;let e=[];for(let t of this.attachments){if(typeof t.content=="string"){e.push({...t,content:t.content});continue}if(this.isBlob(t.content)){let r=t.content;e.push({...t,content:new Uint8Array(await r.arrayBuffer()),resolvedContentType:r.type||void 0});continue}e.push({...t,content:this.attachmentBytes(t.content)})}return e}resolveAttachmentsSync(){if(this.attachments?.length)return this.attachments.map(e=>{if(typeof e.content=="string")return{...e,content:e.content};if(this.isBlob(e.content))throw new Error("Blob attachment content requires async message generation; use getMessageDataAsync(), getEmailDataAsync(), or send through a mailer");return{...e,content:this.attachmentBytes(e.content)}})}attachmentBytes(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}isBlob(e){return typeof Blob<"u"&&e instanceof Blob}attachmentPart(e,t){let r=t.mimeType||t.contentType||t.resolvedContentType||this.getMimeType(t.filename),i=this.attachmentDisposition(t),n=t.encoding||"base64",a=`--${e}\r
34
+ `;return a+=`Content-Type: ${r}; name="${t.filename}"\r
35
+ `,a+=`Content-Description: ${t.filename}\r
36
+ `,t.contentId&&(a+=`Content-ID: <${t.contentId.replace(/[<>]/g,"")}>\r
37
+ `),a+=`Content-Disposition: ${i}; filename="${t.filename}";\r
38
+ `,a+=` creation-date="${new Date().toUTCString()}";\r
39
+ `,a+=`Content-Transfer-Encoding: ${n}\r
40
+ \r
41
+ `,a+=`${this.encodedAttachmentContent(t,n)}\r
42
+ \r
43
+ `,a}attachmentDisposition(e){return e.disposition||(e.contentId?"inline":"attachment")}encodedAttachmentContent(e,t){if(t==="base64")return typeof e.content=="string"?this.wrapBase64(e.content):this.bytesToWrappedBase64(e.content);let r=this.attachmentTextContent(e);if(t==="quoted-printable")return x(r);if(/[^\x00-\x7F]/.test(r))throw new Error("7bit attachment content must contain ASCII only");return r.replace(/\r?\n/g,`\r
44
+ `)}attachmentTextContent(e){return typeof e.content=="string"?e.content:P(e.content)}bytesToWrappedBase64(e){let t="";for(let r=0;r<e.length;r+=57)t&&(t+=`\r
45
+ `),t+=this.bytesToBase64(e.subarray(r,r+57));return t}bytesToBase64(e){let t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}wrapBase64(e){let t="",r="";for(let i of e)/\s/.test(i)||(r+=i,r.length===76&&(t+=t?`\r
46
+ ${r}`:r,r=""));return r&&(t+=t?`\r
47
+ ${r}`:r),t}getEmailData(){return s.toSmtpData(this.getMessageData())}async getEmailDataAsync(){return s.toSmtpData(await this.getMessageDataAsync())}static toSmtpData(e){let t=s.applyDotStuffing(e);return t.endsWith(`\r
48
+ `)?`${t}.\r
49
+ `:`${t}\r
50
+ .\r
51
+ `}static applyDotStuffing(e){let t=e.replace(/\r\n\./g,`\r
52
+ ..`);return t.startsWith(".")&&(t=`.${t}`),t}generateSafeBoundary(e){let t=new Uint8Array(28);crypto.getRandomValues(t);let r=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join(""),i=e+r;return i=i.replace(/[<>@,;:\\/[\]?=" ]/g,"_"),i}getMimeType(e){let t=e.split(".").pop()?.toLowerCase();return{txt:"text/plain",html:"text/html",csv:"text/csv",pdf:"application/pdf",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",zip:"application/zip"}[t||"txt"]||"application/octet-stream"}resolveHeader(){this.resolveFrom(),this.resolveTo(),this.resolveReply(),this.resolveCC(),this.resolveSubject(),this.headers.Date=this.headers.Date??new Date().toUTCString(),this.headers["Message-ID"]=this.headers["Message-ID"]??`<${crypto.randomUUID()}@${this.from.email.split("@").pop()}>`}resolveFrom(){if(this.headers.From)return;let e=this.from.email;this.from.name&&(e=`"${A(this.from.name)}" <${e}>`),this.headers.From=e}resolveTo(){if(this.headers.To)return;let e=this.to.map(t=>t.name?`"${A(t.name)}" <${t.email}>`:t.email);this.headers.To=e.join(", ")}resolveSubject(){this.headers.Subject||this.subject&&(this.headers.Subject=A(this.subject))}resolveReply(){if(!this.headers["Reply-To"]&&this.reply){let e=this.reply.email;this.reply.name&&(e=`"${A(this.reply.name)}" <${e}>`),this.headers["Reply-To"]=e}}resolveCC(){if(!this.headers.CC&&this.cc){let e=this.cc.map(t=>t.name?`"${A(t.name)}" <${t.email}>`:t.email);this.headers.CC=e.join(", ")}}};var z=["from","to","subject","date","message-id","mime-version","content-type"];function U(...s){let e=s.reduce((i,n)=>i+n.length,0),t=new Uint8Array(e),r=0;for(let i of s)t.set(i,r),r+=i.length;return t}function Y(s){let e=atob(s),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function I(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}function $(s){return new Uint8Array(g(s))}function K(s){if(s<128)return new Uint8Array([s]);let e=[],t=s;for(;t>0;)e.unshift(t&255),t>>=8;return new Uint8Array([128|e.length,...e])}function R(s,e){return U(new Uint8Array([s]),K(e.length),e)}function V(s){let e=new Uint8Array([2,1,0]),t=new Uint8Array([6,9,42,134,72,134,247,13,1,1,1]),r=new Uint8Array([5,0]),i=R(48,U(t,r)),n=R(4,s);return R(48,U(e,i,n))}function Q(s){let e=s.match(/-----BEGIN (PRIVATE KEY|RSA PRIVATE KEY)-----([\s\S]+?)-----END \1-----/);if(!e)throw new Error("DKIM privateKey must be a PEM private key");let t=e[1],r=Y(e[2].replace(/\s+/g,""));if(t==="PRIVATE KEY")return r;if(t==="RSA PRIVATE KEY")return V(r);throw new Error(`Unsupported DKIM private key type: ${t}`)}function B(s){return s.replace(/\r?\n/g,`\r
53
+ `)}function _(s){let e=[];for(let t of B(s).split(`\r
54
+ `)){if(/^[ \t]/.test(t)&&e.length){let i=e[e.length-1];i.value+=`\r
55
+ ${t}`;continue}let r=t.indexOf(":");r<1||e.push({name:t.slice(0,r),value:t.slice(r+1)})}return e}function G(s){let e=s.name.trim().toLowerCase(),t=s.value.replace(/\r\n[ \t]+/g," ").replace(/[ \t]+/g," ").trim();return`${e}:${t}`}function Z(s){let e=B(s).split(`\r
56
+ `).map(t=>t.replace(/[ \t]+$/g,"").replace(/[ \t]+/g," "));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e.length?`${e.join(`\r
57
+ `)}\r
58
+ `:`\r
59
+ `}function X(s,e){let t=e&&e.length?e.map(n=>n.toLowerCase()):z,r=new Set,i=[];for(let n of t)for(let a=s.length-1;a>=0;a--)if(!r.has(a)&&s[a].name.trim().toLowerCase()===n){r.add(a),i.push(s[a]);break}return i}function O(s){if(s){if(!s.domainName?.trim())throw new Error("DKIM domainName is required");if(!s.keySelector?.trim())throw new Error("DKIM keySelector is required");if(!s.privateKey?.trim())throw new Error("DKIM privateKey is required");for(let e of[s.domainName,s.keySelector])if(/[\r\n;]/.test(e))throw new Error("DKIM domainName and keySelector must be header safe")}}async function k(s,e){O(e);let t=s.indexOf(`\r
60
+ \r
61
+ `);if(t<0)throw new Error("Unable to DKIM sign message without headers and body");let r=s.slice(0,t),i=s.slice(t+4),n=_(r),a=X(n,e.headerFieldNames);if(!a.some(m=>m.name.toLowerCase()==="from"))throw new Error("DKIM signing requires a From header");let o=Z(i),d=await crypto.subtle.digest("SHA-256",$(o)),h=a.map(m=>m.name.trim().toLowerCase()),l=`v=1; a=rsa-sha256; c=relaxed/relaxed; d=${e.domainName}; s=${e.keySelector}; h=${h.join(":")}; bh=${I(d)}; b=`,f=[...a.map(G),`dkim-signature:${l}`].join(`\r
62
+ `),p=await crypto.subtle.importKey("pkcs8",Q(e.privateKey),{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),T=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",p,$(f));return`DKIM-Signature: ${l}${I(T)}\r
63
+ ${s}`}var J=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.NONE=4]="NONE",n))(J||{}),C=class{constructor(e=1,t){this.level=e;this.prefix=t}level;prefix;debug(e,...t){this.level<=0&&console.debug(this.prefix+e,...t)}info(e,...t){this.level<=1&&console.info(this.prefix+e,...t)}warn(e,...t){this.level<=2&&console.warn(this.prefix+e,...t)}error(e,...t){this.level<=3&&console.error(this.prefix+e,...t)}};var ee=["plain","login","cram-md5"],c=class extends Error{stage;command;response;responseCode;enhancedStatusCode;transient;cause;constructor(e,t){super(e),this.name="SMTPError",this.stage=t.stage,this.command=t.command,this.response=t.response,this.cause=t.cause;let r=t.response?.match(/^(\d{3})/);this.responseCode=r?Number(r[1]):void 0,this.enhancedStatusCode=t.response?.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1),this.transient=this.responseCode?this.responseCode>=400&&this.responseCode<500:!1}},te=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],re=Array.from({length:64},(s,e)=>Math.floor(Math.abs(Math.sin(e+1))*4294967296)>>>0);function ie(s,e){return(s<<e|s>>>32-e)>>>0}function L(...s){let e=s.reduce((i,n)=>i+n.length,0),t=new Uint8Array(e),r=0;for(let i of s)t.set(i,r),r+=i.length;return t}function D(s){let e=BigInt(s.length)*8n,t=s.length+1;for(;t%64!==56;)t++;let r=new Uint8Array(t+8);r.set(s),r[s.length]=128;let i=new DataView(r.buffer);for(let p=0;p<8;p++)i.setUint8(t+p,Number(e>>BigInt(p*8)&0xffn));let n=1732584193,a=4023233417,o=2562383102,d=271733878,h=new Array(16);for(let p=0;p<r.length;p+=64){for(let u=0;u<16;u++)h[u]=i.getUint32(p+u*4,!0);let T=n,m=a,w=o,y=d;for(let u=0;u<64;u++){let v,S;u<16?(v=m&w|~m&y,S=u):u<32?(v=y&m|~y&w,S=(5*u+1)%16):u<48?(v=m^w^y,S=(3*u+5)%16):(v=w^(m|~y),S=7*u%16);let W=y;y=w,w=m,m=m+ie(T+v+re[u]+h[S]>>>0,te[u])>>>0,T=W}n=n+T>>>0,a=a+m>>>0,o=o+w>>>0,d=d+y>>>0}let l=new Uint8Array(16),f=new DataView(l.buffer);return f.setUint32(0,n,!0),f.setUint32(4,a,!0),f.setUint32(8,o,!0),f.setUint32(12,d,!0),l}function se(s){return Array.from(s).map(e=>e.toString(16).padStart(2,"0")).join("")}function ne(s,e){let r=s.length>64?D(s):s,i=new Uint8Array(64).fill(92),n=new Uint8Array(64).fill(54);return r.forEach((a,o)=>{i[o]^=a,n[o]^=a}),se(D(L(i,D(L(n,e)))))}function ae(s){let e=new Uint8Array(s.length);for(let t=0;t<s.length;t++)e[t]=s.charCodeAt(t)&255;return e}function oe(s){return ae(atob(s))}function F(s){let e="";for(let t of g(s))t>=33&&t<=126&&t!==43&&t!==61?e+=String.fromCharCode(t):e+=`+${t.toString(16).toUpperCase().padStart(2,"0")}`;return e}function ce(s){return s.includes(";")?s:`rfc822;${s}`}var N=class{constructor(e,t,r="SmtpMailer"){this.connector=t;this.port=e.port,this.host=e.host,this.secure=!!e.secure,Array.isArray(e.authType)?this.authType=e.authType:typeof e.authType=="string"?this.authType=[e.authType]:this.authType=ee,this.startTls=e.startTls===void 0?!0:e.startTls,this.credentials=e.credentials,this.pipelining=e.pipelining===!1?!1:"auto",this.dsn=e.dsn||{},this.dkim=e.dkim,O(this.dkim),this.socketTimeoutMs=e.socketTimeoutMs||6e4,this.responseTimeoutMs=e.responseTimeoutMs||3e4,this.logger=new C(e.logLevel,`[${r}:${this.host}:${this.port}]`)}connector;host;port;secure;startTls;authType;credentials;pipelining;socketTimeoutMs;responseTimeoutMs;socket;reader;writer;responseBuffer="";logger;dsn;dkim;active=!1;closeError;sendChain=Promise.resolve();emailSending=null;queuedSendRejects=new Set;supportsDSN=!1;supportsSize=!1;maxMessageSize;supports8BitMime=!1;supportsSmtpUtf8=!1;supportsRequireTls=!1;allowAuth=!1;authTypeSupported=[];supportsStartTls=!1;supportsPipelining=!1;send(e){let t=new M(e);t.sent.catch(()=>{});let r,i=!1,n=new Promise((a,o)=>{r=h=>{i=!0,o(h)},this.queuedSendRejects.add(r),this.sendChain.then(async()=>{if(this.queuedSendRejects.delete(r),i)throw this.closedSendError();if(!this.active)throw this.closedSendError();return await this.sendEmail(t)}).then(a,o)});return this.sendChain=n.catch(()=>{}),n}async sendMany(e,t={}){let r=[];for(let i of e)try{let n=await this.send(i);r.push({status:"fulfilled",value:n})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(e){let t=e||new c("EdgeMailer is shutting down",{stage:"quit"});this.active=!1,this.closeError=t,this.logger.info("EdgeMailer is closed",e?.message||"");for(let r of this.queuedSendRejects)r(this.closedSendError());if(this.queuedSendRejects.clear(),!e)try{await this.writeLine("QUIT"),await this.readTimeout("quit","QUIT")}catch{}await this.closeSocket()}isActive(){return this.active}async initializeSmtpSession(){await this.openSocket(),await this.waitForSocketConnected(),await this.greet(),await this.ehlo(),this.startTls&&!this.secure&&this.supportsStartTls&&(await this.tls(),await this.ehlo()),await this.auth(),this.active=!0}async sendEmail(e){this.emailSending=e;let t=await this.prepareEmail(e),r={accepted:[],rejected:[]};try{this.canPipeline()?r=await this.envelopePipelined(t):(await this.mail(t),r=await this.rcpt(t),await this.data());let i=await this.body(t);return e.setSent(),this.createReceipt(t,r,i)}catch(i){let n=i instanceof Error?i:new c("Failed to send email",{stage:"send",cause:i});if(this.logger.error("Failed to send email: "+n.message),e.setSentError(n),this.active)try{await this.rset()}catch(a){await this.close(a instanceof Error?a:new c("Failed to reset after send error",{stage:"rset",cause:a}))}throw n}finally{this.emailSending=null}}canPipeline(){return this.pipelining==="auto"&&this.supportsPipelining}async prepareEmail(e){let t=await e.getMessageDataAsync(),r=this.dkim?await k(t,this.dkim):t,i=M.toSmtpData(r);return{email:e,data:i,size:Math.max(0,g(i).length-g(`.\r
64
+ `).length)}}async readTimeout(e,t){let r,i=!1,n=new c("Timeout while waiting for smtp server response",{stage:e,command:t}),a=this.read(e,t);a.catch(()=>{});try{return await Promise.race([a,new Promise((o,d)=>{r=setTimeout(()=>{i=!0,this.abortConnection(n),d(n)},this.responseTimeoutMs)})])}finally{r&&clearTimeout(r),i&&(this.closeError=n)}}async read(e,t){let r=this.shiftResponse();if(r)return r;for(;;){let{value:i,done:n}=await this.reader.read();if(n)throw new c("SMTP server closed the connection",{stage:e,command:t,response:this.responseBuffer});if(!i?.length)continue;let a=P(i).toString();this.logger.debug(`SMTP server response:
65
+ `+a),this.responseBuffer=this.responseBuffer+a;let o=this.shiftResponse();if(o)return o}}shiftResponse(){let e=0,t=/\r?\n/g,r;for(;r=t.exec(this.responseBuffer);){let i=this.responseBuffer.slice(e,r.index),n=r.index+r[0].length;if(/^\d{3}(?:\s|$)/.test(i)){let a=this.responseBuffer.slice(0,n);return this.responseBuffer=this.responseBuffer.slice(n),a}e=n}}async writeLine(e){await this.write(`${e}\r
66
+ `)}async write(e){this.logger.debug(`Write to socket:
67
+ `+e),await this.writer.write(g(e))}async openSocket(){this.logger.info("Connecting to SMTP server");let e=new c("Socket timeout!",{stage:"connect"}),t=new AbortController,r,i=!1;try{let n=await Promise.race([Promise.resolve(this.connector.connect({hostname:this.host,port:this.port,tls:this.secure?"on":this.startTls?"starttls":"off",signal:t.signal})),new Promise((a,o)=>{r=setTimeout(()=>{i=!0,t.abort(e),o(e)},this.socketTimeoutMs)})]);this.socket=n,this.reader=n.readable.getReader(),this.writer=n.writable.getWriter()}finally{r&&clearTimeout(r),i&&(this.closeError=e)}}async waitForSocketConnected(){let e=this.getSocket();if(!e.opened){this.logger.info("SMTP server connected");return}let t=new c("Socket timeout!",{stage:"connect"}),r;try{await Promise.race([e.opened,new Promise((i,n)=>{r=setTimeout(()=>{this.abortConnection(t),n(t)},this.socketTimeoutMs)})])}finally{r&&clearTimeout(r)}this.logger.info("SMTP server connected")}async greet(){let e=await this.readTimeout("greet");if(!e.startsWith("220"))throw new c("Failed to connect to SMTP server: "+e,{stage:"greet",response:e})}async ehlo(){let e="EHLO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("ehlo",e);if(t.startsWith("421"))throw new c(`Failed to EHLO. ${t}`,{stage:"ehlo",command:e,response:t});if(!t.startsWith("2")){await this.helo();return}this.parseCapabilities(t)}async helo(){let e="HELO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("helo",e);if(!t.startsWith("2"))throw new c(`Failed to HELO. ${t}`,{stage:"helo",command:e,response:t})}async tls(){let e="STARTTLS";await this.writeLine(e);let t=await this.readTimeout("starttls",e);if(!t.startsWith("220"))throw new c("Failed to start TLS: "+t,{stage:"starttls",command:e,response:t});this.reader.releaseLock(),this.writer.releaseLock();let r=this.getSocket();if(!r.startTls)throw new c("Runtime socket does not support STARTTLS",{stage:"starttls",command:e,response:t});this.socket=await r.startTls(),this.reader=this.socket.readable.getReader(),this.writer=this.socket.writable.getWriter(),this.resetCapabilities()}resetCapabilities(){this.supportsDSN=!1,this.supportsSize=!1,this.maxMessageSize=void 0,this.supports8BitMime=!1,this.supportsSmtpUtf8=!1,this.supportsRequireTls=!1,this.allowAuth=!1,this.authTypeSupported=[],this.supportsStartTls=!1,this.supportsPipelining=!1}parseCapabilities(e){this.resetCapabilities();for(let t of e.split(/\r?\n/)){let r=t.match(/^250[ -]([A-Z0-9][A-Z0-9-]*)(?:[ =](.*))?$/i);if(!r)continue;let i=r[1].toUpperCase(),n=r[2]?.trim()||"";if(i==="AUTH"){this.allowAuth=!0;for(let a of n.split(/\s+/)){let o=a.toLowerCase();(o==="plain"||o==="login"||o==="cram-md5")&&this.authTypeSupported.push(o)}}else i==="STARTTLS"?this.supportsStartTls=!0:i==="DSN"?this.supportsDSN=!0:i==="PIPELINING"?this.supportsPipelining=!0:i==="SIZE"?(this.supportsSize=!0,this.maxMessageSize=n?Number(n):void 0):i==="8BITMIME"?this.supports8BitMime=!0:i==="SMTPUTF8"?this.supportsSmtpUtf8=!0:i==="REQUIRETLS"&&(this.supportsRequireTls=!0)}}async auth(){if(this.allowAuth&&this.credentials)if(this.authTypeSupported.includes("plain")&&this.authType.includes("plain"))await this.authWithPlain();else if(this.authTypeSupported.includes("login")&&this.authType.includes("login"))await this.authWithLogin();else if(this.authTypeSupported.includes("cram-md5")&&this.authType.includes("cram-md5"))await this.authWithCramMD5();else throw new c("No supported auth method found.",{stage:"auth"})}async authWithPlain(){let e=`AUTH PLAIN ${btoa(`\0${this.credentials.username}\0${this.credentials.password}`)}`;await this.writeLine(e);let t=await this.readTimeout("auth","AUTH PLAIN");if(!t.startsWith("2"))throw new c(`Failed to plain authentication: ${t}`,{stage:"auth",command:"AUTH PLAIN",response:t})}async authWithLogin(){await this.writeLine("AUTH LOGIN");let e=await this.readTimeout("auth","AUTH LOGIN");if(!e.startsWith("3"))throw new c("Invalid login: "+e,{stage:"auth",command:"AUTH LOGIN",response:e});let t=btoa(this.credentials.username);await this.writeLine(t);let r=await this.readTimeout("auth","AUTH LOGIN username");if(!r.startsWith("3"))throw new c("Failed to login authentication: "+r,{stage:"auth",command:"AUTH LOGIN username",response:r});let i=btoa(this.credentials.password);await this.writeLine(i);let n=await this.readTimeout("auth","AUTH LOGIN password");if(!n.startsWith("2"))throw new c("Failed to login authentication: "+n,{stage:"auth",command:"AUTH LOGIN password",response:n})}async authWithCramMD5(){let e="AUTH CRAM-MD5";await this.writeLine(e);let t=await this.readTimeout("auth",e),r=t.match(/^334\s+([^\r\n]+)/)?.pop();if(!r)throw new c("Invalid CRAM-MD5 challenge: "+t,{stage:"auth",command:e,response:t});let i;try{i=oe(r)}catch(o){throw new c("Invalid CRAM-MD5 challenge encoding",{stage:"auth",command:e,response:t,cause:o})}let n=ne(g(this.credentials.password),i);await this.writeLine(btoa(`${this.credentials.username} ${n}`));let a=await this.readTimeout("auth",e);if(!a.startsWith("2"))throw new c("Failed to cram-md5 authentication: "+a,{stage:"auth",command:e,response:a})}async mail(e){let t=this.mailCommand(e);await this.writeLine(t);let r=await this.readTimeout("mail",t);if(!r.startsWith("2"))throw new c(`Invalid ${t} ${r}`,{stage:"mail",command:t,response:r})}async rcpt(e){let t={accepted:[],rejected:[]},r=this.recipients(e.email);for(let i of r){let n=this.rcptCommand(i,e.email);await this.writeLine(n);let a=await this.readTimeout("rcpt",n);if(!a.startsWith("2"))throw t.rejected.push(this.rejectedRecipient(i,a)),new c(`Invalid ${n} ${a}`,{stage:"rcpt",command:n,response:a});t.accepted.push(i)}return t}async data(){let e="DATA";await this.writeLine(e);let t=await this.readTimeout("data",e);if(!t.startsWith("3"))throw new c(`Failed to send DATA: ${t}`,{stage:"data",command:e,response:t})}async envelopePipelined(e){let t={accepted:[],rejected:[]},r=this.mailCommand(e),i=this.recipients(e.email),n=i.map(l=>this.rcptCommand(l,e.email)),a="DATA";await this.writeLine(r);for(let l of n)await this.writeLine(l);await this.writeLine(a);let o,d=await this.readTimeout("mail",r);d.startsWith("2")||(o=new c(`Invalid ${r} ${d}`,{stage:"mail",command:r,response:d}));for(let[l,f]of n.entries()){let p=await this.readTimeout("rcpt",f);p.startsWith("2")?p.startsWith("2")&&t.accepted.push(i[l]):(t.rejected.push(this.rejectedRecipient(i[l],p)),o||(o=new c(`Invalid ${f} ${p}`,{stage:"rcpt",command:f,response:p})))}let h=await this.readTimeout("data",a);if(!h.startsWith("3")&&!o&&(o=new c(`Failed to send DATA: ${h}`,{stage:"data",command:a,response:h})),o)throw o;return t}async body(e){await this.write(e.data);let t=await this.readTimeout("body","<message body>");if(!t.startsWith("2"))throw new c("Failed send email body: "+t,{stage:"body",command:"<message body>",response:t});return t}createReceipt(e,t,r){return{messageId:e.email.headers["Message-ID"]||"",envelope:{from:this.mailFrom(e.email),to:this.recipients(e.email)},accepted:t.accepted,rejected:t.rejected,response:r,responseCode:this.responseCode(r),enhancedStatusCode:this.enhancedStatusCode(r),size:e.size}}rejectedRecipient(e,t){let r=this.responseCode(t);return{recipient:e,response:t,responseCode:r,enhancedStatusCode:this.enhancedStatusCode(t),transient:r?r>=400&&r<500:!1}}responseCode(e){let t=e.match(/^(\d{3})/);return t?Number(t[1]):void 0}enhancedStatusCode(e){return e.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1)}async rset(){let e="RSET";await this.writeLine(e);let t=await this.readTimeout("rset",e);if(!t.startsWith("2"))throw new c(`Failed to reset: ${t}`,{stage:"rset",command:e,response:t})}mailCommand(e){let t=e.email,r=[`MAIL FROM: <${this.mailFrom(t)}>`],i=this.mailParameters(e);return i.length&&r.push(i.join(" ")),r.join(" ")}rcptCommand(e,t){let r=`RCPT TO: <${e}>`,i=this.rcptParameters(t);return i.length&&(r+=` ${i.join(" ")}`),r}recipients(e){return e.envelope?.to||[...e.to.map(t=>t.email),...(e.cc||[]).map(t=>t.email),...(e.bcc||[]).map(t=>t.email)]}mailFrom(e){return e.envelope?.from||e.from.email}mailParameters(e){let t=e.email,r=[],i=t.envelope;this.supportsSize&&r.push(`SIZE=${i?.size??e.size}`);let n=i?.body?.toUpperCase();if(n){if(!this.supports8BitMime)throw new c(`${n} requires 8BITMIME support`,{stage:"mail",command:"MAIL FROM"});r.push(`BODY=${n}`)}if(this.needsSmtpUtf8(t)){if(!this.supportsSmtpUtf8)throw new c("SMTPUTF8 is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("SMTPUTF8")}if(i?.requireTls){if(!this.supportsRequireTls)throw new c("REQUIRETLS is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("REQUIRETLS")}if(this.supportsDSN&&this.hasDsnRequest(t)){let a=this.retParameter(t);a&&r.push(a);let o=t.dsnOverride?.envelopeId||this.dsn?.envelopeId;o&&r.push(`ENVID=${F(o)}`)}return r}rcptParameters(e){if(!this.supportsDSN||!this.hasDsnRequest(e))return[];let t=[this.notificationParameter(e)],r=e.dsnOverride?.ORCPT||this.dsn?.ORCPT;return r&&t.push(`ORCPT=${F(ce(r))}`),t}notificationParameter(e){if(e.dsnOverride?.NOTIFY?.NEVER||this.dsn?.NOTIFY?.NEVER)return"NOTIFY=NEVER";let t=[];return(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.SUCCESS||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.SUCCESS)&&t.push("SUCCESS"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.FAILURE||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.FAILURE)&&t.push("FAILURE"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.DELAY||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.DELAY)&&t.push("DELAY"),t.length>0?`NOTIFY=${t.join(",")}`:"NOTIFY=NEVER"}retParameter(e){if(e.dsnOverride?.RET&&e.dsnOverride.RET.FULL||!e.dsnOverride?.RET&&this.dsn?.RET?.FULL)return"RET=FULL";if(e.dsnOverride?.RET&&e.dsnOverride.RET.HEADERS||!e.dsnOverride?.RET&&this.dsn?.RET?.HEADERS)return"RET=HDRS"}hasDsnRequest(e){return!!(e.dsnOverride?.envelopeId||e.dsnOverride?.RET||e.dsnOverride?.NOTIFY||e.dsnOverride?.ORCPT||this.dsn?.envelopeId||this.dsn?.RET||this.dsn?.NOTIFY||this.dsn?.ORCPT)}needsSmtpUtf8(e){return!!e.envelope?.smtpUtf8||[this.mailFrom(e),...this.recipients(e)].some(t=>/[^\x00-\x7F]/.test(t))}async abortConnection(e){this.active=!1,this.closeError=e instanceof Error?e:new c("SMTP connection aborted",{stage:"send",cause:e});for(let t of this.queuedSendRejects)t(this.closedSendError());this.queuedSendRejects.clear(),await this.closeSocket()}closedSendError(){return new c(this.closeError?.message||"EdgeMailer is closed",{stage:"send",cause:this.closeError})}async closeSocket(){let e=this.socket;if(e)try{await e.close()}catch{this.logger.error("Failed to close socket")}}getSocket(){if(!this.socket)throw new c("SMTP socket is not open",{stage:"connect"});return this.socket}};var j=class{constructor(e,t){this.options=e;this.connectMailer=t;let r=le(e.pool);this.maxConnections=Math.max(1,r.maxConnections??1),this.maxMessagesPerConnection=Math.max(1,r.maxMessagesPerConnection??Number.MAX_SAFE_INTEGER),this.idleTimeoutMs=Math.max(0,r.idleTimeoutMs??6e4)}options;connectMailer;maxConnections;maxMessagesPerConnection;idleTimeoutMs;ready=[];busy=new Set;waitQueue=[];pendingCreates=new Set;pendingDestroys=new Set;totalConnections=0;closed=!1;async send(e){let t=await this.acquire();try{let r=await t.mailer.send(e);return t.messages++,await this.release(t),r}catch(r){throw t.messages++,await this.release(t),r}}async sendMany(e,t={}){let r=[];for(let i of e)try{r.push({status:"fulfilled",value:await this.send(i)})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(){this.closed=!0;for(let t of this.waitQueue)t.reject(new Error("SMTP connection pool is closed"));this.waitQueue=[];let e=[...this.ready,...this.busy];this.ready=[],this.busy.clear();for(let t of e)this.trackDestroy(t,!1);await this.waitForDrained()}async acquire(){if(this.closed)throw new Error("SMTP connection pool is closed");let e=this.ready.shift();return e?(this.clearIdleTimer(e),this.busy.add(e),e):this.totalConnections<this.maxConnections?await this.createBusyClient():await new Promise((t,r)=>{this.waitQueue.push({resolve:t,reject:r})})}async release(e){if(e.destroyPromise){await this.trackDestroy(e);return}if(this.busy.delete(e),this.closed||!e.mailer.isActive()||e.messages>=this.maxMessagesPerConnection){await this.trackDestroy(e);return}let t=this.waitQueue.shift();if(t){this.busy.add(e),t.resolve(e);return}this.ready.push(e),this.idleTimeoutMs>0&&(e.idleTimer=setTimeout(()=>{this.ready=this.ready.filter(r=>r!==e),this.trackDestroy(e)},this.idleTimeoutMs))}dispatchWaiters(){if(!this.closed){for(;this.waitQueue.length&&this.ready.length;){let e=this.waitQueue.shift(),t=this.ready.shift();this.clearIdleTimer(t),this.busy.add(t),e.resolve(t)}for(;this.waitQueue.length&&this.totalConnections<this.maxConnections;){let e=this.waitQueue.shift();this.createBusyClient().then(e.resolve,e.reject)}}}createBusyClient(){let e=this.createBusyClientInner(),t=e.then(()=>{},()=>{});return this.pendingCreates.add(t),t.finally(()=>{this.pendingCreates.delete(t)}),e}async createBusyClientInner(){this.totalConnections++;let e=!1;try{let r={mailer:await this.connectMailer(this.options),messages:0};if(this.closed)throw e=!0,await this.trackDestroy(r,!1),new Error("SMTP connection pool is closed");return this.busy.add(r),r}catch(t){throw e||(this.totalConnections--,this.dispatchWaiters()),t}}trackDestroy(e,t=!0){let r=this.destroy(e);return this.pendingDestroys.add(r),r.finally(()=>{this.pendingDestroys.delete(r),t&&this.dispatchWaiters()}),r}destroy(e){return e.destroyPromise||(e.destroyPromise=this.destroyOnce(e)),e.destroyPromise}async destroyOnce(e){this.clearIdleTimer(e),this.ready=this.ready.filter(t=>t!==e),this.busy.delete(e);try{await e.mailer.close()}catch{}this.totalConnections=Math.max(0,this.totalConnections-1)}async waitForDrained(){for(;this.pendingCreates.size||this.pendingDestroys.size;)await Promise.all([...this.pendingCreates,...this.pendingDestroys])}clearIdleTimer(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0)}};function le(s){return s&&typeof s=="object"?s:{}}export{A as a,M as b,O as c,k as d,J as e,c as f,N as g,j as h};
@@ -0,0 +1,2 @@
1
+ export { A as AttachmentDisposition, a as AttachmentEncoding, b as AuthType, B as BatchSendOptions, c as BatchSendResult, C as Credentials, D as DkimConfig, d as DsnOptions, E as EdgeMailerOptions, f as Email, g as EmailAttachment, h as EmailAttachmentContent, i as EmailOptions, L as LogLevel, M as MailBodyType, j as MailEnvelopeOptions, P as PipeliningMode, S as SMTPError, k as SMTPErrorOptions, l as SMTPStage, m as SmtpBodyType, n as SmtpConnectionPool, o as SmtpMailer, p as SmtpPoolOptions, q as SmtpRejectedRecipient, r as SmtpSendReceipt, U as User, s as encodeHeader, t as signDkimMessage, v as validateDkimConfig } from './pool-CcxclJQJ.mjs';
2
+ export { EdgeMailer, cloudflareSocketConnector } from './index.mjs';
@@ -0,0 +1,2 @@
1
+ export { A as AttachmentDisposition, a as AttachmentEncoding, b as AuthType, B as BatchSendOptions, c as BatchSendResult, C as Credentials, D as DkimConfig, d as DsnOptions, E as EdgeMailerOptions, f as Email, g as EmailAttachment, h as EmailAttachmentContent, i as EmailOptions, L as LogLevel, M as MailBodyType, j as MailEnvelopeOptions, P as PipeliningMode, S as SMTPError, k as SMTPErrorOptions, l as SMTPStage, m as SmtpBodyType, n as SmtpConnectionPool, o as SmtpMailer, p as SmtpPoolOptions, q as SmtpRejectedRecipient, r as SmtpSendReceipt, U as User, s as encodeHeader, t as signDkimMessage, v as validateDkimConfig } from './pool-CcxclJQJ.js';
2
+ export { EdgeMailer, cloudflareSocketConnector } from './index.js';