sently 0.3.3 → 0.4.1
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 +119 -0
- package/README.md +93 -0
- package/dist/adapters/bun.d.ts +35 -0
- package/dist/{src/adapters → adapters}/bun.js +1 -1
- package/dist/adapters/cf.d.ts +55 -0
- package/dist/{src/adapters → adapters}/cf.js +1 -1
- package/dist/adapters/deno.d.ts +48 -0
- package/dist/{src/adapters → adapters}/deno.js +1 -1
- package/dist/adapters/node.d.ts +35 -0
- package/dist/{src/adapters → adapters}/node.js +1 -1
- package/dist/auth/oauth2.d.ts +34 -0
- package/dist/{src/auth → auth}/oauth2.js +3 -3
- package/dist/{chunk-hdqpvsm8.js → chunk-bvxkmq94.js} +12 -3
- package/dist/{chunk-hdqpvsm8.js.map → chunk-bvxkmq94.js.map} +3 -3
- package/dist/{chunk-dhbe64fc.js → chunk-j6qw8ms6.js} +1 -1
- package/dist/{chunk-qb05tsqn.js → chunk-tjsgb3qb.js} +2 -221
- package/dist/chunk-tjsgb3qb.js.map +11 -0
- package/dist/chunk-z3eq2t1d.js +244 -0
- package/dist/chunk-z3eq2t1d.js.map +10 -0
- package/dist/core/address.d.ts +21 -0
- package/dist/core/base64.d.ts +27 -0
- package/dist/core/cram-md5.d.ts +17 -0
- package/dist/core/dkim.d.ts +22 -0
- package/dist/core/mime.d.ts +13 -0
- package/dist/core/plugin.d.ts +23 -0
- package/dist/core/sigv4.d.ts +57 -0
- package/dist/core/smtp.d.ts +90 -0
- package/dist/core/types.d.ts +291 -0
- package/dist/detect.d.ts +15 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +29 -0
- package/dist/{src/index.js.map → index.js.map} +1 -1
- package/dist/plugins/template.d.ts +61 -0
- package/dist/plugins/template.js +29 -0
- package/dist/plugins/template.js.map +10 -0
- package/dist/pool/connection.d.ts +25 -0
- package/dist/pool/pool.d.ts +59 -0
- package/dist/{src/pool → pool}/pool.js +26 -14
- package/dist/pool/pool.js.map +11 -0
- package/dist/transports/brevo.d.ts +20 -0
- package/dist/{src/transports → transports}/brevo.js +32 -4
- package/dist/transports/brevo.js.map +10 -0
- package/dist/transports/mailgun.d.ts +22 -0
- package/dist/{src/transports → transports}/mailgun.js +29 -4
- package/dist/transports/mailgun.js.map +10 -0
- package/dist/transports/postmark.d.ts +24 -0
- package/dist/{src/transports → transports}/postmark.js +33 -4
- package/dist/transports/postmark.js.map +10 -0
- package/dist/transports/preview.d.ts +15 -0
- package/dist/transports/preview.js +73 -0
- package/dist/transports/preview.js.map +10 -0
- package/dist/transports/resend.d.ts +26 -0
- package/dist/{src/transports → transports}/resend.js +28 -4
- package/dist/transports/resend.js.map +10 -0
- package/dist/transports/resolve-attachments.d.ts +12 -0
- package/dist/transports/retry.d.ts +21 -0
- package/dist/transports/retry.js +79 -0
- package/dist/transports/retry.js.map +10 -0
- package/dist/transports/sendgrid.d.ts +24 -0
- package/dist/{src/transports → transports}/sendgrid.js +33 -4
- package/dist/transports/sendgrid.js.map +10 -0
- package/dist/transports/ses.d.ts +25 -0
- package/dist/{src/transports → transports}/ses.js +45 -6
- package/dist/{src/transports → transports}/ses.js.map +3 -3
- package/dist/transports/smtp.d.ts +52 -0
- package/dist/transports/smtp.js +27 -0
- package/dist/{src/transports → transports}/smtp.js.map +1 -1
- package/package.json +25 -4
- package/dist/chunk-qb05tsqn.js.map +0 -12
- package/dist/src/index.js +0 -18
- package/dist/src/pool/pool.js.map +0 -11
- package/dist/src/transports/brevo.js.map +0 -10
- package/dist/src/transports/mailgun.js.map +0 -10
- package/dist/src/transports/postmark.js.map +0 -10
- package/dist/src/transports/resend.js.map +0 -10
- package/dist/src/transports/sendgrid.js.map +0 -10
- package/dist/src/transports/smtp.js +0 -25
- /package/dist/{src/adapters → adapters}/bun.js.map +0 -0
- /package/dist/{src/adapters → adapters}/cf.js.map +0 -0
- /package/dist/{src/adapters → adapters}/deno.js.map +0 -0
- /package/dist/{src/adapters → adapters}/node.js.map +0 -0
- /package/dist/{src/auth → auth}/oauth2.js.map +0 -0
- /package/dist/{chunk-dhbe64fc.js.map → chunk-j6qw8ms6.js.map} +0 -0
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildMIME
|
|
3
|
-
} from "./chunk-dhbe64fc.js";
|
|
4
|
-
import {
|
|
5
|
-
OAuth2Client
|
|
6
|
-
} from "./chunk-ym3zzv8b.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveAttachments
|
|
9
|
-
} from "./chunk-hdqpvsm8.js";
|
|
10
1
|
import {
|
|
11
2
|
decodeBase64,
|
|
12
3
|
encodeBase64,
|
|
13
4
|
encodeUtf8
|
|
14
5
|
} from "./chunk-794hc3m4.js";
|
|
15
|
-
import {
|
|
16
|
-
__require
|
|
17
|
-
} from "./chunk-v0bahtg2.js";
|
|
18
6
|
|
|
19
7
|
// src/core/cram-md5.ts
|
|
20
8
|
var BLOCK_SIZE = 64;
|
|
@@ -406,213 +394,6 @@ function encodeLine(line) {
|
|
|
406
394
|
`);
|
|
407
395
|
}
|
|
408
396
|
|
|
409
|
-
|
|
410
|
-
class SMTPTransport {
|
|
411
|
-
config;
|
|
412
|
-
adapter = null;
|
|
413
|
-
constructor(config) {
|
|
414
|
-
this.config = resolveSMTPConfig(config);
|
|
415
|
-
}
|
|
416
|
-
async send(options) {
|
|
417
|
-
const resolvedOptions = {
|
|
418
|
-
...options,
|
|
419
|
-
attachments: await resolveAttachments(options.attachments)
|
|
420
|
-
};
|
|
421
|
-
const mime = await buildMIME(resolvedOptions, this.config.dkim);
|
|
422
|
-
const adapter = await this.getAdapter();
|
|
423
|
-
const host = this.config.direct ? await resolveMX(mime.envelope.from.split("@")[1] ?? this.config.host) : this.config.host;
|
|
424
|
-
await adapter.connect(host, this.config.port);
|
|
425
|
-
this.adapter = adapter;
|
|
426
|
-
try {
|
|
427
|
-
await openSMTPSession(adapter, this.config);
|
|
428
|
-
return await deliverSMTPMessage(adapter, mime);
|
|
429
|
-
} finally {
|
|
430
|
-
await closeSMTPSession(adapter);
|
|
431
|
-
this.adapter = null;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
async verify() {
|
|
435
|
-
const adapter = await this.getAdapter();
|
|
436
|
-
await adapter.connect(this.config.host, this.config.port);
|
|
437
|
-
try {
|
|
438
|
-
await openSMTPSession(adapter, this.config);
|
|
439
|
-
return true;
|
|
440
|
-
} finally {
|
|
441
|
-
await closeSMTPSession(adapter);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
async close() {
|
|
445
|
-
if (this.adapter) {
|
|
446
|
-
await this.adapter.close();
|
|
447
|
-
this.adapter = null;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
async getAdapter() {
|
|
451
|
-
if (!this.config.adapter) {
|
|
452
|
-
throw new SMTPError("No socket adapter configured", 0, "CONNECT", "");
|
|
453
|
-
}
|
|
454
|
-
return this.config.adapter;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function resolveSMTPConfig(config) {
|
|
458
|
-
const secure = config.secure ?? false;
|
|
459
|
-
return {
|
|
460
|
-
host: config.host,
|
|
461
|
-
port: config.port ?? (secure ? 465 : 587),
|
|
462
|
-
secure,
|
|
463
|
-
...config.auth !== undefined ? { auth: config.auth } : {},
|
|
464
|
-
...config.dkim !== undefined ? { dkim: config.dkim } : {},
|
|
465
|
-
...config.tls !== undefined ? { tls: config.tls } : {},
|
|
466
|
-
...config.connectionTimeout !== undefined ? { connectionTimeout: config.connectionTimeout } : {},
|
|
467
|
-
...config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {},
|
|
468
|
-
...config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {},
|
|
469
|
-
...config.direct !== undefined ? { direct: config.direct } : {},
|
|
470
|
-
...config.adapter !== undefined ? { adapter: config.adapter } : {}
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
async function openSMTPSession(adapter, config) {
|
|
474
|
-
const greeting = await readSMTPResponse(adapter);
|
|
475
|
-
assertResponse(greeting, [220], "greeting");
|
|
476
|
-
let capabilities = await ehlo(adapter, config.host);
|
|
477
|
-
if (!config.secure && !adapter.secure) {
|
|
478
|
-
await sendRaw(adapter, encodeCommand({ type: "STARTTLS" }));
|
|
479
|
-
const starttlsResp = await readSMTPResponse(adapter);
|
|
480
|
-
assertResponse(starttlsResp, [220], "STARTTLS");
|
|
481
|
-
await adapter.startTLS(config.tls);
|
|
482
|
-
capabilities = await ehlo(adapter, config.host);
|
|
483
|
-
}
|
|
484
|
-
if (config.auth) {
|
|
485
|
-
await authenticate(adapter, config.auth, capabilities);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
async function deliverSMTPMessage(adapter, mime) {
|
|
489
|
-
await sendCommand(adapter, { type: "MAIL_FROM", address: mime.envelope.from });
|
|
490
|
-
const mailResp = await readSMTPResponse(adapter);
|
|
491
|
-
assertResponse(mailResp, [250], "MAIL FROM");
|
|
492
|
-
const accepted = [];
|
|
493
|
-
const rejected = [];
|
|
494
|
-
for (const recipient of mime.envelope.to) {
|
|
495
|
-
await sendRaw(adapter, encodeCommand({ type: "RCPT_TO", address: recipient }));
|
|
496
|
-
const rcptResp = await readSMTPResponse(adapter);
|
|
497
|
-
if (rcptResp.isSuccess) {
|
|
498
|
-
accepted.push(recipient);
|
|
499
|
-
} else {
|
|
500
|
-
rejected.push(recipient);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
await sendCommand(adapter, { type: "DATA" });
|
|
504
|
-
const dataResp = await readSMTPResponse(adapter);
|
|
505
|
-
assertResponse(dataResp, [354], "DATA");
|
|
506
|
-
let finalResp;
|
|
507
|
-
try {
|
|
508
|
-
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
509
|
-
finalResp = await readSMTPResponse(adapter);
|
|
510
|
-
} catch (err) {
|
|
511
|
-
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
512
|
-
finalResp = await readSMTPResponse(adapter);
|
|
513
|
-
if (finalResp.isError) {
|
|
514
|
-
throw err;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
assertResponse(finalResp, [250], "DATA end");
|
|
518
|
-
return {
|
|
519
|
-
messageId: mime.messageId,
|
|
520
|
-
accepted,
|
|
521
|
-
rejected,
|
|
522
|
-
response: finalResp.message,
|
|
523
|
-
envelope: mime.envelope
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
async function closeSMTPSession(adapter) {
|
|
527
|
-
try {
|
|
528
|
-
await sendCommand(adapter, { type: "QUIT" });
|
|
529
|
-
await readSMTPResponse(adapter);
|
|
530
|
-
} catch {} finally {
|
|
531
|
-
await adapter.close();
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
async function ehlo(adapter, host) {
|
|
535
|
-
await sendCommand(adapter, { type: "EHLO", domain: host });
|
|
536
|
-
const response = await readSMTPResponse(adapter);
|
|
537
|
-
assertResponse(response, [250], "EHLO");
|
|
538
|
-
return parseEHLO(response);
|
|
539
|
-
}
|
|
540
|
-
async function authenticate(adapter, auth, capabilities) {
|
|
541
|
-
if (auth.type === "OAUTH2" && auth.oauth2) {
|
|
542
|
-
const client = new OAuth2Client(auth.oauth2);
|
|
543
|
-
const xoauth2 = await client.buildXOAUTH2();
|
|
544
|
-
await sendCommand(adapter, { type: "AUTH_XOAUTH2", xoauth2String: xoauth2 });
|
|
545
|
-
let resp2 = await readSMTPResponse(adapter);
|
|
546
|
-
if (resp2.code === 334) {
|
|
547
|
-
await sendRaw(adapter, encodeLine(""));
|
|
548
|
-
resp2 = await readSMTPResponse(adapter);
|
|
549
|
-
}
|
|
550
|
-
assertResponse(resp2, [235], "AUTH XOAUTH2");
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
const method = auth.type ?? selectAuthMethod(capabilities);
|
|
554
|
-
if (method === "CRAM-MD5") {
|
|
555
|
-
const pass2 = requirePassword(auth, "CRAM-MD5");
|
|
556
|
-
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_INIT" });
|
|
557
|
-
let resp2 = await readSMTPResponse(adapter);
|
|
558
|
-
assertResponse(resp2, [334], "AUTH CRAM-MD5");
|
|
559
|
-
const challenge = resp2.message.trim();
|
|
560
|
-
const response = await computeCRAMMD5(challenge, auth.user, pass2);
|
|
561
|
-
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_RESPONSE", response });
|
|
562
|
-
resp2 = await readSMTPResponse(adapter);
|
|
563
|
-
assertResponse(resp2, [235], "AUTH CRAM-MD5 response");
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
if (method === "PLAIN") {
|
|
567
|
-
const pass2 = requirePassword(auth, "PLAIN");
|
|
568
|
-
await sendRaw(adapter, encodeCommand({ type: "AUTH_PLAIN", user: auth.user, pass: pass2 }));
|
|
569
|
-
const resp2 = await readSMTPResponse(adapter);
|
|
570
|
-
assertResponse(resp2, [235], "AUTH PLAIN");
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
const pass = requirePassword(auth, "LOGIN");
|
|
574
|
-
await sendRaw(adapter, encodeCommand({ type: "AUTH_LOGIN", user: auth.user, pass }));
|
|
575
|
-
let resp = await readSMTPResponse(adapter);
|
|
576
|
-
assertResponse(resp, [334], "AUTH LOGIN");
|
|
577
|
-
await sendRaw(adapter, encodeAuthLoginUser(auth.user));
|
|
578
|
-
resp = await readSMTPResponse(adapter);
|
|
579
|
-
assertResponse(resp, [334], "AUTH LOGIN user");
|
|
580
|
-
await sendRaw(adapter, encodeAuthLoginPass(pass));
|
|
581
|
-
resp = await readSMTPResponse(adapter);
|
|
582
|
-
assertResponse(resp, [235], "AUTH LOGIN pass");
|
|
583
|
-
}
|
|
584
|
-
function requirePassword(auth, method) {
|
|
585
|
-
if (!auth.pass) {
|
|
586
|
-
throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, "");
|
|
587
|
-
}
|
|
588
|
-
return auth.pass;
|
|
589
|
-
}
|
|
590
|
-
async function sendCommand(adapter, command) {
|
|
591
|
-
await sendRaw(adapter, encodeCommand(command));
|
|
592
|
-
}
|
|
593
|
-
async function sendRaw(adapter, data) {
|
|
594
|
-
await adapter.write(data);
|
|
595
|
-
}
|
|
596
|
-
async function readSMTPResponse(adapter) {
|
|
597
|
-
const chunks = [];
|
|
598
|
-
for await (const chunk of adapter.read()) {
|
|
599
|
-
chunks.push(chunk);
|
|
600
|
-
const complete = accumulateResponse(chunks);
|
|
601
|
-
if (complete) {
|
|
602
|
-
return parseResponse(complete);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
throw new SMTPError("Connection closed while reading SMTP response", 0, "READ", "");
|
|
606
|
-
}
|
|
607
|
-
async function resolveMX(domain) {
|
|
608
|
-
const dns = await import("node:dns/promises");
|
|
609
|
-
const records = await dns.resolveMx(domain);
|
|
610
|
-
if (records.length === 0) {
|
|
611
|
-
throw new SMTPError(`No MX records for ${domain}`, 0, "MX", "");
|
|
612
|
-
}
|
|
613
|
-
records.sort((a, b) => a.priority - b.priority);
|
|
614
|
-
return records[0]?.exchange ?? domain;
|
|
615
|
-
}
|
|
616
|
-
export { encodeLine, SMTPTransport, resolveSMTPConfig, openSMTPSession, deliverSMTPMessage, closeSMTPSession, readSMTPResponse };
|
|
397
|
+
export { computeCRAMMD5, SMTPError, encodeCommand, parseResponse, accumulateResponse, selectAuthMethod, parseEHLO, assertResponse, encodeAuthLoginPass, encodeAuthLoginUser, encodeLine };
|
|
617
398
|
|
|
618
|
-
//# debugId=
|
|
399
|
+
//# debugId=2548DBDC5D561CE764756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/cram-md5.ts", "../src/core/smtp.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * Pure-JS HMAC-MD5 implementation for SMTP CRAM-MD5 authentication.\n * Web Crypto does not support MD5, so this is implemented in pure TypeScript\n * with no external dependencies.\n *\n * @example\n * ```ts\n * import { computeCRAMMD5 } from \"sently/core/cram-md5\";\n * const response = await computeCRAMMD5(\"<challenge>\", \"user\", \"pass\");\n * ```\n */\nimport { decodeBase64, encodeBase64, encodeUtf8 } from \"./base64.js\";\n\n/** MD5 block size in bytes (HMAC block size per RFC 2104). */\nconst BLOCK_SIZE = 64;\n\n/** Coerce to unsigned 32-bit integer. */\nfunction u32(x: number): number {\n return x >>> 0;\n}\n\n/**\n * Compute an MD5 hash of the given data (RFC 1321).\n */\nexport function md5(data: Uint8Array): Uint8Array {\n const padded = padMessage(data);\n\n let a0 = 0x67452301;\n let b0 = 0xefcdab89;\n let c0 = 0x98badcfe;\n let d0 = 0x10325476;\n\n for (let i = 0; i < padded.length; i += 64) {\n const block = padded.subarray(i, i + 64);\n const m = new Uint32Array(16);\n for (let j = 0; j < 16; j++) {\n const o = j * 4;\n m[j] = u32(\n (block[o] ?? 0) |\n ((block[o + 1] ?? 0) << 8) |\n ((block[o + 2] ?? 0) << 16) |\n ((block[o + 3] ?? 0) << 24),\n );\n }\n\n let a = a0;\n let b = b0;\n let c = c0;\n let d = d0;\n\n for (let k = 0; k < 64; k++) {\n let f: number;\n let g: number;\n if (k < 16) {\n f = u32((b & c) | (~b & d));\n g = k;\n } else if (k < 32) {\n f = u32((b & d) | (c & ~d));\n g = u32((5 * k + 1) % 16);\n } else if (k < 48) {\n f = u32(b ^ c ^ d);\n g = u32((3 * k + 5) % 16);\n } else {\n f = u32(c ^ (b | ~d));\n g = u32((7 * k) % 16);\n }\n\n const temp = d;\n d = c;\n c = b;\n b = u32(b + leftRotate(u32(a + f + u32((K[k] ?? 0) + (m[g] ?? 0))), S[k] ?? 0));\n a = temp;\n }\n\n a0 = u32(a0 + a);\n b0 = u32(b0 + b);\n c0 = u32(c0 + c);\n d0 = u32(d0 + d);\n }\n\n const out = new Uint8Array(16);\n const view = new DataView(out.buffer);\n view.setUint32(0, a0, true);\n view.setUint32(4, b0, true);\n view.setUint32(8, c0, true);\n view.setUint32(12, d0, true);\n return out;\n}\n\nconst S = [\n 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,\n 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,\n 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,\n];\n\nconst K = new Uint32Array([\n 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,\n 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,\n 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,\n 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,\n 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,\n 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,\n 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,\n 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,\n]);\n\nfunction leftRotate(value: number, shift: number): number {\n return u32((value << shift) | (value >>> (32 - shift)));\n}\n\nfunction padMessage(data: Uint8Array): Uint8Array {\n const bitLen = data.length * 8;\n const padLen = (56 - ((data.length + 1) % 64) + 64) % 64;\n const totalLen = data.length + 1 + padLen + 8;\n const padded = new Uint8Array(totalLen);\n padded.set(data);\n padded[data.length] = 0x80;\n\n const view = new DataView(padded.buffer);\n view.setUint32(totalLen - 8, bitLen >>> 0, true);\n view.setUint32(totalLen - 4, Math.floor(bitLen / 0x100000000), true);\n return padded;\n}\n\n/**\n * Compute HMAC-MD5(key, data) per RFC 2104.\n */\nexport function hmacMD5(key: Uint8Array, data: Uint8Array): Uint8Array {\n let k = key;\n if (k.length > BLOCK_SIZE) {\n k = md5(k);\n }\n const paddedKey = new Uint8Array(BLOCK_SIZE);\n paddedKey.set(k);\n\n const ipad = new Uint8Array(BLOCK_SIZE);\n const opad = new Uint8Array(BLOCK_SIZE);\n for (let i = 0; i < BLOCK_SIZE; i++) {\n ipad[i] = (paddedKey[i] ?? 0) ^ 0x36;\n opad[i] = (paddedKey[i] ?? 0) ^ 0x5c;\n }\n\n const inner = new Uint8Array(ipad.length + data.length);\n inner.set(ipad);\n inner.set(data, ipad.length);\n const innerHash = md5(inner);\n\n const outer = new Uint8Array(opad.length + innerHash.length);\n outer.set(opad);\n outer.set(innerHash, opad.length);\n return md5(outer);\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Compute the CRAM-MD5 response string for SMTP authentication.\n *\n * @param challenge - base64-encoded challenge from server\n * @param user - SMTP username\n * @param pass - SMTP password\n * @returns base64-encoded CRAM-MD5 response\n */\nexport async function computeCRAMMD5(\n challenge: string,\n user: string,\n pass: string,\n): Promise<string> {\n const challengeBytes = decodeBase64(challenge.trim());\n const passBytes = encodeUtf8(pass);\n const digest = hmacMD5(passBytes, challengeBytes);\n const hex = bytesToHex(digest);\n return encodeBase64(`${user} ${hex}`).replace(/\\r\\n/g, \"\");\n}\n",
|
|
6
|
+
"// src/core/smtp.ts\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\n\nexport { computeCRAMMD5 } from \"./cram-md5.js\";\n\n/** SMTP command to send to the server. */\nexport type SMTPCommand =\n | { type: \"EHLO\"; domain: string }\n | { type: \"STARTTLS\" }\n | { type: \"AUTH_LOGIN\"; user: string; pass: string }\n | { type: \"AUTH_PLAIN\"; user: string; pass: string }\n | { type: \"AUTH_CRAM_MD5_INIT\" }\n | { type: \"AUTH_CRAM_MD5_RESPONSE\"; response: string }\n | { type: \"AUTH_XOAUTH2\"; xoauth2String: string }\n | { type: \"MAIL_FROM\"; address: string }\n | { type: \"RCPT_TO\"; address: string }\n | { type: \"DATA\" }\n | { type: \"DATA_BODY\"; content: Uint8Array }\n | { type: \"QUIT\" }\n | { type: \"RSET\" }\n | { type: \"NOOP\" };\n\n/** Parsed SMTP server response. */\nexport interface SMTPResponse {\n code: number;\n message: string;\n isSuccess: boolean;\n isReady: boolean;\n isError: boolean;\n}\n\n/** SMTP protocol error with server response details. */\nexport class SMTPError extends Error {\n /** Creates an SMTP protocol error. */\n constructor(\n message: string,\n public readonly code: number,\n public readonly command: string,\n public readonly response: string,\n ) {\n super(message);\n this.name = \"SMTPError\";\n }\n}\n\n/**\n * Encode an SMTPCommand into a Uint8Array for sending over the socket.\n */\nexport function encodeCommand(cmd: SMTPCommand): Uint8Array {\n let line: string;\n\n switch (cmd.type) {\n case \"EHLO\":\n line = `EHLO ${cmd.domain}`;\n break;\n case \"STARTTLS\":\n line = \"STARTTLS\";\n break;\n case \"AUTH_LOGIN\":\n line = \"AUTH LOGIN\";\n break;\n case \"AUTH_PLAIN\":\n line = `AUTH PLAIN ${encodeBase64(`\\0${cmd.user}\\0${cmd.pass}`).replace(/\\r\\n/g, \"\")}`;\n break;\n case \"AUTH_CRAM_MD5_INIT\":\n line = \"AUTH CRAM-MD5\";\n break;\n case \"AUTH_CRAM_MD5_RESPONSE\":\n return encodeUtf8(`${cmd.response}\\r\\n`);\n case \"AUTH_XOAUTH2\":\n line = `AUTH XOAUTH2 ${cmd.xoauth2String}`;\n break;\n case \"MAIL_FROM\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"MAIL FROM\", cmd.address);\n }\n line = `MAIL FROM:<${cmd.address}>`;\n break;\n case \"RCPT_TO\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"RCPT TO\", cmd.address);\n }\n line = `RCPT TO:<${cmd.address}>`;\n break;\n case \"DATA\":\n line = \"DATA\";\n break;\n case \"DATA_BODY\":\n return encodeUtf8(applyDotStuffing(cmd.content));\n case \"QUIT\":\n line = \"QUIT\";\n break;\n case \"RSET\":\n line = \"RSET\";\n break;\n case \"NOOP\":\n line = \"NOOP\";\n break;\n }\n\n return encodeUtf8(`${line}\\r\\n`);\n}\n\n/**\n * Parse raw bytes from the server into an SMTPResponse.\n */\nexport function parseResponse(data: Uint8Array): SMTPResponse {\n const text = new TextDecoder().decode(data).trim();\n const lines = text.split(/\\r?\\n/);\n const lastLine = lines[lines.length - 1] ?? \"\";\n const match = lastLine.match(/^(\\d{3})([\\s-])(.*)$/);\n\n if (!match) {\n throw new SMTPError(\"Invalid SMTP response\", 0, \"PARSE\", text);\n }\n\n const code = Number.parseInt(match[1] ?? \"0\", 10);\n const message = lines.map((l) => l.replace(/^\\d{3}[\\s-]/, \"\")).join(\" \");\n\n return {\n code,\n message,\n isSuccess: code >= 200 && code < 300,\n isReady: code >= 300 && code < 400,\n isError: code >= 400,\n };\n}\n\n/**\n * Accumulate byte chunks until a complete SMTP response is received.\n */\nexport function accumulateResponse(chunks: Uint8Array[]): Uint8Array | null {\n if (chunks.length === 0) {\n return null;\n }\n\n const total = chunks.reduce((sum, c) => sum + c.length, 0);\n const combined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n const text = new TextDecoder().decode(combined);\n const lines = text.split(/\\r?\\n/).filter((l) => l.length > 0);\n\n if (lines.length === 0) {\n return null;\n }\n\n const lastLine = lines[lines.length - 1] ?? \"\";\n if (/^\\d{3} /.test(lastLine)) {\n return combined;\n }\n\n return null;\n}\n\n/**\n * Select the best AUTH method from EHLO capability lines.\n * Priority: XOAUTH2 > CRAM-MD5 > LOGIN > PLAIN.\n */\nexport function selectAuthMethod(\n capabilities: string[],\n): \"LOGIN\" | \"PLAIN\" | \"CRAM-MD5\" | \"OAUTH2\" {\n const upper = capabilities.map((c) => c.toUpperCase());\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"XOAUTH2\"))) {\n return \"OAUTH2\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"CRAM-MD5\"))) {\n return \"CRAM-MD5\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"LOGIN\"))) {\n return \"LOGIN\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"PLAIN\"))) {\n return \"PLAIN\";\n }\n throw new SMTPError(\"No supported AUTH method\", 0, \"EHLO\", capabilities.join(\" \"));\n}\n\n/**\n * Parse an EHLO multi-line response and extract capability keywords.\n */\nexport function parseEHLO(response: SMTPResponse): string[] {\n return response.message\n .split(/\\s+/)\n .flatMap((part) => part.split(/\\r?\\n/))\n .filter(Boolean);\n}\n\n/**\n * Assert that an SMTPResponse code is within the expected set.\n */\nexport function assertResponse(\n response: SMTPResponse,\n expectedCodes: number[],\n command: string,\n): void {\n if (!expectedCodes.includes(response.code)) {\n throw new SMTPError(\n `Unexpected SMTP response for ${command}`,\n response.code,\n command,\n response.message,\n );\n }\n}\n\nfunction applyDotStuffing(content: Uint8Array): string {\n const text = new TextDecoder().decode(content);\n const lines = text.split(/\\r?\\n/);\n const stuffed = lines.map((line) => (line.startsWith(\".\") ? `.${line}` : line));\n return `${stuffed.join(\"\\r\\n\")}\\r\\n.\\r\\n`;\n}\n\n/** Encode AUTH LOGIN password step (second base64 chunk). */\nexport function encodeAuthLoginPass(pass: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(pass).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode AUTH LOGIN user step when sent separately after 334. */\nexport function encodeAuthLoginUser(user: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(user).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode CRAM-MD5 response after challenge. */\nexport function encodeAuthCramResponse(response: string): Uint8Array {\n return encodeUtf8(`${response}\\r\\n`);\n}\n\n/** Encode raw SMTP line with CRLF. */\nexport function encodeLine(line: string): Uint8Array {\n return encodeUtf8(`${line}\\r\\n`);\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "2548DBDC5D561CE764756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildMIME
|
|
3
|
+
} from "./chunk-j6qw8ms6.js";
|
|
4
|
+
import {
|
|
5
|
+
SMTPError,
|
|
6
|
+
accumulateResponse,
|
|
7
|
+
assertResponse,
|
|
8
|
+
computeCRAMMD5,
|
|
9
|
+
encodeAuthLoginPass,
|
|
10
|
+
encodeAuthLoginUser,
|
|
11
|
+
encodeCommand,
|
|
12
|
+
encodeLine,
|
|
13
|
+
parseEHLO,
|
|
14
|
+
parseResponse,
|
|
15
|
+
selectAuthMethod
|
|
16
|
+
} from "./chunk-tjsgb3qb.js";
|
|
17
|
+
import {
|
|
18
|
+
OAuth2Client
|
|
19
|
+
} from "./chunk-ym3zzv8b.js";
|
|
20
|
+
import {
|
|
21
|
+
resolveAttachments
|
|
22
|
+
} from "./chunk-bvxkmq94.js";
|
|
23
|
+
import {
|
|
24
|
+
__require
|
|
25
|
+
} from "./chunk-v0bahtg2.js";
|
|
26
|
+
|
|
27
|
+
// src/transports/smtp.ts
|
|
28
|
+
class SMTPTransport {
|
|
29
|
+
config;
|
|
30
|
+
adapter = null;
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.config = resolveSMTPConfig(config);
|
|
33
|
+
}
|
|
34
|
+
async send(options) {
|
|
35
|
+
const resolvedOptions = {
|
|
36
|
+
...options,
|
|
37
|
+
attachments: await resolveAttachments(options.attachments)
|
|
38
|
+
};
|
|
39
|
+
const mime = await buildMIME(resolvedOptions, this.config.dkim);
|
|
40
|
+
const adapter = await this.getAdapter();
|
|
41
|
+
const host = this.config.direct ? await resolveMX(mime.envelope.from.split("@")[1] ?? this.config.host) : this.config.host;
|
|
42
|
+
await adapter.connect(host, this.config.port);
|
|
43
|
+
this.adapter = adapter;
|
|
44
|
+
try {
|
|
45
|
+
await openSMTPSession(adapter, this.config);
|
|
46
|
+
return await deliverSMTPMessage(adapter, mime);
|
|
47
|
+
} finally {
|
|
48
|
+
await closeSMTPSession(adapter);
|
|
49
|
+
this.adapter = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async verify() {
|
|
53
|
+
try {
|
|
54
|
+
const adapter = await this.getAdapter();
|
|
55
|
+
await adapter.connect(this.config.host, this.config.port);
|
|
56
|
+
try {
|
|
57
|
+
await openSMTPSession(adapter, this.config);
|
|
58
|
+
return { ok: true, provider: "smtp" };
|
|
59
|
+
} finally {
|
|
60
|
+
await closeSMTPSession(adapter);
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
provider: "smtp",
|
|
66
|
+
message: err instanceof Error ? err.message : String(err)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async close() {
|
|
71
|
+
if (this.adapter) {
|
|
72
|
+
await this.adapter.close();
|
|
73
|
+
this.adapter = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async getAdapter() {
|
|
77
|
+
if (!this.config.adapter) {
|
|
78
|
+
throw new SMTPError("No socket adapter configured", 0, "CONNECT", "");
|
|
79
|
+
}
|
|
80
|
+
return this.config.adapter;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function resolveSMTPConfig(config) {
|
|
84
|
+
const secure = config.secure ?? false;
|
|
85
|
+
return {
|
|
86
|
+
host: config.host,
|
|
87
|
+
port: config.port ?? (secure ? 465 : 587),
|
|
88
|
+
secure,
|
|
89
|
+
...config.auth !== undefined ? { auth: config.auth } : {},
|
|
90
|
+
...config.dkim !== undefined ? { dkim: config.dkim } : {},
|
|
91
|
+
...config.tls !== undefined ? { tls: config.tls } : {},
|
|
92
|
+
...config.connectionTimeout !== undefined ? { connectionTimeout: config.connectionTimeout } : {},
|
|
93
|
+
...config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {},
|
|
94
|
+
...config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {},
|
|
95
|
+
...config.direct !== undefined ? { direct: config.direct } : {},
|
|
96
|
+
...config.adapter !== undefined ? { adapter: config.adapter } : {}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function openSMTPSession(adapter, config) {
|
|
100
|
+
const greeting = await readSMTPResponse(adapter);
|
|
101
|
+
assertResponse(greeting, [220], "greeting");
|
|
102
|
+
let capabilities = await ehlo(adapter, config.host);
|
|
103
|
+
if (!config.secure && !adapter.secure) {
|
|
104
|
+
await sendRaw(adapter, encodeCommand({ type: "STARTTLS" }));
|
|
105
|
+
const starttlsResp = await readSMTPResponse(adapter);
|
|
106
|
+
assertResponse(starttlsResp, [220], "STARTTLS");
|
|
107
|
+
await adapter.startTLS(config.tls);
|
|
108
|
+
capabilities = await ehlo(adapter, config.host);
|
|
109
|
+
}
|
|
110
|
+
if (config.auth) {
|
|
111
|
+
await authenticate(adapter, config.auth, capabilities);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function deliverSMTPMessage(adapter, mime) {
|
|
115
|
+
await sendCommand(adapter, { type: "MAIL_FROM", address: mime.envelope.from });
|
|
116
|
+
const mailResp = await readSMTPResponse(adapter);
|
|
117
|
+
assertResponse(mailResp, [250], "MAIL FROM");
|
|
118
|
+
const accepted = [];
|
|
119
|
+
const rejected = [];
|
|
120
|
+
for (const recipient of mime.envelope.to) {
|
|
121
|
+
await sendRaw(adapter, encodeCommand({ type: "RCPT_TO", address: recipient }));
|
|
122
|
+
const rcptResp = await readSMTPResponse(adapter);
|
|
123
|
+
if (rcptResp.isSuccess) {
|
|
124
|
+
accepted.push(recipient);
|
|
125
|
+
} else {
|
|
126
|
+
rejected.push(recipient);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await sendCommand(adapter, { type: "DATA" });
|
|
130
|
+
const dataResp = await readSMTPResponse(adapter);
|
|
131
|
+
assertResponse(dataResp, [354], "DATA");
|
|
132
|
+
let finalResp;
|
|
133
|
+
try {
|
|
134
|
+
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
135
|
+
finalResp = await readSMTPResponse(adapter);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
138
|
+
finalResp = await readSMTPResponse(adapter);
|
|
139
|
+
if (finalResp.isError) {
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
assertResponse(finalResp, [250], "DATA end");
|
|
144
|
+
return {
|
|
145
|
+
messageId: mime.messageId,
|
|
146
|
+
accepted,
|
|
147
|
+
rejected,
|
|
148
|
+
response: finalResp.message,
|
|
149
|
+
envelope: mime.envelope
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async function closeSMTPSession(adapter) {
|
|
153
|
+
try {
|
|
154
|
+
await sendCommand(adapter, { type: "QUIT" });
|
|
155
|
+
await readSMTPResponse(adapter);
|
|
156
|
+
} catch {} finally {
|
|
157
|
+
await adapter.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function ehlo(adapter, host) {
|
|
161
|
+
await sendCommand(adapter, { type: "EHLO", domain: host });
|
|
162
|
+
const response = await readSMTPResponse(adapter);
|
|
163
|
+
assertResponse(response, [250], "EHLO");
|
|
164
|
+
return parseEHLO(response);
|
|
165
|
+
}
|
|
166
|
+
async function authenticate(adapter, auth, capabilities) {
|
|
167
|
+
if (auth.type === "OAUTH2" && auth.oauth2) {
|
|
168
|
+
const client = new OAuth2Client(auth.oauth2);
|
|
169
|
+
const xoauth2 = await client.buildXOAUTH2();
|
|
170
|
+
await sendCommand(adapter, { type: "AUTH_XOAUTH2", xoauth2String: xoauth2 });
|
|
171
|
+
let resp2 = await readSMTPResponse(adapter);
|
|
172
|
+
if (resp2.code === 334) {
|
|
173
|
+
await sendRaw(adapter, encodeLine(""));
|
|
174
|
+
resp2 = await readSMTPResponse(adapter);
|
|
175
|
+
}
|
|
176
|
+
assertResponse(resp2, [235], "AUTH XOAUTH2");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const method = auth.type ?? selectAuthMethod(capabilities);
|
|
180
|
+
if (method === "CRAM-MD5") {
|
|
181
|
+
const pass2 = requirePassword(auth, "CRAM-MD5");
|
|
182
|
+
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_INIT" });
|
|
183
|
+
let resp2 = await readSMTPResponse(adapter);
|
|
184
|
+
assertResponse(resp2, [334], "AUTH CRAM-MD5");
|
|
185
|
+
const challenge = resp2.message.trim();
|
|
186
|
+
const response = await computeCRAMMD5(challenge, auth.user, pass2);
|
|
187
|
+
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_RESPONSE", response });
|
|
188
|
+
resp2 = await readSMTPResponse(adapter);
|
|
189
|
+
assertResponse(resp2, [235], "AUTH CRAM-MD5 response");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (method === "PLAIN") {
|
|
193
|
+
const pass2 = requirePassword(auth, "PLAIN");
|
|
194
|
+
await sendRaw(adapter, encodeCommand({ type: "AUTH_PLAIN", user: auth.user, pass: pass2 }));
|
|
195
|
+
const resp2 = await readSMTPResponse(adapter);
|
|
196
|
+
assertResponse(resp2, [235], "AUTH PLAIN");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const pass = requirePassword(auth, "LOGIN");
|
|
200
|
+
await sendRaw(adapter, encodeCommand({ type: "AUTH_LOGIN", user: auth.user, pass }));
|
|
201
|
+
let resp = await readSMTPResponse(adapter);
|
|
202
|
+
assertResponse(resp, [334], "AUTH LOGIN");
|
|
203
|
+
await sendRaw(adapter, encodeAuthLoginUser(auth.user));
|
|
204
|
+
resp = await readSMTPResponse(adapter);
|
|
205
|
+
assertResponse(resp, [334], "AUTH LOGIN user");
|
|
206
|
+
await sendRaw(adapter, encodeAuthLoginPass(pass));
|
|
207
|
+
resp = await readSMTPResponse(adapter);
|
|
208
|
+
assertResponse(resp, [235], "AUTH LOGIN pass");
|
|
209
|
+
}
|
|
210
|
+
function requirePassword(auth, method) {
|
|
211
|
+
if (!auth.pass) {
|
|
212
|
+
throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, "");
|
|
213
|
+
}
|
|
214
|
+
return auth.pass;
|
|
215
|
+
}
|
|
216
|
+
async function sendCommand(adapter, command) {
|
|
217
|
+
await sendRaw(adapter, encodeCommand(command));
|
|
218
|
+
}
|
|
219
|
+
async function sendRaw(adapter, data) {
|
|
220
|
+
await adapter.write(data);
|
|
221
|
+
}
|
|
222
|
+
async function readSMTPResponse(adapter) {
|
|
223
|
+
const chunks = [];
|
|
224
|
+
for await (const chunk of adapter.read()) {
|
|
225
|
+
chunks.push(chunk);
|
|
226
|
+
const complete = accumulateResponse(chunks);
|
|
227
|
+
if (complete) {
|
|
228
|
+
return parseResponse(complete);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
throw new SMTPError("Connection closed while reading SMTP response", 0, "READ", "");
|
|
232
|
+
}
|
|
233
|
+
async function resolveMX(domain) {
|
|
234
|
+
const dns = await import("node:dns/promises");
|
|
235
|
+
const records = await dns.resolveMx(domain);
|
|
236
|
+
if (records.length === 0) {
|
|
237
|
+
throw new SMTPError(`No MX records for ${domain}`, 0, "MX", "");
|
|
238
|
+
}
|
|
239
|
+
records.sort((a, b) => a.priority - b.priority);
|
|
240
|
+
return records[0]?.exchange ?? domain;
|
|
241
|
+
}
|
|
242
|
+
export { SMTPTransport, resolveSMTPConfig, openSMTPSession, deliverSMTPMessage, closeSMTPSession, readSMTPResponse };
|
|
243
|
+
|
|
244
|
+
//# debugId=58BDA0CC5C272AF664756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transports/smtp.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * SMTP transport — orchestrates socket adapter, MIME builder, and protocol logic.\n *\n * @example\n * ```ts\n * import { SMTPTransport } from \"sently/transports/smtp\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SMTPTransport({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * }),\n * });\n * ```\n */\nimport { OAuth2Client } from \"../auth/oauth2.js\";\nimport { buildMIME, type MIMEBuildResult } from \"../core/mime.js\";\nimport type { SMTPResponse } from \"../core/smtp.js\";\nimport {\n accumulateResponse,\n assertResponse,\n computeCRAMMD5,\n encodeAuthLoginPass,\n encodeAuthLoginUser,\n encodeCommand,\n encodeLine,\n parseEHLO,\n parseResponse,\n SMTPError,\n selectAuthMethod,\n} from \"../core/smtp.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/**\n * SMTP transport orchestrating adapter, MIME builder, and protocol logic.\n */\nexport class SMTPTransport implements Transport {\n private readonly config: ResolvedSMTPConfig;\n private adapter: SocketAdapter | null = null;\n\n /** Creates an SMTP transport with the given configuration. */\n constructor(config: SMTPConfig) {\n this.config = resolveSMTPConfig(config);\n }\n\n /** Sends an email via SMTP using the configured adapter. */\n async send(options: MailOptions): Promise<SendResult> {\n const resolvedOptions = {\n ...options,\n attachments: await resolveAttachments(options.attachments),\n };\n const mime = await buildMIME(resolvedOptions, this.config.dkim);\n const adapter = await this.getAdapter();\n\n const host = this.config.direct\n ? await resolveMX(mime.envelope.from.split(\"@\")[1] ?? this.config.host)\n : this.config.host;\n\n await adapter.connect(host, this.config.port);\n this.adapter = adapter;\n\n try {\n await openSMTPSession(adapter, this.config);\n return await deliverSMTPMessage(adapter, mime);\n } finally {\n await closeSMTPSession(adapter);\n this.adapter = null;\n }\n }\n\n /** Verifies SMTP connectivity and authentication without sending mail. */\n async verify(): Promise<VerifyResult> {\n try {\n const adapter = await this.getAdapter();\n await adapter.connect(this.config.host, this.config.port);\n\n try {\n await openSMTPSession(adapter, this.config);\n return { ok: true, provider: \"smtp\" };\n } finally {\n await closeSMTPSession(adapter);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Closes the underlying socket adapter if connected. */\n async close(): Promise<void> {\n if (this.adapter) {\n await this.adapter.close();\n this.adapter = null;\n }\n }\n\n private async getAdapter(): Promise<SocketAdapter> {\n if (!this.config.adapter) {\n throw new SMTPError(\"No socket adapter configured\", 0, \"CONNECT\", \"\");\n }\n return this.config.adapter;\n }\n}\n\n/** Resolved SMTP transport configuration with defaults applied. */\nexport interface ResolvedSMTPConfig {\n host: string;\n port: number;\n secure: boolean;\n auth?: SMTPConfig[\"auth\"];\n tls?: SMTPConfig[\"tls\"];\n dkim?: SMTPConfig[\"dkim\"];\n connectionTimeout?: number;\n greetingTimeout?: number;\n socketTimeout?: number;\n direct?: boolean;\n adapter?: SocketAdapter;\n}\n\n/** Apply defaults to SMTP configuration. */\nexport function resolveSMTPConfig(config: SMTPConfig): ResolvedSMTPConfig {\n const secure = config.secure ?? false;\n return {\n host: config.host,\n port: config.port ?? (secure ? 465 : 587),\n secure,\n ...(config.auth !== undefined ? { auth: config.auth } : {}),\n ...(config.dkim !== undefined ? { dkim: config.dkim } : {}),\n ...(config.tls !== undefined ? { tls: config.tls } : {}),\n ...(config.connectionTimeout !== undefined\n ? { connectionTimeout: config.connectionTimeout }\n : {}),\n ...(config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {}),\n ...(config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {}),\n ...(config.direct !== undefined ? { direct: config.direct } : {}),\n ...(config.adapter !== undefined ? { adapter: config.adapter } : {}),\n };\n}\n\n/**\n * Connect greeting, EHLO, optional STARTTLS, and AUTH on an open adapter.\n */\nexport async function openSMTPSession(\n adapter: SocketAdapter,\n config: ResolvedSMTPConfig,\n): Promise<void> {\n const greeting = await readSMTPResponse(adapter);\n assertResponse(greeting, [220], \"greeting\");\n\n let capabilities = await ehlo(adapter, config.host);\n if (!config.secure && !adapter.secure) {\n await sendRaw(adapter, encodeCommand({ type: \"STARTTLS\" }));\n const starttlsResp = await readSMTPResponse(adapter);\n assertResponse(starttlsResp, [220], \"STARTTLS\");\n await adapter.startTLS(config.tls);\n capabilities = await ehlo(adapter, config.host);\n }\n\n if (config.auth) {\n await authenticate(adapter, config.auth, capabilities);\n }\n}\n\n/**\n * MAIL FROM, RCPT TO, and DATA for a built MIME message on an authenticated session.\n */\nexport async function deliverSMTPMessage(\n adapter: SocketAdapter,\n mime: MIMEBuildResult,\n): Promise<SendResult> {\n await sendCommand(adapter, { type: \"MAIL_FROM\", address: mime.envelope.from });\n const mailResp = await readSMTPResponse(adapter);\n assertResponse(mailResp, [250], \"MAIL FROM\");\n\n const accepted: string[] = [];\n const rejected: string[] = [];\n\n for (const recipient of mime.envelope.to) {\n await sendRaw(adapter, encodeCommand({ type: \"RCPT_TO\", address: recipient }));\n const rcptResp = await readSMTPResponse(adapter);\n if (rcptResp.isSuccess) {\n accepted.push(recipient);\n } else {\n rejected.push(recipient);\n }\n }\n\n await sendCommand(adapter, { type: \"DATA\" });\n const dataResp = await readSMTPResponse(adapter);\n assertResponse(dataResp, [354], \"DATA\");\n\n let finalResp: SMTPResponse;\n try {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n } catch (err) {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n if (finalResp.isError) {\n throw err;\n }\n }\n assertResponse(finalResp, [250], \"DATA end\");\n\n return {\n messageId: mime.messageId,\n accepted,\n rejected,\n response: finalResp.message,\n envelope: mime.envelope,\n };\n}\n\n/**\n * QUIT and close an SMTP session adapter.\n */\nexport async function closeSMTPSession(adapter: SocketAdapter): Promise<void> {\n try {\n await sendCommand(adapter, { type: \"QUIT\" });\n await readSMTPResponse(adapter);\n } catch {\n // ignore errors during shutdown\n } finally {\n await adapter.close();\n }\n}\n\nasync function ehlo(adapter: SocketAdapter, host: string): Promise<string[]> {\n await sendCommand(adapter, { type: \"EHLO\", domain: host });\n const response = await readSMTPResponse(adapter);\n assertResponse(response, [250], \"EHLO\");\n return parseEHLO(response);\n}\n\nasync function authenticate(\n adapter: SocketAdapter,\n auth: NonNullable<SMTPConfig[\"auth\"]>,\n capabilities: string[],\n): Promise<void> {\n if (auth.type === \"OAUTH2\" && auth.oauth2) {\n const client = new OAuth2Client(auth.oauth2);\n const xoauth2 = await client.buildXOAUTH2();\n await sendCommand(adapter, { type: \"AUTH_XOAUTH2\", xoauth2String: xoauth2 });\n let resp = await readSMTPResponse(adapter);\n if (resp.code === 334) {\n await sendRaw(adapter, encodeLine(\"\"));\n resp = await readSMTPResponse(adapter);\n }\n assertResponse(resp, [235], \"AUTH XOAUTH2\");\n return;\n }\n\n const method = auth.type ?? selectAuthMethod(capabilities);\n\n if (method === \"CRAM-MD5\") {\n const pass = requirePassword(auth, \"CRAM-MD5\");\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_INIT\" });\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH CRAM-MD5\");\n const challenge = resp.message.trim();\n const response = await computeCRAMMD5(challenge, auth.user, pass);\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_RESPONSE\", response });\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH CRAM-MD5 response\");\n return;\n }\n\n if (method === \"PLAIN\") {\n const pass = requirePassword(auth, \"PLAIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_PLAIN\", user: auth.user, pass }));\n const resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH PLAIN\");\n return;\n }\n\n const pass = requirePassword(auth, \"LOGIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_LOGIN\", user: auth.user, pass }));\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN\");\n\n await sendRaw(adapter, encodeAuthLoginUser(auth.user));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN user\");\n\n await sendRaw(adapter, encodeAuthLoginPass(pass));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH LOGIN pass\");\n}\n\nfunction requirePassword(auth: NonNullable<SMTPConfig[\"auth\"]>, method: string): string {\n if (!auth.pass) {\n throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, \"\");\n }\n return auth.pass;\n}\n\nasync function sendCommand(\n adapter: SocketAdapter,\n command: Parameters<typeof encodeCommand>[0],\n): Promise<void> {\n await sendRaw(adapter, encodeCommand(command));\n}\n\nasync function sendRaw(adapter: SocketAdapter, data: Uint8Array): Promise<void> {\n await adapter.write(data);\n}\n\n/** Reads and parses a complete SMTP response from the adapter. */\nasync function readSMTPResponse(adapter: SocketAdapter): Promise<SMTPResponse> {\n const chunks: Uint8Array[] = [];\n for await (const chunk of adapter.read()) {\n chunks.push(chunk);\n const complete = accumulateResponse(chunks);\n if (complete) {\n return parseResponse(complete);\n }\n }\n throw new SMTPError(\"Connection closed while reading SMTP response\", 0, \"READ\", \"\");\n}\n\nasync function resolveMX(domain: string): Promise<string> {\n const dns = await import(\"node:dns/promises\");\n const records = await dns.resolveMx(domain);\n if (records.length === 0) {\n throw new SMTPError(`No MX records for ${domain}`, 0, \"MX\", \"\");\n }\n records.sort((a: { priority: number }, b: { priority: number }) => a.priority - b.priority);\n return records[0]?.exchange ?? domain;\n}\n\n/** @internal Test helper for raw line writes. */\nexport { encodeLine, readSMTPResponse };\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "58BDA0CC5C272AF664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Address, AddressInput } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize any AddressInput form into Address[].
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseAddresses(input: AddressInput): Address[];
|
|
6
|
+
/**
|
|
7
|
+
* Format an Address for SMTP envelope commands (MAIL FROM / RCPT TO).
|
|
8
|
+
*/
|
|
9
|
+
export declare function toEnvelope(address: Address): string;
|
|
10
|
+
/**
|
|
11
|
+
* Format an Address for use in a MIME header (From, To, CC, etc.).
|
|
12
|
+
*/
|
|
13
|
+
export declare function toMIMEHeader(address: Address): string;
|
|
14
|
+
/**
|
|
15
|
+
* Extract plain email strings from any AddressInput.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractEmails(input: AddressInput): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Basic email format validation (format only, no DNS lookup).
|
|
20
|
+
*/
|
|
21
|
+
export declare function isValidEmail(email: string): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode a Uint8Array or string to Base64.
|
|
3
|
+
* Uses TextEncoder + manual base64 to support binary data correctly.
|
|
4
|
+
*/
|
|
5
|
+
export declare function encodeBase64(data: Uint8Array | string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Decode a Base64 string to Uint8Array.
|
|
8
|
+
*/
|
|
9
|
+
export declare function decodeBase64(data: string): Uint8Array;
|
|
10
|
+
/**
|
|
11
|
+
* Encode text using Quoted-Printable (RFC 2045).
|
|
12
|
+
*/
|
|
13
|
+
export declare function encodeQP(text: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Encode an email header value per RFC 2047.
|
|
16
|
+
* Non-ASCII values become: =?UTF-8?B?<base64>?=
|
|
17
|
+
*/
|
|
18
|
+
export declare function encodeHeader(value: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Returns true if the string contains non-ASCII characters
|
|
21
|
+
* and therefore requires RFC 2047 encoding in headers.
|
|
22
|
+
*/
|
|
23
|
+
export declare function needsEncoding(text: string): boolean;
|
|
24
|
+
/** Decode bytes to UTF-8 string. */
|
|
25
|
+
export declare function decodeUtf8(bytes: Uint8Array): string;
|
|
26
|
+
/** Encode string to UTF-8 bytes. */
|
|
27
|
+
export declare function encodeUtf8(text: string): Uint8Array;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute an MD5 hash of the given data (RFC 1321).
|
|
3
|
+
*/
|
|
4
|
+
export declare function md5(data: Uint8Array): Uint8Array;
|
|
5
|
+
/**
|
|
6
|
+
* Compute HMAC-MD5(key, data) per RFC 2104.
|
|
7
|
+
*/
|
|
8
|
+
export declare function hmacMD5(key: Uint8Array, data: Uint8Array): Uint8Array;
|
|
9
|
+
/**
|
|
10
|
+
* Compute the CRAM-MD5 response string for SMTP authentication.
|
|
11
|
+
*
|
|
12
|
+
* @param challenge - base64-encoded challenge from server
|
|
13
|
+
* @param user - SMTP username
|
|
14
|
+
* @param pass - SMTP password
|
|
15
|
+
* @returns base64-encoded CRAM-MD5 response
|
|
16
|
+
*/
|
|
17
|
+
export declare function computeCRAMMD5(challenge: string, user: string, pass: string): Promise<string>;
|