@mailerport/smtp 0.1.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.
@@ -0,0 +1,4 @@
1
+ import type { MailMessage, DeliveryResult, MailTransport } from "@mailerport/core";
2
+ export interface SmtpOptions { host:string;port?:number;secure?:boolean;requireTLS?:boolean;username?:string;password?:string;helo?:string }
3
+ export class SmtpTransport implements MailTransport { readonly kind:"smtp"; constructor(options:SmtpOptions); send(message:MailMessage):Promise<DeliveryResult> }
4
+ export function createSmtpTransport(options:SmtpOptions):SmtpTransport;
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { SmtpTransport, createSmtpTransport } from "./transports.js";
@@ -0,0 +1,62 @@
1
+ import net from "node:net";
2
+ import tls from "node:tls";
3
+ import { MailPortError, ERROR_CODES } from "@mailerport/core";
4
+ import { createMimeMessage } from "@mailerport/mime";
5
+
6
+ function smtpCommand(socket, command, expected) {
7
+ return new Promise((resolve, reject) => {
8
+ let response = "";
9
+ const onData = (chunk) => {
10
+ response += chunk;
11
+ const lines = response.split("\r\n").filter(Boolean);
12
+ const last = lines.at(-1) || "";
13
+ if (!/^\d{3} /.test(last)) return;
14
+ cleanup();
15
+ if (expected.includes(Number(last.slice(0, 3)))) resolve(response);
16
+ else reject(new Error(`SMTP rejected command (${last.slice(0, 3)})`));
17
+ };
18
+ const onError = (error) => { cleanup(); reject(error); };
19
+ const cleanup = () => { socket.off("data", onData); socket.off("error", onError); };
20
+ socket.on("data", onData); socket.on("error", onError);
21
+ if (command !== null) socket.write(`${command}\r\n`);
22
+ });
23
+ }
24
+
25
+ export class SmtpTransport {
26
+ constructor(options = {}) { this.kind = "smtp"; this.options = options; }
27
+ async send(message) {
28
+ const { host, port = this.options.secure ? 465 : 25, secure = false, username, password } = this.options;
29
+ if (!host) throw new MailPortError(ERROR_CODES.MAIL_DELIVERY_FAILED, "SMTP host is required");
30
+ let socket = secure ? tls.connect({ host, port, servername: host }) : net.connect({ host, port });
31
+ try {
32
+ await smtpCommand(socket, null, [220]);
33
+ let capabilities = await smtpCommand(socket, `EHLO ${this.options.helo || "mailport.local"}`, [250]);
34
+ if (!secure && /STARTTLS/i.test(capabilities)) {
35
+ await smtpCommand(socket, "STARTTLS", [220]);
36
+ socket = await new Promise((resolve, reject) => {
37
+ const secured = tls.connect({ socket, servername: host }, () => resolve(secured)); secured.once("error", reject);
38
+ });
39
+ capabilities = await smtpCommand(socket, `EHLO ${this.options.helo || "mailport.local"}`, [250]);
40
+ } else if (!secure && this.options.requireTLS) {
41
+ throw new Error("SMTP server does not advertise STARTTLS");
42
+ }
43
+ if (username) {
44
+ await smtpCommand(socket, "AUTH LOGIN", [334]);
45
+ await smtpCommand(socket, Buffer.from(username).toString("base64"), [334]);
46
+ await smtpCommand(socket, Buffer.from(password || "").toString("base64"), [235]);
47
+ }
48
+ await smtpCommand(socket, `MAIL FROM:<${message.from}>`, [250]);
49
+ for (const recipient of [...message.to, ...(message.cc || []), ...(message.bcc || [])])
50
+ await smtpCommand(socket, `RCPT TO:<${recipient}>`, [250, 251]);
51
+ await smtpCommand(socket, "DATA", [354]);
52
+ const mime = createMimeMessage(message).replace(/^\./gm, "..");
53
+ await smtpCommand(socket, `${mime}\r\n.`, [250]);
54
+ await smtpCommand(socket, "QUIT", [221]);
55
+ return { status: "sent", transport: "smtp", message_id: `<${message.message_id}@mailport>` };
56
+ } catch (error) {
57
+ throw new MailPortError(ERROR_CODES.MAIL_DELIVERY_FAILED, error.message);
58
+ } finally { socket.destroy(); }
59
+ }
60
+ }
61
+
62
+ export function createSmtpTransport(options) { return new SmtpTransport(options); }
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"name":"@mailerport/smtp","version":"0.1.0","description":"Optional SMTP transport for MailPort services","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"types":"./dist/index.d.ts","files":["dist"],"scripts":{"build":"node ../../scripts/build-package.js smtp","prepack":"npm run build"},"dependencies":{"@mailerport/core":"0.1.0","@mailerport/mime":"0.1.0"},"engines":{"node":">=20"},"license":"MIT","publishConfig":{"access":"public"}}