nodemailer.js
2.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* Nodemailer / SMTP provider。
*
* 纪律:transport 在本工厂内构造(注入凭据),**不在模块顶层 createTransport**、
* 不读 env。`nodemailer` 是可选 peer 依赖,按需 dynamic import,缺失时给出明确报错。
*/
import { EmailError } from '../map-error.js'
/**
* @param {Object} credentials
* @param {string} credentials.host
* @param {number} credentials.port
* @param {boolean} credentials.secure
* @param {{ user: string, pass: string }} credentials.auth
* @param {string} credentials.defaultFrom
* @returns {import('../index.js').Provider}
*/
export function createNodemailerProvider({ host, port, secure, auth, defaultFrom }) {
if (!host || !port || !auth || !defaultFrom) {
throw new EmailError({
provider: 'nodemailer',
reason: 'auth',
retryable: false,
message: 'nodemailer credentials require { host, port, auth, defaultFrom }',
})
}
// 惰性持有 transport:首次 send 时才构造,避免无谓 import;仍在工厂闭包内、非顶层。
/** @type {Promise<any>|null} */
let transportPromise = null
function getTransport() {
if (!transportPromise) {
transportPromise = import('nodemailer')
.then((mod) => {
const nodemailer = mod.default ?? mod
return nodemailer.createTransport({ host, port, secure, auth })
})
.catch((err) => {
transportPromise = null
throw new EmailError({
provider: 'nodemailer',
reason: 'unknown',
retryable: false,
cause: err,
message: 'nodemailer is not installed; add it as a dependency in the composition root',
})
})
}
return transportPromise
}
return {
async send(msg) {
const transport = await getTransport()
try {
const info = await transport.sendMail({
from: msg.from ?? defaultFrom,
to: msg.to,
subject: msg.subject,
html: msg.html,
text: msg.text,
replyTo: msg.reply_to,
cc: msg.cc,
bcc: msg.bcc,
})
return { message_id: info.messageId, provider: 'nodemailer', to: msg.to }
} catch (err) {
if (err instanceof EmailError) throw err
throw err // 由 email.js 编排层统一 mapError 归一
}
},
}
}