feat: add email functionality for invoices using Nodemailer and Markdown template

This commit is contained in:
Astrian Zheng 2025-01-12 11:01:54 +11:00
parent feee4184a4
commit 64578233f0
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
4 changed files with 90 additions and 2 deletions

View File

@ -19,6 +19,8 @@
"koa": "^2.15.3",
"koa-body": "^6.0.1",
"koa-route": "^4.0.1",
"marked": "^15.0.6",
"nodemailer": "^6.9.16",
"npm": "^11.0.0",
"puppeteer": "^24.0.0"
},
@ -28,6 +30,7 @@
"@types/koa": "^2.15.0",
"@types/koa-route": "^3.2.8",
"@types/node": "^14.0.0",
"@types/nodemailer": "^6.4.17",
"nodemon": "^3.1.9",
"prisma": "^6.2.1",
"ts-node": "^10.0.0",
@ -423,6 +426,16 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="
},
"node_modules/@types/nodemailer": {
"version": "6.4.17",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.9.17",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz",
@ -1902,6 +1915,18 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/marked": {
"version": "15.0.6",
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.6.tgz",
"integrity": "sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -2004,6 +2029,15 @@
"node": ">= 0.4.0"
}
},
"node_modules/nodemailer": {
"version": "6.9.16",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemon": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",

View File

@ -14,6 +14,7 @@
"@types/koa": "^2.15.0",
"@types/koa-route": "^3.2.8",
"@types/node": "^14.0.0",
"@types/nodemailer": "^6.4.17",
"nodemon": "^3.1.9",
"prisma": "^6.2.1",
"ts-node": "^10.0.0",
@ -34,6 +35,8 @@
"koa": "^2.15.3",
"koa-body": "^6.0.1",
"koa-route": "^4.0.1",
"marked": "^15.0.6",
"nodemailer": "^6.9.16",
"npm": "^11.0.0",
"puppeteer": "^24.0.0"
}

View File

@ -7,6 +7,8 @@ import { readFileSync } from 'fs'
import dayjs from 'dayjs'
import handlebars from 'handlebars'
import puppeteer from 'puppeteer'
import nodemailer from 'nodemailer'
import marked from 'marked'
console.log = Debug('invoiceIssuer:func/issueInvoice.ts')
@ -102,8 +104,7 @@ export default async (prisma: PrismaClient, payerId: number, period: Date[], ite
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setContent(html)
await page.pdf({
path: `./dist/${invoiceNumber}.pdf`,
const invoiceFile = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
@ -113,5 +114,43 @@ export default async (prisma: PrismaClient, payerId: number, period: Date[], ite
left: '1cm'
}
})
const invoiceBuffer = Buffer.from(invoiceFile)
await browser.close()
// 发送邮件
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '465'),
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
})
const mailMarkdownTemplate = readFileSync('./src/resources/mail.md', 'utf-8')
const mailData = {
payerName: payerInfo.name,
periodStart: dayjs(period[0]).format('D, MMMM YYYY'),
periodEnd: dayjs(period[1]).format('D, MMMM YYYY'),
invoiceNumber: invoiceNumber,
invoiceAmount: `\$${newInvoice.getTotal().toFixed(2)}`,
payeeName: data.payee.name,
}
const mailRender = handlebars.compile(mailMarkdownTemplate)
const mailText = mailRender(mailData)
const mailOptions = {
from: process.env.SMTP_USER,
to: payerInfo.email,
subject: `Invoice ${invoiceNumber}`,
html: await marked.parse(mailText),
attachments: [
{
filename: `${invoiceNumber}.pdf`,
content: invoiceBuffer
}
]
}
await transporter.sendMail(mailOptions)
}

View File

@ -0,0 +1,12 @@
Dear {{payerName}},
I hope this email finds you well. Per our agreement, the invoice for {{periodStart}} ~ {{periodEnd}} is attached. Kindly review it at your earliest convenience and let me know if any questions or clarifications are required.
Details of the Invoice:
- Invoice Number: #{{invoiceNumber}}
- Amount: {{invoiceAmount}}
Thank you for your prompt attention to this matter. I appreciate your support and look forward to your confirmation.
Best regards,<br>{{payeeName}}