Skip to main content

Next.js

The only thing to get right in Next.js is where the key lives. Everything else is a fetch.

Name the variable so it cannot leak

# .env.local
EPOSTIX_API_KEY=tix_live_...

Do not prefix it with NEXT_PUBLIC_. That prefix inlines the value into the client bundle, which would publish a credential that can send mail as your domain.

A route handler

// app/api/contact/route.ts
export async function POST(request: Request) {
const {email, message} = await request.json();

const response = await fetch('https://api.epostix.com/v1/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EPOSTIX_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `contact-${email}-${Date.now()}`,
},
body: JSON.stringify({
reply_to: email,
subject: `New enquiry from ${email}`,
text: message,
}),
});

if (!response.ok) {
const error = await response.json();
return Response.json({error: error.type}, {status: 502});
}

return Response.json({ok: true});
}

reply_to is the field that matters on a contact form. Send from your own verified domain and set the visitor's address as the reply target. Putting a visitor's address in from fails authentication at the receiving server and damages your reputation.

A server action

'use server';

export async function sendWelcome(userId: string, email: string) {
await fetch('https://api.epostix.com/v1/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EPOSTIX_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `user-${userId}-welcome`,
},
body: JSON.stringify({
to: [email],
subject: 'Welcome',
html: '<p>Glad you are here.</p>',
}),
});
}

The idempotency key is derived from the user id, so a double submit or a retried action sends one email rather than two.

Next