use nodemailer instead of sendgrid

This commit is contained in:
Thomas Ruoff
2022-12-22 23:00:44 +01:00
parent e0fe762c80
commit 28b3b03279
5 changed files with 130 additions and 211 deletions

View File

@@ -1,32 +1,46 @@
import { Booking } from '../db/booking'
import { getBaseURL } from '../helpers/url'
import { log } from '../helpers/log'
import { daysFormatFrontend } from './date'
import { generateCalendarEntry } from './ical'
import sgMail from '@sendgrid/mail'
import nodemailer from 'nodemailer';
const SENDGRID_API_KEY = process.env.SENDGRID_API_KEY
const ADMIN_EMAIL = process.env.ADMIN_EMAIL
const FROM_EMAIL = process.env.FROM_EMAIL
const smtpUser = process.env.SMTP_USER
const smtpPass = process.env.SMTP_PASS
const adminEmail = process.env.ADMIN_EMAIL
const fromEmail = process.env.FROM_EMAIL
if (!SENDGRID_API_KEY) {
throw new Error('NO SENDGRID_API_KEY set!')
if (!smtpUser) {
throw new Error('NO SMTP_USER set!')
}
if (!ADMIN_EMAIL) {
if (!smtpPass) {
throw new Error('NO SMTP_PASS set!')
}
if (!adminEmail) {
throw new Error('No ADMIN_EMAIL set!')
}
if (!FROM_EMAIL) {
if (!fromEmail) {
throw new Error('No FROM_EMAIL set!')
}
sgMail.setApiKey(SENDGRID_API_KEY)
let transporter = nodemailer.createTransport({
host: "wirtanen.uberspace.de",
port: 587,
secure: false,
auth: {
user: smtpUser,
pass: smtpPass,
},
logger: true,
});
const footer = `
Viele Grüße
--
Thomas Ruoff
Pfadi Bussle Wart
@@ -105,115 +119,85 @@ MfG`
export async function sendReceivedBookingAdminMail(
booking: Booking
): Promise<void> {
try {
await sendMail({
to: [{ email: ADMIN_EMAIL }],
from: { email: FROM_EMAIL, name: 'Pfadi-Bussle Wart' },
subject: `Buchung für ${booking.days} eingegangen!`,
textPlainContent: getReceivedBookingAdminText(booking),
})
} catch (error) {
log.error(`Failed in sendReceivedBookingMail for ${booking.uuid}`, error)
}
await sendMail({
to: [{ address: adminEmail, name: 'Pfadi-Bussle Wart' }],
from: { address: fromEmail, name: 'Pfadi-Bussle Wart' },
subject: `Buchung für ${booking.days} eingegangen!`,
text: getReceivedBookingAdminText(booking),
})
}
export async function sendReceivedBookingBookerMail(
booking: Booking
): Promise<void> {
try {
await sendMail({
to: [{ email: booking.email, name: booking.name }],
from: { email: FROM_EMAIL, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung ist eingegangen!`,
textPlainContent: getReceivedBookingBookerText(booking),
})
} catch (error) {
log.error(`Failed in sendReceivedBookingMail for ${booking.uuid}`, error)
}
await sendMail({
to: [{ address: booking.email, name: booking.name }],
from: { address: fromEmail, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung ist eingegangen!`,
text: getReceivedBookingBookerText(booking),
})
}
export async function sendBookingConfirmed(booking: Booking): Promise<void> {
try {
await sendMail({
to: [{ email: booking.email, name: booking.name }],
from: { email: FROM_EMAIL, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung wurde bestätigt!`,
textPlainContent: getBookingConfirmedText(booking),
attachments: [
{
content: Buffer.from(generateCalendarEntry(booking)).toString(
'base64'
),
type: 'text/calendar',
filename: 'pfadibussle-buchung.ics',
},
],
})
} catch (error) {
log.error(`Failed in sendBookingConfirmedMail for ${booking.uuid}`, error)
}
await sendMail({
to: [{ address: booking.email, name: booking.name }],
from: { address: fromEmail, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung wurde bestätigt!`,
text: getBookingConfirmedText(booking),
attachments: [
{
content: Buffer.from(generateCalendarEntry(booking)).toString(
'base64'
),
type: 'text/calendar',
filename: 'pfadibussle-buchung.ics',
},
],
})
}
export async function sendBookingRejected(booking: Booking): Promise<void> {
try {
await sendMail({
to: [{ email: booking.email, name: booking.name }],
from: { email: FROM_EMAIL, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung wurde abgelehnt!`,
textPlainContent: getBookingRejectedText(booking),
})
} catch (error) {
log.error(`Failed in sendBookingRejectedMail for ${booking.uuid}`, error)
}
await sendMail({
to: [{ address: booking.email, name: booking.name }],
from: { address: fromEmail, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung wurde abgelehnt!`,
text: getBookingRejectedText(booking),
})
}
export async function sendBookingCanceled(booking: Booking): Promise<void> {
try {
await sendMail({
to: [{ email: booking.email, name: booking.name }],
from: { email: FROM_EMAIL, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung wurde storniert!`,
textPlainContent: getBookingCanceledText(booking),
})
} catch (error) {
log.error(`Failed in sendBookingCanceledMail for ${booking.uuid}`, error)
}
await sendMail({
to: [{ address: booking.email, name: booking.name }],
from: { address: fromEmail, name: 'Pfadi-Bussle Wart' },
subject: `Deine Pfadi-Bussle Buchung wurde storniert!`,
text: getBookingCanceledText(booking),
})
}
async function sendMail({
to,
from,
subject,
textPlainContent,
text,
attachments,
}: {
to: { email: string; name?: string }[]
from: { email: string; name?: string }
to: { address: string; name: string }[]
from: { address: string; name: string }
subject: string
textPlainContent: string
text: string
attachments?: {
content: string
type?: string
filename: string
}[]
}): Promise<void> {
const data = {
// send mail with defined transport object
await transporter.sendMail({
to,
from,
subject,
text: textPlainContent,
text,
attachments,
}
try {
await sgMail.send(data)
} catch (error) {
log.error('Failed to send email', error)
if (error.response) {
log.error('Failed to send email', error.response.body)
}
// TODO: stuff into DB if failed and retry later
}
}
});
}

127
package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "0.1.0",
"dependencies": {
"@next-auth/mongodb-adapter": "1.1.1",
"@sendgrid/mail": "7.7.0",
"@vercel/analytics": "^0.1.4",
"autoprefixer": "10.4.13",
"classnames": "2.3.2",
@@ -22,13 +21,14 @@
"next-auth": "4.18.7",
"next-axiom": "0.16.0",
"next-mdx-remote": "4.2.0",
"nodemailer": "6.8.0",
"nodemailer": "^6.8.0",
"react": "18.2.0",
"react-calendar": "4.0.0",
"react-dom": "18.2.0",
"uuid": "9.0.0"
},
"devDependencies": {
"@types/nodemailer": "^6.4.6",
"@types/react": "18.0.26",
"@types/react-calendar": "3.9.0",
"@types/uuid": "8.3.4",
@@ -3047,41 +3047,6 @@
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
"dev": true
},
"node_modules/@sendgrid/client": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.7.0.tgz",
"integrity": "sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==",
"dependencies": {
"@sendgrid/helpers": "^7.7.0",
"axios": "^0.26.0"
},
"engines": {
"node": "6.* || 8.* || >=10.*"
}
},
"node_modules/@sendgrid/helpers": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.7.0.tgz",
"integrity": "sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==",
"dependencies": {
"deepmerge": "^4.2.2"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/@sendgrid/mail": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.7.0.tgz",
"integrity": "sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==",
"dependencies": {
"@sendgrid/client": "^7.7.0",
"@sendgrid/helpers": "^7.7.0"
},
"engines": {
"node": "6.* || 8.* || >=10.*"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.24.19",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.19.tgz",
@@ -3274,6 +3239,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz",
"integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g=="
},
"node_modules/@types/nodemailer": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz",
"integrity": "sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz",
@@ -3755,14 +3729,6 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
},
"node_modules/axobject-query": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
@@ -4463,6 +4429,7 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5667,25 +5634,6 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/fraction.js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
@@ -13911,32 +13859,6 @@
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
"dev": true
},
"@sendgrid/client": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.7.0.tgz",
"integrity": "sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==",
"requires": {
"@sendgrid/helpers": "^7.7.0",
"axios": "^0.26.0"
}
},
"@sendgrid/helpers": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.7.0.tgz",
"integrity": "sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==",
"requires": {
"deepmerge": "^4.2.2"
}
},
"@sendgrid/mail": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.7.0.tgz",
"integrity": "sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==",
"requires": {
"@sendgrid/client": "^7.7.0",
"@sendgrid/helpers": "^7.7.0"
}
},
"@sinclair/typebox": {
"version": "0.24.19",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.19.tgz",
@@ -14131,6 +14053,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz",
"integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g=="
},
"@types/nodemailer": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.6.tgz",
"integrity": "sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz",
@@ -14468,14 +14399,6 @@
"integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==",
"dev": true
},
"axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"requires": {
"follow-redirects": "^1.14.8"
}
},
"axobject-query": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
@@ -14947,7 +14870,8 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"define-lazy-prop": {
"version": "2.0.0",
@@ -15855,11 +15779,6 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
},
"fraction.js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",

View File

@@ -12,7 +12,6 @@
},
"dependencies": {
"@next-auth/mongodb-adapter": "1.1.1",
"@sendgrid/mail": "7.7.0",
"@vercel/analytics": "^0.1.4",
"autoprefixer": "10.4.13",
"classnames": "2.3.2",
@@ -25,13 +24,14 @@
"next-auth": "4.18.7",
"next-axiom": "0.16.0",
"next-mdx-remote": "4.2.0",
"nodemailer": "6.8.0",
"nodemailer": "^6.8.0",
"react": "18.2.0",
"react-calendar": "4.0.0",
"react-dom": "18.2.0",
"uuid": "9.0.0"
},
"devDependencies": {
"@types/nodemailer": "^6.4.6",
"@types/react": "18.0.26",
"@types/react-calendar": "3.9.0",
"@types/uuid": "8.3.4",
@@ -52,4 +52,4 @@
"^.+\\.(ts|tsx)$": "ts-jest"
}
}
}
}

View File

@@ -63,12 +63,16 @@ export default async function userHandler(
const { current, previous } = await patchBooking(uuid, req.body)
// TODO: this should really go into the schema
if (wasRejected(previous, current)) {
sendBookingRejected(current)
} else if (wasConfirmed(previous, current)) {
sendBookingConfirmed(current)
} else if (wasCanceled(previous, current)) {
sendBookingCanceled(current)
try {
if (wasRejected(previous, current)) {
sendBookingRejected(current)
} else if (wasConfirmed(previous, current)) {
sendBookingConfirmed(current)
} else if (wasCanceled(previous, current)) {
sendBookingCanceled(current)
}
} catch (error) {
log.error(`Failed to send booking ${current} for booking ${uuid}`)
}
res.status(200).json(current)
@@ -81,4 +85,4 @@ export default async function userHandler(
res.setHeader('Allow', ['PATCH'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
}

View File

@@ -34,17 +34,29 @@ export default async function userHandler(
`received booking ${booking.uuid} from ${booking.email}`,
booking
)
await sendReceivedBookingAdminMail(booking)
log.info(`send booking ${booking.uuid} received to admin`, booking)
await sendReceivedBookingBookerMail(booking)
log.info(
`send booking ${booking.uuid} received to {booking.email}`,
booking
)
try {
await sendReceivedBookingAdminMail(booking)
log.info(`send booking ${booking.uuid} received to admin`, booking)
} catch (error) {
log.error(`failed to send booking ${booking.uuid} received to admin`, booking)
}
try {
await sendReceivedBookingBookerMail(booking)
log.info(
`send booking ${booking.uuid} received to {booking.email}`,
booking
)
} catch (error) {
log.error(
`failed to send booking ${booking.uuid} received to {booking.email}`,
booking
)
}
res.status(200).json(booking)
break
default:
res.setHeader('Allow', ['POST'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
}