@bumail/smtp 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +30 -2
  2. package/dist/client/connect.d.ts +18 -0
  3. package/dist/client/connect.d.ts.map +1 -0
  4. package/dist/client/inbox.d.ts +2 -0
  5. package/dist/client/inbox.d.ts.map +1 -1
  6. package/dist/client/index.d.ts +2 -0
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +594 -299
  9. package/dist/client/index.js.map +19 -11
  10. package/dist/client/options.d.ts +2 -0
  11. package/dist/client/options.d.ts.map +1 -1
  12. package/dist/client/pool/idle.d.ts +10 -0
  13. package/dist/client/pool/idle.d.ts.map +1 -0
  14. package/dist/client/pool/index.d.ts +18 -0
  15. package/dist/client/pool/index.d.ts.map +1 -0
  16. package/dist/client/pool/options.d.ts +26 -0
  17. package/dist/client/pool/options.d.ts.map +1 -0
  18. package/dist/client/pool/reset.d.ts +9 -0
  19. package/dist/client/pool/reset.d.ts.map +1 -0
  20. package/dist/client/pool/send.d.ts +8 -0
  21. package/dist/client/pool/send.d.ts.map +1 -0
  22. package/dist/client/pool/state.d.ts +28 -0
  23. package/dist/client/pool/state.d.ts.map +1 -0
  24. package/dist/client/send.d.ts +10 -1
  25. package/dist/client/send.d.ts.map +1 -1
  26. package/dist/client/session.d.ts +2 -2
  27. package/dist/client/session.d.ts.map +1 -1
  28. package/dist/client/settings.d.ts +11 -5
  29. package/dist/client/settings.d.ts.map +1 -1
  30. package/dist/client/socket-events.d.ts +15 -0
  31. package/dist/client/socket-events.d.ts.map +1 -0
  32. package/dist/client/socket.d.ts +12 -2
  33. package/dist/client/socket.d.ts.map +1 -1
  34. package/dist/client/transaction.d.ts +9 -3
  35. package/dist/client/transaction.d.ts.map +1 -1
  36. package/docs/README.md +3 -3
  37. package/docs/guide.md +25 -3
  38. package/docs/roadmap.md +10 -4
  39. package/docs/troubleshooting.md +84 -0
  40. package/package.json +2 -2
@@ -225,6 +225,20 @@ function contentOf(message, normalize, size) {
225
225
  };
226
226
  }
227
227
 
228
+ // src/protocol/ehlo.ts
229
+ function parseEhlo(lines) {
230
+ const extensions = new Map;
231
+ for (const line of lines.slice(1)) {
232
+ const text = line.trim();
233
+ const space = text.indexOf(" ");
234
+ const keyword = (space < 0 ? text : text.slice(0, space)).toUpperCase();
235
+ if (keyword === "")
236
+ continue;
237
+ extensions.set(keyword, space < 0 ? "" : text.slice(space + 1).trim());
238
+ }
239
+ return extensions;
240
+ }
241
+
228
242
  // src/protocol/reply-reader.ts
229
243
  var MAX_REPLY_LINES = 100;
230
244
  var isDigit = (c) => c >= 48 && c <= 57;
@@ -316,20 +330,6 @@ function afterDot(error, taken) {
316
330
  });
317
331
  }
318
332
 
319
- // src/protocol/ehlo.ts
320
- function parseEhlo(lines) {
321
- const extensions = new Map;
322
- for (const line of lines.slice(1)) {
323
- const text = line.trim();
324
- const space = text.indexOf(" ");
325
- const keyword = (space < 0 ? text : text.slice(0, space)).toUpperCase();
326
- if (keyword === "")
327
- continue;
328
- extensions.set(keyword, space < 0 ? "" : text.slice(space + 1).trim());
329
- }
330
- return extensions;
331
- }
332
-
333
333
  // src/client/session.ts
334
334
  async function hello(socket, settings, allowHelo) {
335
335
  const seconds = settings.timeouts.command;
@@ -408,206 +408,6 @@ async function open(socket, settings, tls) {
408
408
  return { extensions, authenticated: settings.auth !== undefined };
409
409
  }
410
410
 
411
- // src/client/settings.ts
412
- import { hostname as machineName } from "node:os";
413
-
414
- // src/client/dsn.ts
415
- var EVENTS = ["SUCCESS", "FAILURE", "DELAY"];
416
- function dsnOf(given, to, fail) {
417
- const { ret, envid, recipients } = given;
418
- if (ret !== undefined && ret !== "FULL" && ret !== "HDRS")
419
- throw fail(`ret must be 'FULL' or 'HDRS', not ${String(ret)}`);
420
- if (envid !== undefined && (typeof envid !== "string" || !/^[ -~]{1,100}$/.test(envid) || encodeXtext(envid).length > 100))
421
- throw fail("envid must be 1 to 100 printable ASCII characters, at most 100 once encoded as xtext");
422
- const byAddress = new Map;
423
- if (recipients !== undefined) {
424
- if (!Array.isArray(recipients))
425
- throw fail("recipients must be an array of { address, notify, orcpt }");
426
- for (const entry of recipients) {
427
- const address = entry?.address;
428
- if (typeof address !== "string" || !to.includes(address))
429
- throw fail(`recipients names ${JSON.stringify(address)}, which is not in to`);
430
- if (byAddress.has(address))
431
- throw fail(`recipients names ${address} twice`);
432
- checkRecipient(entry, fail);
433
- byAddress.set(address, entry);
434
- }
435
- }
436
- return {
437
- ...ret ? { ret } : {},
438
- ...envid === undefined ? {} : { envid },
439
- recipients: byAddress
440
- };
441
- }
442
- function checkRecipient({ address, notify, orcpt }, fail) {
443
- if (notify !== undefined && notify !== "NEVER") {
444
- const events = Array.isArray(notify) ? notify : [];
445
- if (events.length === 0 || events.some((e, i) => !EVENTS.includes(e) || events.indexOf(e) !== i))
446
- throw fail(`notify of ${address} must be 'NEVER' or a list of 'SUCCESS', 'FAILURE', 'DELAY', each once`);
447
- }
448
- if (orcpt !== undefined) {
449
- if (typeof orcpt !== "object" || orcpt === null || typeof orcpt.type !== "string" || !/^[A-Za-z0-9-]+$/.test(orcpt.type))
450
- throw fail(`orcpt of ${address} must be { type, address }, the type a token`);
451
- if (typeof orcpt.address !== "string" || orcpt.address === "")
452
- throw fail(`orcpt.address of ${address} must be a non-empty string`);
453
- const encoded = orcpt.type.toLowerCase() === "utf-8" ? utf8OrcptLength(orcpt.address) : encodeXtext(orcpt.address).length;
454
- if (orcpt.type.length + 1 + encoded > 500)
455
- throw fail(`orcpt of ${address} is over 500 characters once encoded`);
456
- }
457
- }
458
- function mailDsn(dsn, offered) {
459
- if (!offered)
460
- return "";
461
- let out = "";
462
- if (dsn.ret)
463
- out += ` RET=${dsn.ret}`;
464
- if (dsn.envid !== undefined)
465
- out += ` ENVID=${encodeXtext(dsn.envid)}`;
466
- return out;
467
- }
468
- function orcptParameter({ type, address }, smtputf8) {
469
- const kind = type.toLowerCase();
470
- if (kind === "rfc822")
471
- return /^[\x20-\x7e]+$/.test(address) ? ` ORCPT=${type};${encodeXtext(address)}` : "";
472
- if (kind !== "utf-8") {
473
- const value = `${type};${encodeXtext(address)}`;
474
- return address.isWellFormed() ? ` ORCPT=${value}` : "";
475
- }
476
- const encoded = encodeUtf8Orcpt(address, smtputf8);
477
- if (encoded === undefined)
478
- return "";
479
- const value = `${type};${encoded}`;
480
- return new TextEncoder().encode(value).length > 500 ? "" : ` ORCPT=${value}`;
481
- }
482
- function rcptDsn(dsn, address, offered, smtputf8 = false) {
483
- const entry = dsn.recipients.get(address);
484
- if (!offered || !entry)
485
- return "";
486
- let out = "";
487
- const { notify, orcpt } = entry;
488
- if (notify !== undefined)
489
- out += ` NOTIFY=${notify === "NEVER" ? "NEVER" : notify.join(",")}`;
490
- if (orcpt)
491
- out += orcptParameter(orcpt, smtputf8);
492
- return out;
493
- }
494
-
495
- // src/client/settings.ts
496
- var MAX_SECONDS = Math.floor(2147483647 / 1000);
497
- var TIMEOUTS = {
498
- connect: 30,
499
- greeting: 300,
500
- command: 300,
501
- mail: 300,
502
- rcpt: 300,
503
- dataStart: 120,
504
- dataBlock: 180,
505
- dataEnd: 600
506
- };
507
- var invalid = (message) => new SmtpError2("INVALID_OPTION", `sendMail(): ${message}`);
508
- function seconds(name, value, fallback) {
509
- if (value === undefined)
510
- return fallback;
511
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > MAX_SECONDS) {
512
- throw invalid(`${name} must be a number of seconds above 0 and at most ${MAX_SECONDS}, not ${String(value)}`);
513
- }
514
- return value;
515
- }
516
- function timeoutsOf(given = {}) {
517
- const out = {};
518
- for (const [name, fallback] of Object.entries(TIMEOUTS)) {
519
- out[name] = seconds(`timeouts.${name}`, given[name], fallback);
520
- }
521
- return out;
522
- }
523
- function address(value, allowNull) {
524
- if (allowNull && value === "")
525
- return "";
526
- if (isMailbox(value))
527
- return value;
528
- const text = typeof value === "string" ? value : "";
529
- const routed = typeof value === "string" && parsePath2(`<${text}>`, false);
530
- if (routed) {
531
- throw invalid(`${JSON.stringify(value)} holds a source route (@host:), which RFC 5321 says a client should not send: pass ${JSON.stringify(`${routed.local}@${text.slice(text.lastIndexOf("@") + 1)}`)} alone`);
532
- }
533
- throw invalid(`${JSON.stringify(value)} is not an address (local@domain${allowNull ? ", or '' for a bounce" : ""})`);
534
- }
535
- function heloOf(value, byMx) {
536
- if (value !== undefined) {
537
- if (!isHelloName(value))
538
- throw invalid(`helo "${value}" is not a host name`);
539
- return value;
540
- }
541
- if (byMx)
542
- throw invalid("helo is required for delivery by MX: pass your server's public name, such as helo: 'mail.example.com'");
543
- const name = machineName();
544
- return isHelloName(name) ? name : "localhost";
545
- }
546
- function authOf(auth) {
547
- if (auth === undefined)
548
- return;
549
- const { username, password, mechanism } = auth;
550
- if (typeof username !== "string" || username === "")
551
- throw invalid("auth.username must be a non-empty string");
552
- if (typeof password !== "string" || password === "")
553
- throw invalid("auth.password must be a non-empty string");
554
- if (mechanism !== undefined && mechanism !== "PLAIN" && mechanism !== "LOGIN")
555
- throw invalid(`auth.mechanism must be 'PLAIN' or 'LOGIN', not ${mechanism}`);
556
- return auth;
557
- }
558
- function tlsOf(options, plaintextAuth) {
559
- const secure = options.secure === true;
560
- const tls = options.tls ?? (secure || options.auth && !plaintextAuth ? "required" : "opportunistic");
561
- if (tls !== "opportunistic" && tls !== "required" && tls !== "none")
562
- throw invalid(`tls must be 'opportunistic', 'required' or 'none', not ${tls}`);
563
- if (tls === "none" && secure)
564
- throw invalid("secure is TLS from the first byte: it cannot go with tls: 'none'");
565
- if (tls === "none" && options.auth && !plaintextAuth)
566
- throw invalid("auth with tls: 'none' would send the password in clear; pass allowPlaintextAuth: true for a local test server");
567
- if (tls === "opportunistic" && options.auth && !plaintextAuth)
568
- throw invalid("auth with tls: 'opportunistic' would send the password to a server whose certificate is not checked; leave tls out to check it, or pass allowPlaintextAuth: true for a local test server");
569
- return tls;
570
- }
571
- function settingsOf(options) {
572
- if (typeof options !== "object" || options === null)
573
- throw invalid("options must be an object");
574
- const from = address(options.from, true);
575
- const list = typeof options.to === "string" ? [options.to] : options.to;
576
- if (!Array.isArray(list) || list.length === 0)
577
- throw invalid("to must name one recipient or more");
578
- const to = list.map((recipient) => address(recipient, false));
579
- const allowPlaintextAuth = options.allowPlaintextAuth === true;
580
- const { size } = options;
581
- if (size !== undefined && (!Number.isSafeInteger(size) || size < 0))
582
- throw invalid(`size must be a number of bytes, not ${size}`);
583
- const ca = options.ca === undefined ? undefined : [options.ca].flat();
584
- const auth = authOf(options.auth);
585
- return {
586
- from,
587
- to,
588
- helo: heloOf(options.helo, byMx(options)),
589
- tls: tlsOf(options, allowPlaintextAuth),
590
- secure: options.secure === true,
591
- ...ca ? { ca } : {},
592
- ...auth ? { auth } : {},
593
- allowPlaintextAuth,
594
- ...size === undefined ? {} : { size },
595
- dsn: dsnOf(options, to, invalid),
596
- smtputf8: options.smtputf8 === true || [from, ...to].some((text) => /[^ -~]/.test(text)),
597
- normalizeLineEnds: options.normalizeLineEnds === true,
598
- timeouts: timeoutsOf(options.timeouts),
599
- deadline: seconds("deadline", options.deadline, 1800)
600
- };
601
- }
602
- var byMx = (options) => !(("host" in options) && options.host !== undefined);
603
- function portOf(port, fallback) {
604
- if (port === undefined)
605
- return fallback;
606
- if (!Number.isInteger(port) || port < 1 || port > 65535)
607
- throw invalid(`port must be an integer from 1 to 65535, not ${port}`);
608
- return port;
609
- }
610
-
611
411
  // src/client/inbox.ts
612
412
  var MAX_REPLY_BYTES = 1024 * 1024;
613
413
 
@@ -634,6 +434,9 @@ class Inbox {
634
434
  }
635
435
  return;
636
436
  }
437
+ reset() {
438
+ this.#received = 0;
439
+ }
637
440
  shift() {
638
441
  return this.#replies.shift();
639
442
  }
@@ -675,10 +478,26 @@ class Clock {
675
478
  }
676
479
  }
677
480
 
481
+ // src/client/socket-events.ts
482
+ function socketHandlers(live, hooks) {
483
+ const when = (run) => (...args) => {
484
+ if (live())
485
+ run(...args);
486
+ };
487
+ return {
488
+ data: when((_, chunk) => hooks.receive(chunk)),
489
+ drain: when(() => hooks.drain()),
490
+ close: when(() => hooks.lost()),
491
+ error: when(() => hooks.lost()),
492
+ handshake: when((_, ok, error) => hooks.handshake(ok, error))
493
+ };
494
+ }
495
+ var connectFailure = (endpoint, error) => error instanceof SmtpError2 ? error : new SmtpError2("CONNECTION_FAILED", `Could not connect to ${endpoint.host} (${endpoint.address}:${endpoint.port}): ${reason(error)}`);
496
+
678
497
  // src/client/socket.ts
679
498
  class ClientSocket {
680
499
  host;
681
- #clock;
500
+ clock;
682
501
  #socket;
683
502
  #outgoing = new Outgoing(() => 0);
684
503
  #tls = false;
@@ -687,12 +506,13 @@ class ClientSocket {
687
506
  #closed = false;
688
507
  #wake;
689
508
  #handshaking;
690
- #gone = Promise.withResolvers();
509
+ #watchers = new Set;
691
510
  secure = false;
511
+ replies = 0;
692
512
  verified = false;
693
513
  constructor(host, clock) {
694
514
  this.host = host;
695
- this.#clock = clock;
515
+ this.clock = clock;
696
516
  }
697
517
  static async open(endpoint, tls, seconds, clock) {
698
518
  const client = new ClientSocket(endpoint.host, clock);
@@ -710,9 +530,7 @@ class ClientSocket {
710
530
  client.#use(await client.#race(connecting, seconds, "the connection"));
711
531
  } catch (error) {
712
532
  connecting.then((socket) => socket.terminate(), () => {});
713
- if (error instanceof SmtpError2)
714
- throw error;
715
- throw new SmtpError2("CONNECTION_FAILED", `Could not connect to ${endpoint.host} (${address}:${port}): ${reason(error)}`);
533
+ throw connectFailure(endpoint, error);
716
534
  }
717
535
  if (handshake) {
718
536
  await client.#race(handshake, seconds, "the TLS handshake").catch((e) => {
@@ -727,17 +545,12 @@ class ClientSocket {
727
545
  this.#outgoing = new Outgoing((bytes) => socket.write(bytes));
728
546
  }
729
547
  #handlers(tls) {
730
- const live = (run) => (...args) => {
731
- if (this.#tls === tls)
732
- run(...args);
733
- };
734
- return {
735
- data: live((_, chunk) => this.#receive(chunk)),
736
- drain: live(() => this.#outgoing.drain()),
737
- close: live(() => this.#lost()),
738
- error: live(() => this.#lost()),
739
- handshake: live((_, ok, error) => this.#handshaking?.(ok, error))
740
- };
548
+ return socketHandlers(() => this.#tls === tls, {
549
+ receive: (chunk) => this.#receive(chunk),
550
+ drain: () => this.#outgoing.drain(),
551
+ lost: () => this.#lost(),
552
+ handshake: (ok, error) => this.#handshaking?.(ok, error)
553
+ });
741
554
  }
742
555
  #expectHandshake(tls) {
743
556
  return new Promise((resolve, reject) => {
@@ -769,7 +582,8 @@ class ClientSocket {
769
582
  }
770
583
  #lost() {
771
584
  this.#closed = true;
772
- this.#gone.resolve();
585
+ for (const watcher of this.#watchers)
586
+ watcher();
773
587
  this.#outgoing.clear();
774
588
  this.#handshaking?.(false, null);
775
589
  this.#wakeUp();
@@ -782,16 +596,17 @@ class ClientSocket {
782
596
  }
783
597
  #race(promise, seconds, what, start = performance.now()) {
784
598
  const ms = start + seconds * 1000 - performance.now();
785
- const { host } = this;
786
- const deadline = this.#clock.seconds;
787
- return this.#clock.race(promise, ms, (passed) => this.#fail(timedOut(host, what, seconds, passed ? deadline : undefined)));
599
+ const { host, clock } = this;
600
+ return clock.race(promise, ms, (passed) => this.#fail(timedOut(host, what, seconds, passed ? clock.seconds : undefined)));
788
601
  }
789
602
  async reply(what, seconds) {
790
603
  const start = performance.now();
791
604
  for (;; ) {
792
605
  const next = this.#inbox.shift();
793
- if (next)
606
+ if (next) {
607
+ this.replies++;
794
608
  return next;
609
+ }
795
610
  if (this.#failure)
796
611
  throw this.#failure;
797
612
  if (this.#closed)
@@ -802,16 +617,28 @@ class ClientSocket {
802
617
  }
803
618
  }
804
619
  within(promise, seconds, what) {
805
- const over = new AbortController;
806
- const gone = this.#gone.promise.then(() => {
807
- if (!over.signal.aborted)
620
+ const watcher = Promise.withResolvers();
621
+ if (this.#closed)
622
+ watcher.resolve();
623
+ else
624
+ this.#watchers.add(watcher.resolve);
625
+ const gone = watcher.promise;
626
+ let over = false;
627
+ const hung = gone.then(() => {
628
+ if (!over)
808
629
  throw this.#hungUp(lost(this.host, what));
809
630
  });
810
- const race = Promise.race([promise, gone]);
631
+ const race = Promise.race([promise, hung]);
811
632
  race.catch(() => {
812
633
  return;
813
634
  });
814
- return this.#race(race, seconds, what).finally(() => over.abort());
635
+ hung.catch(() => {
636
+ return;
637
+ });
638
+ return this.#race(race, seconds, what).finally(() => {
639
+ over = true;
640
+ this.#watchers.delete(watcher.resolve);
641
+ });
815
642
  }
816
643
  write(data) {
817
644
  if (this.#failure || this.#closed)
@@ -845,6 +672,18 @@ class ClientSocket {
845
672
  this.#use(encrypted);
846
673
  await this.#race(handshake, seconds, "the TLS handshake");
847
674
  }
675
+ get quiet() {
676
+ return !this.#failure && !this.#closed && !this.#inbox.holding;
677
+ }
678
+ ref(keep) {
679
+ if (keep)
680
+ this.#socket?.ref();
681
+ else
682
+ this.#socket?.unref();
683
+ }
684
+ newMessage() {
685
+ this.#inbox.reset();
686
+ }
848
687
  close(now = false) {
849
688
  this.#closed = true;
850
689
  if (now)
@@ -854,14 +693,253 @@ class ClientSocket {
854
693
  }
855
694
  }
856
695
 
857
- // src/client/transaction.ts
858
- var missing = (host, extension, why) => new SmtpError2("EXTENSION_MISSING", `${host} does not offer ${extension}, which ${why}`);
859
- function mailParameters(host, settings, content, extensions) {
860
- let parameters = "";
861
- const size = extensions.get("SIZE");
862
- if (size !== undefined && content.size !== undefined) {
863
- const max = Number(size);
864
- if (Number.isSafeInteger(max) && max > 0 && content.size > max) {
696
+ // src/client/connect.ts
697
+ var early = new WeakSet;
698
+ async function connect(endpoint, settings, clock) {
699
+ const tls = {
700
+ servername: endpoint.host,
701
+ verify: settings.tls === "required",
702
+ ...settings.ca ? { ca: settings.ca } : {}
703
+ };
704
+ let socket;
705
+ try {
706
+ socket = await ClientSocket.open(endpoint, settings.secure ? tls : undefined, settings.timeouts.connect, clock);
707
+ } catch (error) {
708
+ early.add(error);
709
+ throw error;
710
+ }
711
+ try {
712
+ const ready = await open(socket, settings, tls);
713
+ return { socket, ready };
714
+ } catch (error) {
715
+ early.add(error);
716
+ socket.write(`QUIT\r
717
+ `);
718
+ socket.close();
719
+ throw error;
720
+ }
721
+ }
722
+ function resultOf(delivered, { socket, ready }, endpoint) {
723
+ return {
724
+ ...delivered,
725
+ host: endpoint.host,
726
+ port: endpoint.port,
727
+ tls: socket.secure ? { verified: socket.verified } : false,
728
+ authenticated: ready.authenticated
729
+ };
730
+ }
731
+
732
+ // src/client/settings.ts
733
+ import { hostname as machineName } from "node:os";
734
+
735
+ // src/client/dsn.ts
736
+ var EVENTS = ["SUCCESS", "FAILURE", "DELAY"];
737
+ function dsnOf(given, to, fail) {
738
+ const { ret, envid, recipients } = given;
739
+ if (ret !== undefined && ret !== "FULL" && ret !== "HDRS")
740
+ throw fail(`ret must be 'FULL' or 'HDRS', not ${String(ret)}`);
741
+ if (envid !== undefined && (typeof envid !== "string" || !/^[ -~]{1,100}$/.test(envid) || encodeXtext(envid).length > 100))
742
+ throw fail("envid must be 1 to 100 printable ASCII characters, at most 100 once encoded as xtext");
743
+ const byAddress = new Map;
744
+ if (recipients !== undefined) {
745
+ if (!Array.isArray(recipients))
746
+ throw fail("recipients must be an array of { address, notify, orcpt }");
747
+ for (const entry of recipients) {
748
+ const address = entry?.address;
749
+ if (typeof address !== "string" || !to.includes(address))
750
+ throw fail(`recipients names ${JSON.stringify(address)}, which is not in to`);
751
+ if (byAddress.has(address))
752
+ throw fail(`recipients names ${address} twice`);
753
+ checkRecipient(entry, fail);
754
+ byAddress.set(address, entry);
755
+ }
756
+ }
757
+ return {
758
+ ...ret ? { ret } : {},
759
+ ...envid === undefined ? {} : { envid },
760
+ recipients: byAddress
761
+ };
762
+ }
763
+ function checkRecipient({ address, notify, orcpt }, fail) {
764
+ if (notify !== undefined && notify !== "NEVER") {
765
+ const events = Array.isArray(notify) ? notify : [];
766
+ if (events.length === 0 || events.some((e, i) => !EVENTS.includes(e) || events.indexOf(e) !== i))
767
+ throw fail(`notify of ${address} must be 'NEVER' or a list of 'SUCCESS', 'FAILURE', 'DELAY', each once`);
768
+ }
769
+ if (orcpt !== undefined) {
770
+ if (typeof orcpt !== "object" || orcpt === null || typeof orcpt.type !== "string" || !/^[A-Za-z0-9-]+$/.test(orcpt.type))
771
+ throw fail(`orcpt of ${address} must be { type, address }, the type a token`);
772
+ if (typeof orcpt.address !== "string" || orcpt.address === "")
773
+ throw fail(`orcpt.address of ${address} must be a non-empty string`);
774
+ const encoded = orcpt.type.toLowerCase() === "utf-8" ? utf8OrcptLength(orcpt.address) : encodeXtext(orcpt.address).length;
775
+ if (orcpt.type.length + 1 + encoded > 500)
776
+ throw fail(`orcpt of ${address} is over 500 characters once encoded`);
777
+ }
778
+ }
779
+ function mailDsn(dsn, offered) {
780
+ if (!offered)
781
+ return "";
782
+ let out = "";
783
+ if (dsn.ret)
784
+ out += ` RET=${dsn.ret}`;
785
+ if (dsn.envid !== undefined)
786
+ out += ` ENVID=${encodeXtext(dsn.envid)}`;
787
+ return out;
788
+ }
789
+ function orcptParameter({ type, address }, smtputf8) {
790
+ const kind = type.toLowerCase();
791
+ if (kind === "rfc822")
792
+ return /^[\x20-\x7e]+$/.test(address) ? ` ORCPT=${type};${encodeXtext(address)}` : "";
793
+ if (kind !== "utf-8") {
794
+ const value = `${type};${encodeXtext(address)}`;
795
+ return address.isWellFormed() ? ` ORCPT=${value}` : "";
796
+ }
797
+ const encoded = encodeUtf8Orcpt(address, smtputf8);
798
+ if (encoded === undefined)
799
+ return "";
800
+ const value = `${type};${encoded}`;
801
+ return new TextEncoder().encode(value).length > 500 ? "" : ` ORCPT=${value}`;
802
+ }
803
+ function rcptDsn(dsn, address, offered, smtputf8 = false) {
804
+ const entry = dsn.recipients.get(address);
805
+ if (!offered || !entry)
806
+ return "";
807
+ let out = "";
808
+ const { notify, orcpt } = entry;
809
+ if (notify !== undefined)
810
+ out += ` NOTIFY=${notify === "NEVER" ? "NEVER" : notify.join(",")}`;
811
+ if (orcpt)
812
+ out += orcptParameter(orcpt, smtputf8);
813
+ return out;
814
+ }
815
+
816
+ // src/client/settings.ts
817
+ var MAX_SECONDS = Math.floor(2147483647 / 1000);
818
+ var TIMEOUTS = {
819
+ connect: 30,
820
+ greeting: 300,
821
+ command: 300,
822
+ mail: 300,
823
+ rcpt: 300,
824
+ dataStart: 120,
825
+ dataBlock: 180,
826
+ dataEnd: 600
827
+ };
828
+ var invalid = (message) => new SmtpError2("INVALID_OPTION", `sendMail(): ${message}`);
829
+ function seconds(name, value, fallback) {
830
+ if (value === undefined)
831
+ return fallback;
832
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > MAX_SECONDS) {
833
+ throw invalid(`${name} must be a number of seconds above 0 and at most ${MAX_SECONDS}, not ${String(value)}`);
834
+ }
835
+ return value;
836
+ }
837
+ function timeoutsOf(given = {}) {
838
+ const out = {};
839
+ for (const [name, fallback] of Object.entries(TIMEOUTS)) {
840
+ out[name] = seconds(`timeouts.${name}`, given[name], fallback);
841
+ }
842
+ return out;
843
+ }
844
+ function address(value, allowNull) {
845
+ if (allowNull && value === "")
846
+ return "";
847
+ if (isMailbox(value))
848
+ return value;
849
+ const text = typeof value === "string" ? value : "";
850
+ const routed = typeof value === "string" && parsePath2(`<${text}>`, false);
851
+ if (routed) {
852
+ throw invalid(`${JSON.stringify(value)} holds a source route (@host:), which RFC 5321 says a client should not send: pass ${JSON.stringify(`${routed.local}@${text.slice(text.lastIndexOf("@") + 1)}`)} alone`);
853
+ }
854
+ throw invalid(`${JSON.stringify(value)} is not an address (local@domain${allowNull ? ", or '' for a bounce" : ""})`);
855
+ }
856
+ function heloOf(value, byMx) {
857
+ if (value !== undefined) {
858
+ if (!isHelloName(value))
859
+ throw invalid(`helo "${value}" is not a host name`);
860
+ return value;
861
+ }
862
+ if (byMx)
863
+ throw invalid("helo is required for delivery by MX: pass your server's public name, such as helo: 'mail.example.com'");
864
+ const name = machineName();
865
+ return isHelloName(name) ? name : "localhost";
866
+ }
867
+ function authOf(auth) {
868
+ if (auth === undefined)
869
+ return;
870
+ const { username, password, mechanism } = auth;
871
+ if (typeof username !== "string" || username === "")
872
+ throw invalid("auth.username must be a non-empty string");
873
+ if (typeof password !== "string" || password === "")
874
+ throw invalid("auth.password must be a non-empty string");
875
+ if (mechanism !== undefined && mechanism !== "PLAIN" && mechanism !== "LOGIN")
876
+ throw invalid(`auth.mechanism must be 'PLAIN' or 'LOGIN', not ${mechanism}`);
877
+ return auth;
878
+ }
879
+ function tlsOf(options, plaintextAuth) {
880
+ const secure = options.secure === true;
881
+ const tls = options.tls ?? (secure || options.auth && !plaintextAuth ? "required" : "opportunistic");
882
+ if (tls !== "opportunistic" && tls !== "required" && tls !== "none")
883
+ throw invalid(`tls must be 'opportunistic', 'required' or 'none', not ${tls}`);
884
+ if (tls === "none" && secure)
885
+ throw invalid("secure is TLS from the first byte: it cannot go with tls: 'none'");
886
+ if (tls === "none" && options.auth && !plaintextAuth)
887
+ throw invalid("auth with tls: 'none' would send the password in clear; pass allowPlaintextAuth: true for a local test server");
888
+ if (tls === "opportunistic" && options.auth && !plaintextAuth)
889
+ throw invalid("auth with tls: 'opportunistic' would send the password to a server whose certificate is not checked; leave tls out to check it, or pass allowPlaintextAuth: true for a local test server");
890
+ return tls;
891
+ }
892
+ function settingsOf(options) {
893
+ if (typeof options !== "object" || options === null)
894
+ throw invalid("options must be an object");
895
+ const from = address(options.from, true);
896
+ const list = typeof options.to === "string" ? [options.to] : options.to;
897
+ if (!Array.isArray(list) || list.length === 0)
898
+ throw invalid("to must name one recipient or more");
899
+ const to = list.map((recipient) => address(recipient, false));
900
+ const allowPlaintextAuth = options.allowPlaintextAuth === true;
901
+ const { size } = options;
902
+ if (size !== undefined && (!Number.isSafeInteger(size) || size < 0))
903
+ throw invalid(`size must be a number of bytes, not ${size}`);
904
+ const ca = options.ca === undefined ? undefined : [options.ca].flat();
905
+ const auth = authOf(options.auth);
906
+ return {
907
+ from,
908
+ to,
909
+ helo: heloOf(options.helo, byMx(options)),
910
+ tls: tlsOf(options, allowPlaintextAuth),
911
+ secure: options.secure === true,
912
+ ...ca ? { ca } : {},
913
+ ...auth ? { auth } : {},
914
+ allowPlaintextAuth,
915
+ ...size === undefined ? {} : { size },
916
+ dsn: dsnOf(options, to, invalid),
917
+ smtputf8: options.smtputf8 === true || [from, ...to].some((text) => /[^ -~]/.test(text)),
918
+ normalizeLineEnds: options.normalizeLineEnds === true,
919
+ timeouts: timeoutsOf(options.timeouts),
920
+ deadline: seconds("deadline", options.deadline, 1800)
921
+ };
922
+ }
923
+ var byMx = (options) => !(("host" in options) && options.host !== undefined);
924
+ function portOf(port, fallback) {
925
+ if (port === undefined)
926
+ return fallback;
927
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
928
+ throw invalid(`port must be an integer from 1 to 65535, not ${port}`);
929
+ return port;
930
+ }
931
+
932
+ // src/client/transaction.ts
933
+ var stepped = new WeakSet;
934
+ var broken = new WeakSet;
935
+ var inStep = (error) => typeof error === "object" && error !== null && stepped.has(error);
936
+ var missing = (host, extension, why) => new SmtpError2("EXTENSION_MISSING", `${host} does not offer ${extension}, which ${why}`);
937
+ function mailParameters(host, settings, content, extensions) {
938
+ let parameters = "";
939
+ const size = extensions.get("SIZE");
940
+ if (size !== undefined && content.size !== undefined) {
941
+ const max = Number(size);
942
+ if (Number.isSafeInteger(max) && max > 0 && content.size > max) {
865
943
  throw new SmtpError2("MESSAGE_TOO_BIG", `The message (${content.size} bytes) is larger than the ${max} bytes ${host} takes (SIZE)`, { temporary: false });
866
944
  }
867
945
  parameters += ` SIZE=${content.size}`;
@@ -883,15 +961,34 @@ async function envelope(socket, settings, content, ready) {
883
961
  const { host } = socket;
884
962
  const { timeouts } = settings;
885
963
  const dsn = ready.extensions.has("DSN");
886
- const mail = `MAIL FROM:<${settings.from}>${mailParameters(host, settings, content, ready.extensions)}${mailDsn(settings.dsn, dsn)}\r
964
+ let parameters;
965
+ try {
966
+ parameters = mailParameters(host, settings, content, ready.extensions);
967
+ } catch (error) {
968
+ stepped.add(error);
969
+ throw error;
970
+ }
971
+ const mail = `MAIL FROM:<${settings.from}>${parameters}${mailDsn(settings.dsn, dsn)}\r
887
972
  `;
888
973
  const rcpts = settings.to.map((to) => `RCPT TO:<${to}>${rcptDsn(settings.dsn, to, dsn, settings.smtputf8)}\r
889
974
  `);
890
975
  const pipelining = ready.extensions.has("PIPELINING");
891
976
  socket.write(pipelining ? mail + rcpts.join("") : mail);
892
977
  const from = await socket.reply("the reply to MAIL FROM", timeouts.mail);
893
- if (from.code !== 250)
894
- throw refused(host, "the sender", from);
978
+ if (from.code !== 250) {
979
+ const refusal = refused(host, "the sender", from);
980
+ if (pipelining) {
981
+ for (const _ of rcpts) {
982
+ try {
983
+ await socket.reply("the reply to RCPT TO", timeouts.rcpt);
984
+ } catch {
985
+ broken.add(refusal);
986
+ break;
987
+ }
988
+ }
989
+ }
990
+ throw refusal;
991
+ }
895
992
  const accepted = [];
896
993
  const rejected = [];
897
994
  for (const [i, recipient] of settings.to.entries()) {
@@ -954,63 +1051,49 @@ async function data(socket, settings, content, ready, taken) {
954
1051
  return end;
955
1052
  }
956
1053
  async function transact(socket, settings, content, ready) {
957
- const { accepted, rejected } = await envelope(socket, settings, content, ready);
958
- const reply = await data(socket, settings, content, ready, {
959
- accepted,
960
- rejected
961
- });
962
- return {
963
- accepted,
964
- rejected,
965
- reply,
966
- dsnOffered: ready.extensions.has("DSN")
967
- };
1054
+ try {
1055
+ const { accepted, rejected } = await envelope(socket, settings, content, ready);
1056
+ const reply = await data(socket, settings, content, ready, {
1057
+ accepted,
1058
+ rejected
1059
+ });
1060
+ const delivered = {
1061
+ accepted,
1062
+ rejected,
1063
+ reply,
1064
+ dsnOffered: ready.extensions.has("DSN")
1065
+ };
1066
+ return { delivered };
1067
+ } catch (error) {
1068
+ const code = error instanceof SmtpError2 ? error.reply?.code : undefined;
1069
+ if (code !== undefined && code >= 400 && code < 600 && code !== 421 && !broken.has(error))
1070
+ stepped.add(error);
1071
+ throw error;
1072
+ }
968
1073
  }
969
1074
 
970
1075
  // src/client/send.ts
971
1076
  var MAX_ADDRESSES = 10;
972
1077
  var QUIT_WAIT = 5;
973
- var early = new WeakSet;
974
- async function attempt(endpoint, settings, content, clock) {
975
- const tls = {
976
- servername: endpoint.host,
977
- verify: settings.tls === "required",
978
- ...settings.ca ? { ca: settings.ca } : {}
979
- };
980
- let socket;
981
- try {
982
- socket = await ClientSocket.open(endpoint, settings.secure ? tls : undefined, settings.timeouts.connect, clock);
983
- } catch (error) {
984
- early.add(error);
985
- throw error;
986
- }
1078
+ var attempt = async (endpoint, settings, content, clock) => {
1079
+ const { socket, ready } = await connect(endpoint, settings, clock);
987
1080
  let quit = false;
988
1081
  try {
989
- const ready = await open(socket, settings, tls).catch((error) => {
990
- early.add(error);
991
- throw error;
992
- });
993
- const delivered = await transact(socket, settings, content, ready);
1082
+ const { delivered } = await transact(socket, settings, content, ready);
994
1083
  socket.write(`QUIT\r
995
1084
  `);
996
1085
  quit = true;
997
1086
  await socket.reply("the reply to QUIT", Math.min(QUIT_WAIT, settings.timeouts.command)).catch(() => {});
998
- return {
999
- ...delivered,
1000
- host: endpoint.host,
1001
- port: endpoint.port,
1002
- tls: socket.secure ? { verified: socket.verified } : false,
1003
- authenticated: ready.authenticated
1004
- };
1087
+ return resultOf(delivered, { socket, ready }, endpoint);
1005
1088
  } finally {
1006
1089
  if (!quit)
1007
1090
  socket.write(`QUIT\r
1008
1091
  `);
1009
1092
  socket.close();
1010
1093
  }
1011
- }
1094
+ };
1012
1095
  var withinDeadline = (lookup, clock, name, what) => clock.race(lookup, Number.POSITIVE_INFINITY, () => timedOut(name, what, clock.seconds, clock.seconds));
1013
- async function viaMx(options, settings, content, clock) {
1096
+ async function viaMx(try_, options, settings, content, clock) {
1014
1097
  const { domain, resolver } = options;
1015
1098
  if (typeof domain !== "string" || domain === "")
1016
1099
  throw invalid("domain must be a domain name");
@@ -1035,7 +1118,7 @@ async function viaMx(options, settings, content, clock) {
1035
1118
  if (tried++ >= MAX_ADDRESSES)
1036
1119
  break hosts;
1037
1120
  try {
1038
- return await attempt({ host: mx.host, address, port }, settings, content, clock);
1121
+ return await try_({ host: mx.host, address, port }, settings, content, clock);
1039
1122
  } catch (error) {
1040
1123
  const next = error instanceof SmtpError2 && error.temporary && early.has(error);
1041
1124
  if (!next)
@@ -1046,25 +1129,237 @@ async function viaMx(options, settings, content, clock) {
1046
1129
  }
1047
1130
  throw last ?? noAddress(domain, dnsError);
1048
1131
  }
1049
- async function sendMail(message, options) {
1050
- const settings = settingsOf(options);
1051
- const content = contentOf(message, settings.normalizeLineEnds, settings.size);
1052
- const clock = new Clock(settings.deadline);
1132
+ function deliver(try_, options, settings, content, clock) {
1053
1133
  if (!byMx(options)) {
1054
1134
  const { host } = options;
1055
1135
  if (typeof host !== "string" || !/^[^\s/]+$/.test(host))
1056
1136
  throw invalid(`host must be a host name or an address, not ${host}`);
1057
1137
  const port = portOf(options.port, settings.secure ? 465 : 25);
1058
- return attempt({ host, address: host, port }, settings, content, clock);
1138
+ return try_({ host, address: host, port }, settings, content, clock);
1139
+ }
1140
+ return viaMx(try_, options, settings, content, clock);
1141
+ }
1142
+ async function sendMail(message, options) {
1143
+ const settings = settingsOf(options);
1144
+ const content = contentOf(message, settings.normalizeLineEnds, settings.size);
1145
+ return deliver(attempt, options, settings, content, new Clock(settings.deadline));
1146
+ }
1147
+
1148
+ // src/client/pool/options.ts
1149
+ var invalid2 = (message) => new SmtpError2("INVALID_OPTION", `createSmtpPool(): ${message}`);
1150
+ function count(name, value, fallback) {
1151
+ if (value === undefined)
1152
+ return fallback;
1153
+ if (!Number.isSafeInteger(value) || value < 1)
1154
+ throw invalid2(`${name} must be an integer of at least 1, not ${String(value)}`);
1155
+ return value;
1156
+ }
1157
+ function seconds2(name, value, fallback) {
1158
+ if (value === undefined)
1159
+ return fallback;
1160
+ if (!Number.isFinite(value) || value <= 0 || value > MAX_SECONDS)
1161
+ throw invalid2(`${name} must be a number of seconds above 0 and at most ${MAX_SECONDS}, not ${String(value)}`);
1162
+ return value;
1163
+ }
1164
+ function limitsOf(options = {}) {
1165
+ if (typeof options !== "object" || options === null)
1166
+ throw invalid2("options must be an object");
1167
+ return {
1168
+ maxMessages: count("maxMessages", options.maxMessages, 100),
1169
+ idleSeconds: seconds2("idleSeconds", options.idleSeconds, 5),
1170
+ maxAgeSeconds: seconds2("maxAgeSeconds", options.maxAgeSeconds, 300),
1171
+ maxIdlePerHost: count("maxIdlePerHost", options.maxIdlePerHost, 4)
1172
+ };
1173
+ }
1174
+ var sameList = (a, b) => a === b || a !== undefined && b !== undefined && a.length === b.length && a.every((item, i) => item === b[i]);
1175
+ var sameAuth = (a, b) => a === b || a !== undefined && b !== undefined && a.username === b.username && a.password === b.password && a.mechanism === b.mechanism;
1176
+ function sameIdentity(a, b) {
1177
+ const x = a.connection;
1178
+ const y = b.connection;
1179
+ return a.host === b.host && a.address === b.address && a.port === b.port && x.helo === y.helo && x.tls === y.tls && x.secure === y.secure && x.allowPlaintextAuth === y.allowPlaintextAuth && sameList(x.ca, y.ca) && sameAuth(x.auth, y.auth);
1180
+ }
1181
+ var identityOf = (endpoint, settings) => ({
1182
+ host: endpoint.host,
1183
+ address: endpoint.address,
1184
+ port: endpoint.port,
1185
+ connection: {
1186
+ helo: settings.helo,
1187
+ tls: settings.tls,
1188
+ secure: settings.secure,
1189
+ allowPlaintextAuth: settings.allowPlaintextAuth,
1190
+ ...settings.ca ? { ca: [...settings.ca] } : {},
1191
+ ...settings.auth ? { auth: { ...settings.auth } } : {}
1059
1192
  }
1060
- return viaMx(options, settings, content, clock);
1193
+ });
1194
+
1195
+ // src/client/pool/reset.ts
1196
+ var RESET_WAIT = 5;
1197
+ async function reset(entry, clock, seconds) {
1198
+ const { socket } = entry;
1199
+ if (!socket.quiet)
1200
+ return false;
1201
+ socket.clock = clock;
1202
+ socket.write(`RSET\r
1203
+ `);
1204
+ try {
1205
+ return (await socket.reply("the reply to RSET", seconds)).code === 250;
1206
+ } catch {
1207
+ return false;
1208
+ }
1209
+ }
1210
+ var stale = (entry, before, error) => error instanceof SmtpError2 && error.code === "CONNECTION_LOST" && error.reply === undefined && !error.afterData && entry.socket.replies === before;
1211
+
1212
+ // src/client/pool/state.ts
1213
+ var newState = (limits) => ({
1214
+ limits,
1215
+ idle: [],
1216
+ busy: new Set,
1217
+ resetting: new Set,
1218
+ closed: false
1219
+ });
1220
+ function hangUp(entry, polite = true) {
1221
+ clearTimeout(entry.timer);
1222
+ if (polite)
1223
+ entry.socket.write(`QUIT\r
1224
+ `);
1225
+ entry.socket.close();
1226
+ }
1227
+ var aged = (state, entry) => performance.now() - entry.born >= state.limits.maxAgeSeconds * 1000;
1228
+
1229
+ // src/client/pool/idle.ts
1230
+ function forget(state, entry) {
1231
+ const at = state.idle.indexOf(entry);
1232
+ if (at >= 0)
1233
+ state.idle.splice(at, 1);
1234
+ clearTimeout(entry.timer);
1235
+ }
1236
+ function release(state, entry) {
1237
+ const { limits, idle } = state;
1238
+ const spent = state.closed || entry.messages >= limits.maxMessages || aged(state, entry) || !entry.socket.quiet;
1239
+ for (const other of idle.filter((e) => !e.socket.quiet)) {
1240
+ forget(state, other);
1241
+ hangUp(other, false);
1242
+ }
1243
+ const same = idle.filter((e) => e.identity.host === entry.identity.host);
1244
+ if (spent || same.length >= limits.maxIdlePerHost) {
1245
+ hangUp(entry);
1246
+ return;
1247
+ }
1248
+ entry.timer = setTimeout(() => {
1249
+ forget(state, entry);
1250
+ hangUp(entry);
1251
+ }, limits.idleSeconds * 1000);
1252
+ entry.timer.unref();
1253
+ entry.socket.ref(false);
1254
+ idle.push(entry);
1255
+ }
1256
+ async function borrow(state, identity, seconds, clock) {
1257
+ for (;; ) {
1258
+ const entry = state.idle.find((e) => sameIdentity(e.identity, identity));
1259
+ if (!entry)
1260
+ return;
1261
+ forget(state, entry);
1262
+ entry.socket.ref(true);
1263
+ if (aged(state, entry)) {
1264
+ hangUp(entry, true);
1265
+ continue;
1266
+ }
1267
+ if (await reset(entry, clock, seconds))
1268
+ return entry;
1269
+ hangUp(entry, false);
1270
+ }
1271
+ }
1272
+
1273
+ // src/client/pool/send.ts
1274
+ class Stale extends Error {
1275
+ }
1276
+ function recover(state, entry) {
1277
+ const job = reset(entry, new Clock(RESET_WAIT), RESET_WAIT).then((ok) => ok ? release(state, entry) : hangUp(entry, false)).catch(() => hangUp(entry, false));
1278
+ state.resetting.add(job);
1279
+ job.finally(() => state.resetting.delete(job));
1280
+ }
1281
+ async function send(state, entry, settings, content, clock, reused) {
1282
+ const { socket, ready } = entry;
1283
+ socket.clock = clock;
1284
+ socket.newMessage();
1285
+ const before = socket.replies;
1286
+ let result;
1287
+ try {
1288
+ const { delivered } = await transact(socket, settings, content, ready);
1289
+ result = {
1290
+ ...resultOf(delivered, entry, entry.identity),
1291
+ reused
1292
+ };
1293
+ } catch (error) {
1294
+ if (inStep(error)) {
1295
+ entry.messages++;
1296
+ recover(state, entry);
1297
+ } else
1298
+ hangUp(entry);
1299
+ if (reused && stale(entry, before, error))
1300
+ throw new Stale;
1301
+ throw error;
1302
+ }
1303
+ entry.messages++;
1304
+ release(state, entry);
1305
+ return result;
1306
+ }
1307
+ async function attempt2(state, endpoint, settings, content, clock) {
1308
+ const identity = identityOf(endpoint, settings);
1309
+ const wait = Math.min(RESET_WAIT, settings.timeouts.command);
1310
+ const entry = await borrow(state, identity, wait, clock);
1311
+ if (entry) {
1312
+ try {
1313
+ return await send(state, entry, settings, content, clock, true);
1314
+ } catch (error) {
1315
+ if (!(error instanceof Stale))
1316
+ throw error;
1317
+ }
1318
+ }
1319
+ const { socket, ready } = await connect(endpoint, settings, clock);
1320
+ const fresh = {
1321
+ socket,
1322
+ ready,
1323
+ identity,
1324
+ born: performance.now(),
1325
+ messages: 0
1326
+ };
1327
+ return send(state, fresh, settings, content, clock, false);
1328
+ }
1329
+
1330
+ // src/client/pool/index.ts
1331
+ function createSmtpPool(options) {
1332
+ const state = newState(limitsOf(options));
1333
+ return {
1334
+ sendMail(message, sendOptions) {
1335
+ const job = (async () => {
1336
+ if (state.closed)
1337
+ throw new SmtpError2("STOPPED", "The SMTP pool is closed");
1338
+ const settings = settingsOf(sendOptions);
1339
+ const content = contentOf(message, settings.normalizeLineEnds, settings.size);
1340
+ return deliver((endpoint, s, c, clock) => attempt2(state, endpoint, s, c, clock), sendOptions, settings, content, new Clock(settings.deadline));
1341
+ })();
1342
+ state.busy.add(job);
1343
+ const done = () => state.busy.delete(job);
1344
+ job.then(done, done);
1345
+ return job;
1346
+ },
1347
+ async close() {
1348
+ state.closed = true;
1349
+ for (const entry of state.idle.splice(0))
1350
+ hangUp(entry);
1351
+ await Promise.allSettled([...state.busy]);
1352
+ await Promise.allSettled([...state.resetting]);
1353
+ }
1354
+ };
1061
1355
  }
1062
1356
  export {
1063
1357
  SmtpError2 as SmtpError,
1358
+ createSmtpPool,
1064
1359
  isMailbox,
1065
1360
  resolveMx,
1066
1361
  sendMail
1067
1362
  };
1068
1363
 
1069
- //# debugId=3C3AEF3D2B40447E64756E2164756E21
1364
+ //# debugId=CDDCD67CAFC1023D64756E2164756E21
1070
1365
  //# sourceMappingURL=index.js.map