Hours by client: add search and CSV filtering; add alerts cron (BRT 08:00 guard) + alerts panel filters; admin companies shows last alert; PDF Inter font from public/fonts; fix Select empty value; type cleanups; tests for CSV/TZ; remove Knowledge Base nav
This commit is contained in:
parent
2cf399dcb1
commit
08cc8037d5
151 changed files with 1404 additions and 214 deletions
69
src/server/email-smtp.ts
Normal file
69
src/server/email-smtp.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import tls from "tls"
|
||||
|
||||
type SmtpConfig = {
|
||||
host: string
|
||||
port: number
|
||||
username: string
|
||||
password: string
|
||||
from: string
|
||||
}
|
||||
|
||||
function b64(input: string) {
|
||||
return Buffer.from(input, "utf8").toString("base64")
|
||||
}
|
||||
|
||||
export async function sendSmtpMail(cfg: SmtpConfig, to: string, subject: string, html: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const socket = tls.connect(cfg.port, cfg.host, { rejectUnauthorized: false }, () => {
|
||||
let buffer = ""
|
||||
const send = (line: string) => socket.write(line + "\r\n")
|
||||
const wait = (expected: string | RegExp) =>
|
||||
new Promise<void>((res, rej) => {
|
||||
const onData = (data: Buffer) => {
|
||||
buffer += data.toString()
|
||||
const lines = buffer.split(/\r?\n/)
|
||||
const last = lines.filter(Boolean).slice(-1)[0] ?? ""
|
||||
if (typeof expected === "string" ? last.startsWith(expected) : expected.test(last)) {
|
||||
socket.removeListener("data", onData)
|
||||
res()
|
||||
}
|
||||
}
|
||||
socket.on("data", onData)
|
||||
socket.on("error", rej)
|
||||
})
|
||||
|
||||
;(async () => {
|
||||
await wait(/^220 /)
|
||||
send(`EHLO ${cfg.host}`)
|
||||
await wait(/^250-/)
|
||||
await wait(/^250 /)
|
||||
send("AUTH LOGIN")
|
||||
await wait(/^334 /)
|
||||
send(b64(cfg.username))
|
||||
await wait(/^334 /)
|
||||
send(b64(cfg.password))
|
||||
await wait(/^235 /)
|
||||
send(`MAIL FROM:<${cfg.from.match(/<(.+)>/)?.[1] ?? cfg.from}>`)
|
||||
await wait(/^250 /)
|
||||
send(`RCPT TO:<${to}>`)
|
||||
await wait(/^250 /)
|
||||
send("DATA")
|
||||
await wait(/^354 /)
|
||||
const headers = [
|
||||
`From: ${cfg.from}`,
|
||||
`To: ${to}`,
|
||||
`Subject: ${subject}`,
|
||||
"MIME-Version: 1.0",
|
||||
"Content-Type: text/html; charset=UTF-8",
|
||||
].join("\r\n")
|
||||
send(headers + "\r\n\r\n" + html + "\r\n.")
|
||||
await wait(/^250 /)
|
||||
send("QUIT")
|
||||
socket.end()
|
||||
resolve()
|
||||
})().catch(reject)
|
||||
})
|
||||
socket.on("error", reject)
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue