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 server plugin for sending email.
Supports:
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" | |
// ... |