Replace Resend with Mailcow SMTP via nodemailer

- Swap resend SDK for nodemailer in contact form API route
- Update docker-compose.yml env vars: RESEND_API_KEY → SMTP_*
- Update package.json: resend → nodemailer dependency
- Remove Vercel deploy boilerplate from README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-15 16:07:20 -07:00
parent d497c5735e
commit 4febc3b11d
4 changed files with 19 additions and 18 deletions

View File

@ -28,9 +28,3 @@ To learn more about Next.js, take a look at the following resources:
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

View File

@ -4,7 +4,11 @@ services:
container_name: xhivart-mirror
restart: always
environment:
- RESEND_API_KEY=${RESEND_API_KEY}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- SMTP_FROM=${SMTP_FROM}
networks:
- traefik-public
labels:

View File

@ -12,7 +12,7 @@
"next": "16.1.4",
"react": "19.2.3",
"react-dom": "19.2.3",
"resend": "^6.9.1"
"nodemailer": "^6.9.16"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",

View File

@ -1,7 +1,15 @@
import { NextResponse } from 'next/server';
import { Resend } from 'resend';
import nodemailer from 'nodemailer';
const resend = new Resend(process.env.RESEND_API_KEY);
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT) || 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
function escapeHtml(str: string): string {
return str
@ -30,8 +38,8 @@ export async function POST(request: Request) {
const safeEmail = escapeHtml(email.trim());
const safeMessage = escapeHtml(message.trim());
const { error } = await resend.emails.send({
from: 'XHIVA Art <noreply@jeffemmett.com>',
await transporter.sendMail({
from: `XHIVA Art <${process.env.SMTP_FROM || process.env.SMTP_USER}>`,
to: 'xhivart@gmail.com',
replyTo: email.trim(),
subject: `New message from ${name.trim()} — XHIVA Art`,
@ -65,14 +73,9 @@ export async function POST(request: Request) {
`,
});
if (error) {
console.error('Resend error:', error);
return NextResponse.json({ error: 'Failed to send message' }, { status: 500 });
}
return NextResponse.json({ success: true });
} catch (err) {
console.error('Contact API error:', err);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
return NextResponse.json({ error: 'Failed to send message' }, { status: 500 });
}
}