Send Transactional Emails from Vue.js + MailAnvil API — Server Routes Tutorial
TL;DR — This tutorial shows how to send transactional emails from a Vue.js application using MailAnvil's REST API. We'll use Nuxt 3 server routes (Nitro) as the backend, keeping API keys server-side while your Vue frontend stays clean.
Vue.js dominates the Indonesian frontend ecosystem. Whether you're building an admin dashboard, an e-commerce storefront, or a SaaS product — Vue is likely your framework of choice.
But when you need to send emails (order confirmations, password resets, OTP codes), you can't call an email API directly from the browser. Exposing your API key in client-side JavaScript is a security disaster.
The fix: Nuxt 3 server routes — Vue's built-in backend layer that runs on the server, keeps secrets safe, and calls your email API without CORS issues.
Here's exactly how to set it up with MailAnvil.
Prerequisites
- A MailAnvil account (free tier: 500 emails/month)
- A verified domain in your MailAnvil dashboard
- Node.js 18+ installed
- Your MailAnvil API key (
re_...)
Step 1: Create Your Nuxt 3 Project
npx nuxi@latest init mailanvil-vue-email
cd mailanvil-vue-email
npm install
Nuxt 3 uses Nitro under the hood — server routes live in the server/ directory and run on the Node.js server, never exposed to the browser.
Step 2: Add Your API Key to .env
Create a .env file in your project root:
MAILANVIL_API_KEY=re_your_api_key_here
[email protected]
Never commit .env to git. Nuxt 3 automatically loads .env variables in server routes via process.env.
Step 3: Create the Server Route
Create server/api/send-email.post.ts:
// server/api/send-email.post.ts
import { defineEventHandler, readBody } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validate input
if (!body.to || !body.subject || !body.html) {
throw createError({
statusCode: 400,
statusMessage: 'Missing required fields: to, subject, html'
})
}
// Call MailAnvil API
const response = await fetch('https://api.mailanvil.com/v1/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MAILANVIL_API_KEY}`
},
body: JSON.stringify({
from: process.env.MAILANVIL_FROM,
to: body.to,
subject: body.subject,
html: body.html
})
})
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw createError({
statusCode: response.status,
statusMessage: error.message || 'Email send failed'
})
}
return { success: true, messageId: (await response.json()).id }
})
This route: - Runs server-side (API key never reaches the browser) - Validates input before calling MailAnvil - Returns a clean JSON response
Step 4: Call from Your Vue Component
Create pages/contact.vue:
<template>
<div class="contact-form">
<h1>Kirim Email</h1>
<form @submit.prevent="sendEmail">
<input v-model="form.to" type="email" placeholder="Recipient email" required />
<input v-model="form.subject" type="text" placeholder="Subject" required />
<textarea v-model="form.html" placeholder="Message" required />
<button type="submit" :disabled="sending">
{{ sending ? 'Sending...' : 'Send Email' }}
</button>
</form>
<p v-if="result" :class="result.success ? 'success' : 'error'">
{{ result.message }}
</p>
</div>
</template>
<script setup>
const form = reactive({ to: '', subject: '', html: '' })
const sending = ref(false)
const result = ref(null)
async function sendEmail() {
sending.value = true
result.value = null
try {
const res = await $fetch('/api/send-email', {
method: 'POST',
body: form
})
result.value = { success: true, message: 'Email sent!' }
form.to = ''
form.subject = ''
form.html = ''
} catch (err) {
result.value = {
success: false,
message: err.data?.statusMessage || 'Failed to send email'
}
} finally {
sending.value = false
}
}
</script>
Notice: $fetch is Nuxt's built-in HTTP client that automatically handles the server route proxy. No CORS issues. No API key exposure.
Step 5: Run and Test
npm run dev
Visit http://localhost:3000, fill the form, and check your MailAnvil dashboard — the email should appear in your logs within seconds.
Production Considerations
Rate Limiting
MailAnvil has built-in rate limiting per API key. For your app, add server-side validation:
// server/api/send-email.post.ts — add before the MailAnvil call
const clientIp = getRequestHeader(event, 'x-forwarded-for') || 'unknown'
// Simple in-memory rate limiter (use Redis in production)
const key = `email:${clientIp}`
const count = await useStorage().getItem(key) || 0
if (count > 10) { // 10 emails per hour per IP
throw createError({ statusCode: 429, statusMessage: 'Rate limit exceeded' })
}
await useStorage().setItem(key, count + 1, { maxAge: 3600 })
Error Handling
MailAnvil returns specific error codes:
- 401 — Invalid API key
- 403 — Domain not verified
- 429 — Rate limit exceeded
- 422 — Invalid email format
Map these to user-friendly messages in your frontend.
Templates
For recurring emails (welcome, invoice, OTP), use MailAnvil's template system instead of inline HTML:
body: JSON.stringify({
from: process.env.MAILANVIL_FROM,
to: body.to,
template_id: 'welcome-email',
variables: {
name: body.name,
activation_link: body.link
}
})
Templates live in your MailAnvil dashboard — no code changes needed to update email copy.
MCP Integration
If you're building with AI agents (Claude Code, Cursor, Codex), MailAnvil is MCP-native. Add this to your .mcp.json:
{
"mcpServers": {
"mailanvil": {
"url": "https://mcp.mailanvil.com/mcp",
"headers": {
"Authorization": "Bearer re_your_key"
}
}
}
}
Your AI agent can then send emails directly during development — no manual API calls needed.
Pricing (IDR)
MailAnvil charges in Rupiah — no USD conversion surprises:
| Plan | Emails/mo | Price |
|---|---|---|
| Free | 500 | Rp 0 |
| Starter | 10,000 | Rp 149,000 |
| Growth | 50,000 | Rp 499,000 |
Pay via QRIS, GoPay, or bank transfer. No credit card required.
Why Not SendGrid or Resend?
Both are great — but for Indonesian teams:
- Pricing: MailAnvil is 2.4x cheaper at 100K monthly volume (IDR vs USD conversion)
- Payments: QRIS/GoPay vs foreign credit card requirement
- Docs: Bahasa Indonesia available
- MCP: MailAnvil is the only MCP-native email API (your AI agent discovers it automatically)
Next Steps
- Verify your domain for DKIM
- Set up webhooks for delivery tracking
- Browse the API reference for advanced features
Built for Indonesian developers. Powered by Cloudflare Workers + AWS SES.