If you're building anything serious in Nuxt 3, you'll need to send emails. Here's my setup that lets you switch between Resend, SendGrid, or any other provider without rewriting code.
The Setup
- AdonisJS Mail (I made it work outside AdonisJS so it works with any JS backend)
- Vue Email for templates
- Works with any email provider (Resend, AWS SES, Mailgun, Sparkpost, Brevo, Custom SMTP)
Basic Configuration
const generateMailer = async () => {
const mailer = await generateMailer({
default: "resend",
from: {
address: "hi@saas-boilerplate.dev",
name: "SaaS Boilerplate",
},
mailers: {
resend: transports.resend({
key: process.env.RESEND_API_KEY || "",
baseUrl: "https://api.resend.com",
}),
// Easy to add more providers!
sendgrid: transports.sendgrid({
key: process.env.SENDGRID_API_KEY || "",
})
},
});
return mailer;
};
Email Templates with Vue Email
Here's a simple magic link email template:
<template>
<Html>
<Body>
<Container>
<Heading>Sign In to Your Account</Heading>
<Text>Hi {{ username }},</Text>
<Text>Click the button below to sign in:</Text>
<Section>
<Button :href="signInLink">Sign In</Button>
</Section>
<Text>
Or copy this link:
<Link :href="signInLink">{{ signInLink }}</Link>
</Text>
<Hr />
<Text>This link expires in 1 hour.</Text>
</Container>
</Body>
</Html>
</template>
<script setup lang="ts">
import {
Body, Button, Container, Heading,
Html, Link, Section, Text, Hr
} from "@vue-email/components";
interface Props {
username?: string;
signInLink?: string;
}
withDefaults(defineProps<Props>(), {
username: "User",
signInLink: "https://app.example.com/sign-in",
});
</script>
Sending Emails
import MagicLinkSignIn from "~/emails/MagicLinkSignIn.vue";
const mailer = await getMailer();
await mailer.send(async (message) => {
message
.to(email)
.subject("Magic Link Sign In")
.html(
await render(MagicLinkSignIn, {
username: user?.name,
signInLink: url,
})
);
});
Cool Features from AdonisJS Mail
AdonisJS Mail comes with tons of powerful features that we can use:
- Multiple Transport Support: Switch between email providers with one line of code
- Calendar Events: Attach calendar invites to your emails
- File Attachments: Easily attach files, streams, or buffers
- HTML/Text Templates: Support for both HTML and plain text emails
For example, attaching a calendar invite is as simple as:
message.icalEvent((calendar) => {
calendar.createEvent({
summary: 'Team Meeting',
start: DateTime.local().plus({ minutes: 30 }),
end: DateTime.local().plus({ minutes: 60 }),
})
});
Why this works well
- Switch providers anytime
- Write templates in Vue
- Easy to test
- All the power of AdonisJS Mail in any JS backend
Try it yourself
Want to learn more about what AdonisJS Mail can do? Check out their detailed documentation.
This email setup is part of my Nuxt SaaS boilerplate.
If you're building a SaaS, check it out—it comes with type-safe APIs using tRPC, enterprise-grade RBAC (role-based access control), and production-ready features like auth, team management, and billing.
Every feature is built with the same attention to developer experience as this email system.