feat: add email functionality for invoices using Nodemailer and Markdown template
This commit is contained in:
parent
feee4184a4
commit
64578233f0
34
backend/package-lock.json
generated
34
backend/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
12
backend/src/resources/mail.md
Normal file
12
backend/src/resources/mail.md
Normal 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}}
|
Loading…
Reference in New Issue
Block a user