@flun/mailer 2.1.24 → 2.1.25
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 +7 -5
- package/LICENSE +19 -0
- package/Mail.js +2 -2
- package/README.md +35 -31
- package/copy-files.js +2 -2
- package/index.d.ts +27 -24
- package/index.js +24 -19
- package/lib/addressparser.js +21 -17
- package/lib/base64.js +8 -7
- package/lib/dkim/index.js +20 -17
- package/lib/dkim/message-parser.js +15 -12
- package/lib/dkim/relaxed-body.js +16 -15
- package/lib/dkim/sign.js +14 -13
- package/lib/fetch/cookies.js +24 -22
- package/lib/fetch/index.js +20 -17
- package/lib/http-proxy-client.js +14 -9
- package/lib/json-transport.js +8 -4
- package/lib/mail-composer.js +56 -39
- package/lib/mailer/index.js +17 -16
- package/lib/mailer/mail-message.js +28 -24
- package/lib/mime-funcs/index.js +69 -64
- package/lib/mime-funcs/mime-types.js +12 -6
- package/lib/mime-node/index.js +88 -74
- package/lib/mime-node/last-newline.js +4 -4
- package/lib/mime-node/le-unix.js +2 -2
- package/lib/mime-node/le-windows.js +3 -3
- package/lib/punycode.js +46 -40
- package/lib/qp.js +19 -15
- package/lib/regexs.js +30 -30
- package/lib/sendmail-transport.js +3 -3
- package/lib/ses-transport.js +12 -8
- package/lib/shared.js +74 -59
- package/lib/smtp-connection/data-stream.js +10 -9
- package/lib/smtp-connection/index.js +104 -86
- package/lib/smtp-pool/index.js +25 -22
- package/lib/smtp-pool/pool-resource.js +1 -1
- package/lib/smtp-transport.js +3 -2
- package/lib/stream-transport.js +12 -10
- package/lib/well-known/index.js +5 -4
- package/lib/xoauth2.js +31 -26
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# 变更日志
|
|
2
|
+
## [2.1.25] - 2026-10-25 08:24
|
|
3
|
+
|
|
4
|
+
### 优化
|
|
5
|
+
- 项目标点已改为以英文标点为主;
|
|
6
|
+
- 行宽改为以 123 为主,部分超宽表格改为了列表;
|
|
7
|
+
|
|
2
8
|
## [2.1.24] - 2026-10-06 14:37
|
|
3
9
|
### 修复
|
|
4
10
|
- 修复了" lib\mime-node\index.js" 中'key' 声明方式错误的问题;
|
|
@@ -7,8 +13,4 @@
|
|
|
7
13
|
## [2.1.23] - 2026-09-29 19:29
|
|
8
14
|
### 更改
|
|
9
15
|
- 加固了拷贝文件的一些逻辑;
|
|
10
|
-
- 升级了依赖;
|
|
11
|
-
|
|
12
|
-
## [2.1.22] - 2026-07-22 21:48
|
|
13
|
-
### 优化:
|
|
14
|
-
- 对 'README.md' 中一些描述做了一些优化;
|
|
16
|
+
- 升级了依赖;
|
package/LICENSE
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
ISC 许可证 (ISC License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, flun <open@flun.top>
|
|
4
|
+
|
|
5
|
+
特此免费授予任何获得本软件及相关文档文件("软件")副本的人,不受限制地处理本软件的权利,
|
|
6
|
+
包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售本软件副本的权利,
|
|
7
|
+
并允许向其提供本软件的人这样做,但须满足以下条件:
|
|
8
|
+
|
|
9
|
+
上述版权声明和本许可声明应包含在本软件的所有副本或重要部分中;
|
|
10
|
+
|
|
11
|
+
本软件按"原样"提供,不作任何形式的明示或暗示的保证,
|
|
12
|
+
包括但不限于适销性、特定用途的适用性和不侵权的保证;
|
|
13
|
+
在任何情况下,作者或版权持有人均不对因本软件或本软件的使用或其它交易而产生的
|
|
14
|
+
任何索赔、损害或其它责任负责,无论是合同之诉、侵权之诉还是其它行为之诉;
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
英文原文 (English Version)
|
|
19
|
+
|
|
1
20
|
ISC License
|
|
2
21
|
|
|
3
22
|
Copyright (c) 2026, flun <open@flun.top>
|
package/Mail.js
CHANGED
|
@@ -13,7 +13,7 @@ const transporter = createTransport({
|
|
|
13
13
|
// 步骤2:填写SMTP服务器的账号和密码
|
|
14
14
|
auth: {
|
|
15
15
|
user: 'your-email@example.com', // 您的邮箱地址
|
|
16
|
-
pass: 'your-password-or-app-password' // 您的邮箱密码或授权码(Gmail
|
|
16
|
+
pass: 'your-password-or-app-password' // 您的邮箱密码或授权码(Gmail需要应用专用密码,QQ邮箱需要授权码)
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ transporter.sendMail({
|
|
|
23
23
|
to: 'recipient@example.com', // 收件人
|
|
24
24
|
subject: '测试邮件', // 邮件主题
|
|
25
25
|
text: '这是一封测试邮件(text)', // 纯文本内容
|
|
26
|
-
html: '<b>这是一封测试邮件(html)</b>' // HTML
|
|
26
|
+
html: '<b>这是一封测试邮件(html)</b>' // HTML内容(可选;注:html内容优先级高于纯文本内容)
|
|
27
27
|
}, (error, info) => {
|
|
28
28
|
if (error) console.error('发送失败:', error);
|
|
29
29
|
else console.log('发送成功:', info.response);
|
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# @flun/mailer - 轻量级邮件发送库
|
|
2
2
|
|
|
3
3
|
一个简单易用的 Node.js 邮件发送库,支持多种邮件服务商,无需依赖外部邮件测试服务;
|
|
4
|
-
### 本包以 ESM
|
|
4
|
+
### 本包以 ESM 模块系统编写
|
|
5
|
+
|
|
6
|
+
-> 未来趋势;只要你的 Node.js 版本大于22.12,可保留CJS `require()` 语法调用,否则请使用 `import` 语法;
|
|
5
7
|
|
|
6
8
|
## 项目运行导图
|
|
7
9
|
|
|
@@ -24,7 +26,8 @@ graph TD
|
|
|
24
26
|
|
|
25
27
|
### 允许安装脚本执行
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
本包在安装时可能触发某些依赖包的自动脚本(如 `postinstall` 等);如果你的 npm 全局配置或项目配置禁止了脚本执行(例如设置了
|
|
30
|
+
`ignore-scripts=true`),可能会导致安装不完整或运行时异常;
|
|
28
31
|
|
|
29
32
|
推荐在项目根目录的 `package.json` 中添加 `allowScripts` 字段,显式放行本包及其依赖的脚本:
|
|
30
33
|
|
|
@@ -32,12 +35,13 @@ graph TD
|
|
|
32
35
|
{
|
|
33
36
|
"allowScripts": {
|
|
34
37
|
"@flun/mailer": true
|
|
35
|
-
//
|
|
38
|
+
// 如果依赖的其它包(如 bcrypt、electron-winstaller 等)也有脚本,请按需添加,格式相同
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
> 如果你信任所有安装包,也可以直接在项目 `.npmrc` 中设置 `allow-scripts = false
|
|
43
|
+
> 如果你信任所有安装包,也可以直接在项目 `.npmrc` 中设置 `allow-scripts = false`(表示关闭脚本拦截,所有脚本均允许执行),
|
|
44
|
+
或删除 `ignore-scripts` 字段;
|
|
41
45
|
|
|
42
46
|
---
|
|
43
47
|
|
|
@@ -49,12 +53,12 @@ npm i @flun/mailer
|
|
|
49
53
|
|
|
50
54
|
## 优势
|
|
51
55
|
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
56
|
+
- **安全性**:不依赖外部服务,数据完全可控
|
|
57
|
+
- **真实性**:使用真实邮箱测试,结果更准确
|
|
58
|
+
- **灵活性**:支持各种邮件服务商(Gmail、Outlook、QQ邮箱等)
|
|
59
|
+
- **简单性**:安装时在项目根目录动态创建邮件示例,配置更直接,无需理解复杂的测试服务概念
|
|
60
|
+
- **配置验证**:发送前可验证配置,确保必要的配置项已填写
|
|
61
|
+
- **清晰的错误提示**:指导用户正确使用
|
|
58
62
|
|
|
59
63
|
## 快速开始
|
|
60
64
|
|
|
@@ -89,7 +93,7 @@ transporter.sendMail({
|
|
|
89
93
|
to: 'recipient@example.com', // 收件人
|
|
90
94
|
subject: '测试邮件', // 邮件主题
|
|
91
95
|
text: '这是一封测试邮件(text)', // 纯文本内容
|
|
92
|
-
html: '<b>这是一封测试邮件(html)</b>' // HTML
|
|
96
|
+
html: '<b>这是一封测试邮件(html)</b>' // HTML内容(可选;注:html内容优先级高于纯文本内容)
|
|
93
97
|
}, (error, info) => {
|
|
94
98
|
if (error) console.error('发送失败:', error);
|
|
95
99
|
else console.log('发送成功:', info.response);
|
|
@@ -229,8 +233,8 @@ async function sendEmail() {
|
|
|
229
233
|
sendEmail();
|
|
230
234
|
```
|
|
231
235
|
---
|
|
232
|
-
|
|
233
|
-
## 1. SMTP
|
|
236
|
+
:#其它使用示例:
|
|
237
|
+
## 1. SMTP 连接池(高并发场景)
|
|
234
238
|
|
|
235
239
|
```javascript
|
|
236
240
|
import { createTransport } from '@flun/mailer';
|
|
@@ -282,7 +286,7 @@ async function sendBulkEmails() {
|
|
|
282
286
|
sendBulkEmails();
|
|
283
287
|
```
|
|
284
288
|
|
|
285
|
-
## 2. Sendmail
|
|
289
|
+
## 2. Sendmail 传输(Linux/Unix 系统)
|
|
286
290
|
|
|
287
291
|
```javascript
|
|
288
292
|
import { createTransport } from '@flun/mailer';
|
|
@@ -298,8 +302,8 @@ const mailOptions = {
|
|
|
298
302
|
from: '"系统" <system@server.com>',
|
|
299
303
|
to: 'admin@example.com',
|
|
300
304
|
subject: '服务器报警',
|
|
301
|
-
text: 'CPU使用率超过90
|
|
302
|
-
html: '<h1>服务器报警</h1><p>CPU使用率超过90
|
|
305
|
+
text: 'CPU使用率超过90%,请及时处理!',
|
|
306
|
+
html: '<h1>服务器报警</h1><p>CPU使用率超过90%,请及时处理!</p>'
|
|
303
307
|
};
|
|
304
308
|
|
|
305
309
|
// 3. 发送邮件
|
|
@@ -313,7 +317,7 @@ transporter.sendMail(mailOptions, (error, info) => {
|
|
|
313
317
|
});
|
|
314
318
|
```
|
|
315
319
|
|
|
316
|
-
## 3.
|
|
320
|
+
## 3. 流传输(用于测试和开发)
|
|
317
321
|
|
|
318
322
|
```javascript
|
|
319
323
|
import { createTransport } from '@flun/mailer';
|
|
@@ -341,7 +345,7 @@ const mailOptions = {
|
|
|
341
345
|
]
|
|
342
346
|
};
|
|
343
347
|
|
|
344
|
-
// 3.
|
|
348
|
+
// 3. 发送邮件(实际上只是生成邮件源码)
|
|
345
349
|
transporter.sendMail(mailOptions, (error, info) => {
|
|
346
350
|
if (error) {
|
|
347
351
|
console.log('生成邮件失败:', error);
|
|
@@ -349,14 +353,14 @@ transporter.sendMail(mailOptions, (error, info) => {
|
|
|
349
353
|
console.log('邮件源码:');
|
|
350
354
|
console.log(info.message.toString());
|
|
351
355
|
|
|
352
|
-
//
|
|
356
|
+
// 可选:保存到文件
|
|
353
357
|
fs.writeFileSync('debug-email.eml', info.message.toString());
|
|
354
358
|
console.log('邮件已保存到 debug-email.eml');
|
|
355
359
|
}
|
|
356
360
|
});
|
|
357
361
|
```
|
|
358
362
|
|
|
359
|
-
## 4. JSON
|
|
363
|
+
## 4. JSON 传输(用于调试)
|
|
360
364
|
|
|
361
365
|
```javascript
|
|
362
366
|
import { createTransport } from '@flun/mailer';
|
|
@@ -380,18 +384,18 @@ transporter.sendMail(mailOptions, (error, info) => {
|
|
|
380
384
|
if (error) {
|
|
381
385
|
console.log('错误:', error);
|
|
382
386
|
} else {
|
|
383
|
-
console.log('
|
|
387
|
+
console.log('邮件信息(JSON格式):');
|
|
384
388
|
console.log(JSON.stringify(info.message, null, 2));
|
|
385
389
|
|
|
386
|
-
// info.message
|
|
387
|
-
// - envelope:
|
|
390
|
+
// info.message 包含:
|
|
391
|
+
// - envelope: 信封信息(发件人、收件人)
|
|
388
392
|
// - messageId: 邮件ID
|
|
389
393
|
// - response: 响应信息
|
|
390
394
|
}
|
|
391
395
|
});
|
|
392
396
|
```
|
|
393
397
|
|
|
394
|
-
## 5. SES
|
|
398
|
+
## 5. SES 传输(AWS 亚马逊服务)
|
|
395
399
|
|
|
396
400
|
```javascript
|
|
397
401
|
import { createTransport } from '@flun/mailer';
|
|
@@ -466,22 +470,22 @@ transporter.sendMail(mailOptions)
|
|
|
466
470
|
});
|
|
467
471
|
```
|
|
468
472
|
|
|
469
|
-
|
|
473
|
+
每个示例都包含了:
|
|
470
474
|
1. **配置传输器** - 创建特定类型的邮件传输器
|
|
471
475
|
2. **设置邮件内容** - 定义发件人、收件人、主题、内容等
|
|
472
476
|
3. **发送邮件** - 执行发送操作并处理结果
|
|
473
477
|
|
|
474
|
-
对于生产环境,推荐使用 SMTP 或 SES
|
|
478
|
+
对于生产环境,推荐使用 SMTP 或 SES;对于开发和测试,可以使用流传输或 JSON 传输;
|
|
475
479
|
|
|
476
480
|
---
|
|
477
481
|
## 错误处理
|
|
478
482
|
|
|
479
|
-
|
|
483
|
+
库提供了详细的错误信息,帮助您快速定位问题:
|
|
480
484
|
|
|
481
|
-
-
|
|
482
|
-
-
|
|
483
|
-
-
|
|
484
|
-
-
|
|
485
|
+
- **配置错误**:如缺少必要参数、无效的端口号等
|
|
486
|
+
- **连接错误**:如无法连接到SMTP服务器
|
|
487
|
+
- **认证错误**:如用户名或密码错误
|
|
488
|
+
- **发送错误**:如收件人地址无效、邮件被拒绝等
|
|
485
489
|
|
|
486
490
|
## 注意事项
|
|
487
491
|
|
package/copy-files.js
CHANGED
|
@@ -16,11 +16,11 @@ const __filename = fileURLToPath(import.meta.url), __dirname = path.dirname(__fi
|
|
|
16
16
|
const copyFile = () => {
|
|
17
17
|
console.log(`🔍 检查 ${fileName} 文件...`), console.log(`📁 项目根目录:${targetDir}`);
|
|
18
18
|
try {
|
|
19
|
-
// 目标目录不是 Node 项目 || 包自身的 npm install
|
|
19
|
+
// 目标目录不是 Node 项目 || 包自身的 npm install(开发调试)→ 跳过
|
|
20
20
|
if (!fs.existsSync(path.join(targetDir, 'package.json'))) return true;
|
|
21
21
|
if (targetDir === path.resolve(__dirname, '..')) return true;
|
|
22
22
|
if (fs.existsSync(targetFile)) return true; // 目标文件已存在 → 直接返回
|
|
23
|
-
console.log(`⚠️ 在项目根目录未找到 ${fileName}
|
|
23
|
+
console.log(`⚠️ 在项目根目录未找到 ${fileName} 文件,正在创建...`);
|
|
24
24
|
|
|
25
25
|
fs.copyFileSync(sourceFile, targetFile); // 复制源文件到项目根目录
|
|
26
26
|
console.log(`✓ 已创建 ${fileName} 示例文件:${targetFile}`), console.log(`💡 请编辑 ${fileName} 文件,配置您的邮箱`);
|
package/index.d.ts
CHANGED
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
PK, regexs, resetRegex, dnsLookup, dnsResolve, format, isIP, netConnect, fsReadStream, osHostname, nmfetch,
|
|
35
35
|
getLogger, resolveHostname, newURL, parseConnectionUrl, parseDataURI, callbackPromise, resolveStream, resolveContent,
|
|
36
36
|
assign, encodeXText, initSmtpConstructor, getSocket, cleanup, createSmtpConnection, setupConnectionHandlers,
|
|
37
|
-
|
|
37
|
+
performSmtpAuthentica
|
|
38
|
+
*tion, createAuthConfig, prepareMessageForSending, handleSendResult, verifySmtp
|
|
38
39
|
} from './lib/shared.js';
|
|
39
40
|
import { SmtpTransport } from './lib/smtp-transport.js';
|
|
40
41
|
import { StreamTransport } from './lib/stream-transport.js';
|
|
@@ -150,7 +151,8 @@ declare module './lib/mailer/mail-message.js' {
|
|
|
150
151
|
* detectMimeType(); // 根据文件名检测MIME类型;
|
|
151
152
|
* foldLines(); // 折叠长行,适用于折叠头行(afterSpace=false)和流文本(afterSpace=true);
|
|
152
153
|
* ```
|
|
153
|
-
* >查看定义:@see {@link isPlainText}、{@link hasLongerLines}、
|
|
154
|
+
* >查看定义:@see {@link isPlainText}、{@link hasLongerLines}、
|
|
155
|
+
*{@link encodeWord}、{@link encodeWords}、{@link buildHeaderValue}、
|
|
154
156
|
* {@link parseHeaderValue}、{@link detectExtension}、{@link detectMimeType}、{@link foldLines}
|
|
155
157
|
*/
|
|
156
158
|
declare module './lib/mime-funcs/index.js' {
|
|
@@ -406,11 +408,11 @@ declare module './lib/ses-transport.js' {
|
|
|
406
408
|
* // 文件导出内容
|
|
407
409
|
* const PK={}; // package.json 包对象(含 name,,version,,homepage);
|
|
408
410
|
* const regexs={}; // 常用正则表达式集合;
|
|
409
|
-
* resetRegex(); //
|
|
411
|
+
* resetRegex(); // 重置正则表达式(清除 lastIndex);
|
|
410
412
|
* dnsLookup(); // dns.lookup 的引用;
|
|
411
413
|
* dnsResolve(); // dns.resolve 的引用;
|
|
412
414
|
* format(); // util.format 的引用;
|
|
413
|
-
* isIP(); // 检查字符串是否为 IP
|
|
415
|
+
* isIP(); // 检查字符串是否为 IP 地址(v4 或 v6);
|
|
414
416
|
* netConnect(); // net.connect 的引用;
|
|
415
417
|
* fsReadStream(); // fs.createReadStream 的引用;
|
|
416
418
|
* osHostname(); // os.hostname 的引用;
|
|
@@ -420,11 +422,11 @@ declare module './lib/ses-transport.js' {
|
|
|
420
422
|
* newURL(); // 安全地创建 URL 对象;
|
|
421
423
|
* parseConnectionUrl(); // 解析连接 URL 为配置对象;
|
|
422
424
|
* parseDataURI(); // 解析 Data URI 字符串;
|
|
423
|
-
* callbackPromise(); // 创建 Promise
|
|
425
|
+
* callbackPromise(); // 创建 Promise 回调包装器(resolve, reject);
|
|
424
426
|
* resolveStream(); // 将可读流完整读取到 Buffer 中;
|
|
425
427
|
* resolveContent(); // 解析字符串或 Buffer 值的内容;
|
|
426
|
-
* assign(); //
|
|
427
|
-
* encodeXText(); // XText
|
|
428
|
+
* assign(); // 合并对象属性(深度合并 tls 与 auth);
|
|
429
|
+
* encodeXText(); // XText 编码(SMTP 扩展);
|
|
428
430
|
* initSmtpConstructor(); // 初始化 SMTP 传输对象的公共构造函数逻辑;
|
|
429
431
|
* getSocket(); // 获取代理套接字的占位函数;
|
|
430
432
|
* cleanup(); // 用于清理状态对象;
|
|
@@ -441,7 +443,8 @@ declare module './lib/ses-transport.js' {
|
|
|
441
443
|
* {@link resolveHostname}、{@link newURL}、{@link parseConnectionUrl}、{@link parseDataURI}、{@link callbackPromise}、
|
|
442
444
|
* {@link resolveStream}、{@link resolveContent}、{@link assign}、{@link encodeXText}、{@link initSmtpConstructor}、
|
|
443
445
|
* {@link getSocket}、{@link cleanup}、{@link createSmtpConnection}、{@link setupConnectionHandlers}、
|
|
444
|
-
* {@link performSmtpAuthentication}、{@link createAuthConfig}、
|
|
446
|
+
* {@link performSmtpAuthentication}、{@link createAuthConfig}、
|
|
447
|
+
* {@link prepareMessageForSending}、{@link handleSendResult}、
|
|
445
448
|
* {@link verifySmtp}
|
|
446
449
|
*/
|
|
447
450
|
declare module './lib/shared.js' {
|
|
@@ -485,7 +488,7 @@ declare module './lib/xoauth2.js' {
|
|
|
485
488
|
}
|
|
486
489
|
|
|
487
490
|
/**
|
|
488
|
-
* 邮件发送模块
|
|
491
|
+
* 邮件发送模块 主要功能:
|
|
489
492
|
* ```js
|
|
490
493
|
* createTransport(); // 创建邮件传输器
|
|
491
494
|
* validateConfig(); // 验证配置
|
|
@@ -515,7 +518,7 @@ declare module './lib/xoauth2.js' {
|
|
|
515
518
|
* to: 'xyz@189.cn', // 收件人
|
|
516
519
|
* subject: '测试邮件', // 邮件主题
|
|
517
520
|
* text: '纯文本测试', // 纯文本内容
|
|
518
|
-
* html: '<b>html格式测试</b>' // HTML
|
|
521
|
+
* html: '<b>html格式测试</b>' // HTML内容(可选;注:html内容优先级高于纯文本内容)
|
|
519
522
|
* }, (error, info) => {
|
|
520
523
|
* if (error) console.error('发送失败:', error);
|
|
521
524
|
* else console.log('发送成功:', info.response);
|
|
@@ -533,7 +536,7 @@ declare module './index.js' {
|
|
|
533
536
|
/**
|
|
534
537
|
* 创建邮件传输器
|
|
535
538
|
*
|
|
536
|
-
*
|
|
539
|
+
* 根据配置自动选择合适的传输器类型:
|
|
537
540
|
* - 启用连接池: pool=true -> SmtpPool
|
|
538
541
|
* - 启用sendmail: sendmail=true或字符串 -> SendmailTransport
|
|
539
542
|
* - 启用流传输: streamTransport=true -> StreamTransport
|
|
@@ -593,7 +596,7 @@ declare module './index.js' {
|
|
|
593
596
|
|
|
594
597
|
/**
|
|
595
598
|
* 传输器配置类型
|
|
596
|
-
*
|
|
599
|
+
* 可以是:连接URL字符串、已封装好的传输器实例、或具体的传输器配置对象
|
|
597
600
|
*/
|
|
598
601
|
export type TransportConfig = string | Transporter | SpecificTransportConfig;
|
|
599
602
|
|
|
@@ -635,7 +638,7 @@ declare module './index.js' {
|
|
|
635
638
|
*/
|
|
636
639
|
sendMail(mail: MailData, callback: (err: Error | null, info: SentMessageInfo) => void): void;
|
|
637
640
|
/**
|
|
638
|
-
*
|
|
641
|
+
* 发送邮件(Promise 形式)
|
|
639
642
|
* @param mail 邮件内容
|
|
640
643
|
*/
|
|
641
644
|
sendMail(mail: MailData): Promise<SentMessageInfo>;
|
|
@@ -659,7 +662,7 @@ declare module './index.js' {
|
|
|
659
662
|
* 具体的传输器配置对象
|
|
660
663
|
*/
|
|
661
664
|
export interface SpecificTransportConfig {
|
|
662
|
-
/** 连接URL
|
|
665
|
+
/** 连接URL(优先级高于其他配置) */
|
|
663
666
|
url?: string;
|
|
664
667
|
/** SMTP服务器地址 */
|
|
665
668
|
host?: string;
|
|
@@ -688,7 +691,7 @@ declare module './index.js' {
|
|
|
688
691
|
maxConnections?: number;
|
|
689
692
|
/** 单个连接最大消息数 */
|
|
690
693
|
maxMessages?: number;
|
|
691
|
-
/** 使用 sendmail
|
|
694
|
+
/** 使用 sendmail 发送(布尔值或sendmail路径) */
|
|
692
695
|
sendmail?: boolean | string;
|
|
693
696
|
/** 使用流传输器输出 */
|
|
694
697
|
streamTransport?: boolean;
|
|
@@ -698,7 +701,7 @@ declare module './index.js' {
|
|
|
698
701
|
SES?: {
|
|
699
702
|
[key: string]: any;
|
|
700
703
|
};
|
|
701
|
-
/**
|
|
704
|
+
/** 自定义传输器发送方法(若直接提供传输器实例则无需配置) */
|
|
702
705
|
send?: (mail: MailMessage, callback: (err: Error | null, info: SentMessageInfo) => void) => void;
|
|
703
706
|
/** 其他扩展字段 */
|
|
704
707
|
[key: string]: any;
|
|
@@ -708,9 +711,9 @@ declare module './index.js' {
|
|
|
708
711
|
* 邮件数据对象,描述邮件内容及收件人信息
|
|
709
712
|
*/
|
|
710
713
|
export interface MailData {
|
|
711
|
-
/**
|
|
714
|
+
/** 发件人地址(格式:"名称 <地址>" 或纯地址) */
|
|
712
715
|
from?: string;
|
|
713
|
-
/**
|
|
716
|
+
/** 收件人地址(字符串或数组) */
|
|
714
717
|
to?: string | string[];
|
|
715
718
|
/** 抄送 */
|
|
716
719
|
cc?: string | string[];
|
|
@@ -720,7 +723,7 @@ declare module './index.js' {
|
|
|
720
723
|
subject?: string;
|
|
721
724
|
/** 纯文本内容 */
|
|
722
725
|
text?: string;
|
|
723
|
-
/** HTML
|
|
726
|
+
/** HTML内容(优先级高于纯文本) */
|
|
724
727
|
html?: string;
|
|
725
728
|
/** 附件列表 */
|
|
726
729
|
attachments?: Attachment[];
|
|
@@ -735,7 +738,7 @@ declare module './index.js' {
|
|
|
735
738
|
}
|
|
736
739
|
|
|
737
740
|
/**
|
|
738
|
-
*
|
|
741
|
+
* 内部使用的邮件消息对象(传递给底层传输器)
|
|
739
742
|
*/
|
|
740
743
|
export interface MailMessage {
|
|
741
744
|
/** 信封信息 */
|
|
@@ -745,7 +748,7 @@ declare module './index.js' {
|
|
|
745
748
|
};
|
|
746
749
|
/** 原始邮件数据 */
|
|
747
750
|
data?: string | Buffer | object;
|
|
748
|
-
/**
|
|
751
|
+
/** 消息内容(可能为字符串或Buffer) */
|
|
749
752
|
message?: string | Buffer;
|
|
750
753
|
/** 其他传输器需要的元数据 */
|
|
751
754
|
[key: string]: any;
|
|
@@ -757,13 +760,13 @@ declare module './index.js' {
|
|
|
757
760
|
export interface Attachment {
|
|
758
761
|
/** 文件名 */
|
|
759
762
|
filename?: string;
|
|
760
|
-
/**
|
|
763
|
+
/** 内容(支持字符串、Buffer、流) */
|
|
761
764
|
content?: string | Buffer | NodeJS.ReadableStream;
|
|
762
765
|
/** 文件路径 */
|
|
763
766
|
path?: string;
|
|
764
767
|
/** MIME类型 */
|
|
765
768
|
contentType?: string;
|
|
766
|
-
/** 内嵌ID
|
|
769
|
+
/** 内嵌ID(用于html内嵌图片) */
|
|
767
770
|
cid?: string;
|
|
768
771
|
/** 编码 */
|
|
769
772
|
encoding?: string;
|
|
@@ -805,7 +808,7 @@ declare module './index.js' {
|
|
|
805
808
|
}
|
|
806
809
|
|
|
807
810
|
/**
|
|
808
|
-
*
|
|
811
|
+
* 日志记录器接口(传输器内部使用)
|
|
809
812
|
*/
|
|
810
813
|
export interface Logger {
|
|
811
814
|
info(...args: any[]): void;
|
package/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* mailer 主模块
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - Mailer:
|
|
6
|
-
* - SmtpPool: SMTP
|
|
7
|
-
* - SmtpTransport: 标准SMTP
|
|
8
|
-
* - SendmailTransport: 本地sendmail
|
|
9
|
-
* - StreamTransport:
|
|
10
|
-
* - JsonTransport: JSON
|
|
11
|
-
* - SesTransport: Amazon SES
|
|
12
|
-
* - shared:
|
|
4
|
+
* 模块依赖说明:
|
|
5
|
+
* - Mailer: 核心邮件发送器,负责管理传输器和邮件发送流程
|
|
6
|
+
* - SmtpPool: SMTP连接池传输器,支持连接复用和并发发送
|
|
7
|
+
* - SmtpTransport: 标准SMTP传输器,用于通过SMTP服务器发送邮件
|
|
8
|
+
* - SendmailTransport: 本地sendmail传输器,使用系统sendmail命令发送
|
|
9
|
+
* - StreamTransport: 流传输器,将邮件输出为流格式,主要用于测试
|
|
10
|
+
* - JsonTransport: JSON传输器,将邮件转换为JSON格式输出,用于调试
|
|
11
|
+
* - SesTransport: Amazon SES传输器,通过AWS Simple Email Service发送邮件
|
|
12
|
+
* - shared: 共享工具函数模块,包含URL解析,正则常量等通用功能
|
|
13
13
|
*/
|
|
14
14
|
import { Mailer } from './lib/mailer/index.js';
|
|
15
15
|
import { SmtpPool } from './lib/smtp-pool/index.js';
|
|
@@ -59,9 +59,9 @@ const createTransport = (transporter, defaults) => {
|
|
|
59
59
|
}
|
|
60
60
|
const { host, service, auth, port, secure, tls, pool, maxConnections, maxMessages, sendmail, SES } = config;
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// 如果是字符串(URL格式)
|
|
63
63
|
if (typeof config === 'string' && !regexs.TRANSPORTER_PROTOCOL.test(config))
|
|
64
|
-
errors.push('URL
|
|
64
|
+
errors.push('URL格式不正确,应以 smtp://, smtps:// 或 direct:// 开头');
|
|
65
65
|
// 否则,如果是配置对象
|
|
66
66
|
else if (typeof config === 'object') {
|
|
67
67
|
// 检查传输器类型
|
|
@@ -71,34 +71,39 @@ const createTransport = (transporter, defaults) => {
|
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
if (!hasTransportType && typeof config.send !== 'function') {
|
|
74
|
-
// 默认为SMTP
|
|
74
|
+
// 默认为SMTP传输器,验证SMTP相关配置
|
|
75
75
|
if (!host && !service) errors.push('必须指定 host 或 service');
|
|
76
76
|
|
|
77
77
|
if (auth) {
|
|
78
78
|
if (!auth.user) errors.push('认证配置中缺少 user 字段');
|
|
79
|
-
if (!auth.pass && !auth.oauth2 && !auth.xoauth2)
|
|
79
|
+
if (!auth.pass && !auth.oauth2 && !auth.xoauth2)
|
|
80
|
+
warnings.push('(pass,oauth2,xoauth2)都无配置,将尝试无密码连接');
|
|
80
81
|
}
|
|
81
82
|
// 非25端口通常需要认证
|
|
82
|
-
else if (port !== 25) warnings.push('
|
|
83
|
+
else if (port !== 25) warnings.push('未提供认证信息,某些邮件服务器可能拒绝连接');
|
|
83
84
|
|
|
84
85
|
if (port && (port < 1 || port > 65535)) errors.push('端口号应在 1-65535 范围内');
|
|
85
86
|
// 检查TLS/SSL配置
|
|
86
87
|
if (secure !== undefined && typeof secure !== 'boolean') errors.push('secure 字段应为布尔值');
|
|
87
|
-
if (tls !== undefined && typeof tls !== 'object' && typeof tls !== 'boolean')
|
|
88
|
+
if (tls !== undefined && typeof tls !== 'object' && typeof tls !== 'boolean')
|
|
89
|
+
errors.push('tls 字段应为对象或布尔值');
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// 验证池配置
|
|
91
93
|
if (pool && typeof pool !== 'boolean') errors.push('pool 字段应为布尔值');
|
|
92
|
-
if (maxConnections && (typeof maxConnections !== 'number' || maxConnections < 1))
|
|
93
|
-
|
|
94
|
+
if (maxConnections && (typeof maxConnections !== 'number' || maxConnections < 1))
|
|
95
|
+
errors.push('maxConnections 应为大于0的数字');
|
|
96
|
+
if (maxMessages && (typeof maxMessages !== 'number' || maxMessages < 1))
|
|
97
|
+
errors.push('maxMessages 应为大于0的数字');
|
|
94
98
|
|
|
95
99
|
// 验证Sendmail配置
|
|
96
|
-
if (sendmail && sendmail !== true && typeof sendmail !== 'string')
|
|
100
|
+
if (sendmail && sendmail !== true && typeof sendmail !== 'string')
|
|
101
|
+
errors.push('sendmail 字段应为布尔值或字符串路径');
|
|
97
102
|
|
|
98
103
|
// 验证SES配置
|
|
99
104
|
if (SES) {
|
|
100
105
|
if (typeof SES !== 'object') errors.push('SES 配置应为对象');
|
|
101
|
-
else if (SES.ses && SES.aws) warnings.push('检测到旧版SES
|
|
106
|
+
else if (SES.ses && SES.aws) warnings.push('检测到旧版SES配置,建议使用新版AWS SDK');
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
else errors.push('配置应为字符串或对象');
|
package/lib/addressparser.js
CHANGED
|
@@ -9,7 +9,7 @@ class Tokenizer {
|
|
|
9
9
|
constructor(str = '') {
|
|
10
10
|
this.str = str.toString(), this.operatorExpecting = '', this.node = null, this.escaped = false, this.list = [];
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// 操作符映射:开始操作符到期望的结束操作符(引号,注释,邮件地址,地址分隔符,组定义开始,组定义结束)
|
|
13
13
|
this.operators = { '"': '"', '(': ')', '<': '>', ',': '', ':': ';', ';': '' };
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -35,9 +35,9 @@ class Tokenizer {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* 处理单个字符,识别操作符或文本内容
|
|
39
39
|
* @param {String} chr 当前字符
|
|
40
|
-
* @param {String} nextChr
|
|
40
|
+
* @param {String} nextChr 下一个字符(用于前瞻)
|
|
41
41
|
*/
|
|
42
42
|
#processChar(chr, nextChr) {
|
|
43
43
|
const { escaped, operatorExpecting: oExpecting, operators } = this;
|
|
@@ -47,7 +47,7 @@ class Tokenizer {
|
|
|
47
47
|
// 检查是否是新的开始操作符
|
|
48
48
|
else if (!escaped && !oExpecting && chr in operators) return this.#addOperatorToken(chr, nextChr, operators[chr]);
|
|
49
49
|
|
|
50
|
-
//
|
|
50
|
+
// 检查转义字符(在引号或注释中)
|
|
51
51
|
else if (!escaped && ['"', "'"].includes(oExpecting) && chr === '\\') return this.escaped = true;
|
|
52
52
|
this.#addTextChar(chr), this.escaped = false; // 处理文本字符并重置转义标志
|
|
53
53
|
}
|
|
@@ -55,12 +55,12 @@ class Tokenizer {
|
|
|
55
55
|
/**
|
|
56
56
|
* 添加操作符令牌到列表
|
|
57
57
|
* @param {String} chr 操作符字符
|
|
58
|
-
* @param {String} nextChr
|
|
58
|
+
* @param {String} nextChr 下一个字符(用于设置noBreak标志)
|
|
59
59
|
* @param {String} newExpecting 新的期望操作符
|
|
60
60
|
*/
|
|
61
61
|
#addOperatorToken(chr, nextChr, newExpecting = '') {
|
|
62
62
|
const node = { type: 'operator', value: chr };
|
|
63
|
-
//
|
|
63
|
+
// 如果下一个字符不是空白或分隔符,设置noBreak标志
|
|
64
64
|
if (nextChr && ![' ', '\t', '\r', '\n', ',', ';'].includes(nextChr)) node.noBreak = true;
|
|
65
65
|
this.list.push(node), this.node = null, this.escaped = false, this.operatorExpecting = newExpecting;
|
|
66
66
|
}
|
|
@@ -70,15 +70,17 @@ class Tokenizer {
|
|
|
70
70
|
* @param {String} chr 文本字符
|
|
71
71
|
*/
|
|
72
72
|
#addTextChar(chr) {
|
|
73
|
-
if (!this.node)
|
|
73
|
+
if (!this.node)
|
|
74
|
+
this.node = { type: 'text', value: '' }, this.list.push(this.node); // 如果没有当前文本节点,则创建一个新的
|
|
74
75
|
|
|
75
76
|
const processedChr = chr === '\n' ? ' ' : chr; // 规范化空白字符
|
|
76
|
-
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr))
|
|
77
|
+
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr))
|
|
78
|
+
this.node.value += processedChr; // 如果是可见字符或空格,则添加
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
81
|
-
*
|
|
83
|
+
* 如果文本数据为空但存在注释数据,则用注释数据替换文本数据
|
|
82
84
|
* @param {Object} data 包含文本和注释数据的对象
|
|
83
85
|
* @return {Object} 处理后的数据对象
|
|
84
86
|
*/
|
|
@@ -127,7 +129,7 @@ const _replaceTextWithComment = data => {
|
|
|
127
129
|
let isGroup = false, state = 'text';
|
|
128
130
|
const data = { address: [], comment: [], group: [], text: [] };
|
|
129
131
|
|
|
130
|
-
//
|
|
132
|
+
// 解析令牌,分类到地址、注释、组或文本
|
|
131
133
|
for (let i = 0; i < tokens.length; i++) {
|
|
132
134
|
const token = tokens[i], prevToken = i ? tokens[i - 1] : null;
|
|
133
135
|
if (token.type === 'operator')
|
|
@@ -150,24 +152,26 @@ const _replaceTextWithComment = data => {
|
|
|
150
152
|
// 清理地址令牌中的多余字符(如未引用的名称中的"<")
|
|
151
153
|
if (state === 'address') token.value = token.value.replace(regexs.ADDRESS_CLEAN, '');
|
|
152
154
|
|
|
153
|
-
// 如果前一个令牌设置了noBreak
|
|
155
|
+
// 如果前一个令牌设置了noBreak,则附加到最后一个元素,否则创建新的元素
|
|
154
156
|
const dLen = data[state].length;
|
|
155
157
|
prevToken?.noBreak && dLen ? data[state][dLen - 1] += token.value : data[state].push(token.value);
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
|
-
const { text, group, address } = _replaceTextWithComment(data); //
|
|
159
|
-
//
|
|
160
|
+
const { text, group, address } = _replaceTextWithComment(data); // 如果没有文本但有注释,用注释替换文本
|
|
161
|
+
// 如果组存在,处理组地址:组名和组成员列表
|
|
160
162
|
if (isGroup) return [{ name: text.join(' ') || '', group: group.length ? addressparser(group.join(',')) : [] }];
|
|
161
163
|
else {
|
|
162
|
-
if (!address.length && text.length)
|
|
164
|
+
if (!address.length && text.length)
|
|
165
|
+
_extractEmailFromText(data); // 如果没有地址,从文本中提取电子邮件地址
|
|
163
166
|
|
|
164
167
|
const { text: newTxt, address: newAdd } = _replaceTextWithComment(data); // 再次检查是否需要将注释转换为文本
|
|
165
|
-
if (newAdd.length > 1)
|
|
168
|
+
if (newAdd.length > 1)
|
|
169
|
+
newTxt.push(...newAdd.splice(1)); // 如果找到多个地址,只保留第一个,其余的转到文本数组
|
|
166
170
|
const addressText = newTxt.join(' ').trim(), addressValue = newAdd.join(' ').trim(),// 合并文本和地址数据
|
|
167
171
|
// 创建地址对象
|
|
168
172
|
addressObj = { address: addressValue || addressText || '', name: addressText || addressValue || '' };
|
|
169
173
|
|
|
170
|
-
//
|
|
174
|
+
// 如果地址和名称相同,根据内容决定清除哪个(如果是邮件地址,清除名称,否则,清除地址)
|
|
171
175
|
if (addressObj.address === addressObj.name)
|
|
172
176
|
addressObj.address.includes('@') ? addressObj.name = '' : addressObj.address = '';
|
|
173
177
|
|
|
@@ -213,7 +217,7 @@ const addressparser = (str, options = {}) => {
|
|
|
213
217
|
if (address.length) parsedAddresses = parsedAddresses.concat(address);
|
|
214
218
|
});
|
|
215
219
|
|
|
216
|
-
//
|
|
220
|
+
// 如果要求展平,将组地址转换为平面列表
|
|
217
221
|
if (options.flatten) {
|
|
218
222
|
const flattenedAddresses = [],
|
|
219
223
|
walkAddressList = list => {
|