import tls from 'tls'
import net from 'net'
class SmtpClient {
constructor (opts = {}) {
this.host = opts.host
this.port = opts.port || 465
this.user = opts.user
this.pass = opts.pass
this.from = opts.from || opts.user
this.secure = opts.secure !== false // TLS by default
this._socket = null
}
connect () {
return new Promise((resolve, reject) => {
const onConnect = () => {
this._readLine().then(() => resolve()).catch(reject)
}
if (this.secure) {
this._socket = tls.connect({
host: this.host,
port: this.port,
rejectUnauthorized: true
}, onConnect)
} else {
this._socket = net.connect(this.port, this.host, onConnect)
}
this._socket.setEncoding('utf8')
this._socket.on('error', reject)
this._socket.setTimeout(15000, () => reject(new Error('SMTP connection timeout')))
})
}
_readLine () {
return new Promise((resolve, reject) => {
let buf = ''
const onData = (chunk) => {
buf += chunk
if (buf.includes('\r\n')) {
this._socket.removeListener('data', onData)
const code = parseInt(buf.slice(0, 3), 10)
if (code >= 400) reject(new Error(`SMTP ${code}: ${buf.trim()}`))
else resolve(buf.trim())
}
}
this._socket.on('data', onData)
setTimeout(() => {
this._socket.removeListener('data', onData)
reject(new Error('SMTP read timeout'))
}, 10000)
})
}
async _send (cmd) {
this._socket.write(cmd + '\r\n')
return this._readLine()
}
async ehlo () {
return this._send('EHLO bridge-email')
}
async auth () {
await this._send('AUTH LOGIN')
await this._send(Buffer.from(this.user).toString('base64'))
return this._send(Buffer.from(this.pass).toString('base64'))
}
async sendMail (to, subject, body, opts = {}) {
await this._send(`MAIL FROM:<${this.from}>`)
await this._send(`RCPT TO:<${to}>`)
await this._send('DATA')
const inReplyTo = opts.inReplyTo || ''
const references = opts.references || ''
let headers = `From: ${this.from}\r\n`
headers += `To: ${to}\r\n`
headers += `Subject: ${subject}\r\n`
headers += `Date: ${new Date().toUTCString()}\r\n`
headers += `Content-Type: text/plain; charset=utf-8\r\n`
headers += `Content-Transfer-Encoding: 8bit\r\n`
headers += `X-Bridge: bridge-email\r\n`
if (inReplyTo) headers += `In-Reply-To: ${inReplyTo}\r\n`
if (references) headers += `References: ${references}\r\n`
const message = headers + '\r\n' + body + '\r\n.'
return this._send(message)
}
async quit () {
try {
await this._send('QUIT')
} catch {}
this.close()
}
close () {
if (this._socket) {
this._socket.destroy()
this._socket = null
}
}
}
export { SmtpClient }