Skip to content

Instantly share code, notes, and snippets.

@kalnode
Last active May 9, 2025 18:51
Show Gist options
  • Save kalnode/459d9e02a2e0abfd7e3280448f3d3dee to your computer and use it in GitHub Desktop.
Save kalnode/459d9e02a2e0abfd7e3280448f3d3dee to your computer and use it in GitHub Desktop.
Nuxt server plugin for sending emails, supporting nodemailer (general) & worker-mailer (Cloudflare prod)

Nuxt server plugin for sending email.

Supports:

  • nodemailer (local & most prod env's)
  • worker-mailer (Cloudflare).

Exclude worker-mailer stuff if you're never deploying to Cloudflare.

NUXT_MAIL_SMTP = 'smtp.gmail.com'
NUXT_MAIL_PORT = 465
NUXT_MAIL_USERNAME = '[email protected]'
NUXT_MAIL_PASSWORD = 'xxxx xxxx xxxx xxxx'
NUXT_MAIL_TARGET = '[email protected]'
NUXT_MAIL_USE_WMAILER = false # set true when deploying to cloudflare
// FILE: server/api/mailer/index.post.ts
// For now, direct custom type import
import { Mail } from '~~/types/mail'
export default defineEventHandler(async (event) => {
try {
if ( process.env.NUXT_MAIL_TARGET
&& process.env.NUXT_MAIL_USERNAME
&& process.env.NUXT_MAIL_PASSWORD
&& process.env.NUXT_MAIL_SMTP
&& process.env.NUXT_MAIL_PORT) {
const body = await readBody(event)
const finalBody:Mail = { ...body, from: process.env.NUXT_MAIL_USERNAME, to: process.env.NUXT_MAIL_TARGET }
// CLOUDFLARE (use Worker-Mailer)
if (process.env.NUXT_MAIL_USE_WMAILER === 'true') {
const { mail_WorkerMailer } = await import('~~/server/utils/mail/mail_workermailer')
return await mail_WorkerMailer(finalBody)
.then( (response) => {
return { message: "Mail successfully sent" }
})
.catch( (error) => {
throw 'Mail failure: worker-mailer'
})
// ALL OTHER CASES (use Nodemailer)
} else {
const { mail_NodeMailer } = await import('~~/server/utils/mail/mail_nodemailer')
return await mail_NodeMailer(finalBody)
.then( (response) => {
return { message: "Mail successfully sent" }
})
.catch( (error) => {
throw 'Mail failure: nodemailer'
})
}
} else {
throw 'Env values missing'
}
} catch (error) {
console.error("Nuxt Server API mailer - Error:", error)
throw createError({ statusCode: 400, statusMessage: 'Mail failure: see server logs' })
}
})
// FILE: types/mail.d.ts
export type Mail = {
from: string
to: string
replyTo: string
reply: string
subject: string
text: string
html: html
}
// FILE: server/utils/mail/mail_nodemailer.ts
// For now, direct custom type import
import { Mail } from '~~/types/mail'
export const mail_NodeMailer = async (payload:Mail) => {
try {
const nodemailer = await import('nodemailer')
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.NUXT_MAIL_USERNAME,
pass: process.env.NUXT_MAIL_PASSWORD
}
})
return await transporter.sendMail(payload, (error, info) => {
if (error) {
console.error("ERROR - mail_NodeMailer", error)
throw error
} else {
return info
}
})
} catch (error) {
throw "Problem with sending nodemailer"
}
}
// FILE: server/utils/mail/mail_workermailer.ts
// For now, direct custom type import
import { Mail } from '~~/types/mail'
export const mail_WorkerMailer = async (payload:Mail) => {
try {
if ( process.env.NUXT_MAIL_TARGET
&& process.env.NUXT_MAIL_USERNAME
&& process.env.NUXT_MAIL_PASSWORD
&& process.env.NUXT_MAIL_SMTP
&& process.env.NUXT_MAIL_PORT) {
const { WorkerMailer } = await import('worker-mailer')
const mailer = await WorkerMailer.connect({
credentials: {
username: process.env.NUXT_MAIL_USERNAME,
password: process.env.NUXT_MAIL_PASSWORD,
},
authType: 'plain',
host: process.env.NUXT_MAIL_SMTP,
port: parseInt(process.env.NUXT_MAIL_PORT!),
secure: true,
})
return await mailer.send(payload)
} else {
throw 'Env variables not found'
}
} catch (error) {
console.error("ERROR - mail_WorkerMailer", error)
throw "Problem with sending worker-mailer"
}
}
// FILE: app/components/myvuecomponent.vue
// ...
// NOTE, gmail smtp:
// FAILS: "John Doe <[email protected]>"
// WORKS: "John Doe [email protected]"
const fromFinal = `${event.data.name} ${event.data.email}`
const payloadFinal:Partial<Mail> = {
// For now, specifying different "reply" keys; relevant services ignore the other
// Nodemailer
replyTo: fromFinal,
// Cloudflare/worker-mailer
reply: fromFinal,
subject: event.data.subject,
text: event.data.message // In theory, could also do "html: htmlcontent"
}
await $fetch('/api/mailer', {
method: 'POST',
body: payloadFinal
})
.then( (response) => {
console.log("Mailer api response is:", response)
})
.catch( (error:any) => {
console.error("ERROR: Message submission", error)
})
// nuxt.config.ts
//...
modules: ['@nuxt/content', '@nuxt/ui', "@nuxt/image", '@pinia/nuxt', '@nuxthub/core' ],
// ...
// ...
"nodemailer": "^6.10.0",
"worker-mailer": "^1.1.1"
// ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment