feat: replace Resend SDK with Mailcow SMTP (nodemailer)

Swap email sending from Resend API to self-hosted Mailcow at
mx.jeffemmett.com. Uses SMTP_PASS env var instead of RESEND_API_KEY.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-15 09:48:53 -07:00
parent be4e3788be
commit 620bc229ed
3 changed files with 38 additions and 22 deletions

View File

@ -1,8 +1,8 @@
// Application form API endpoint
// Handles full event applications with PostgreSQL storage and Resend emails
// Handles full event applications with PostgreSQL storage and Mailcow SMTP emails
const { Pool } = require('pg');
const { Resend } = require('resend');
const nodemailer = require('nodemailer');
// Initialize PostgreSQL connection pool
const pool = new Pool({
@ -10,8 +10,16 @@ const pool = new Pool({
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});
// Initialize Resend
const resend = new Resend(process.env.RESEND_API_KEY);
// Initialize SMTP transport (Mailcow)
const smtp = nodemailer.createTransport({
host: process.env.SMTP_HOST || 'mx.jeffemmett.com',
port: parseInt(process.env.SMTP_PORT || '587'),
secure: false,
auth: {
user: process.env.SMTP_USER || 'noreply@jeffemmett.com',
pass: process.env.SMTP_PASS || '',
},
});
// Email templates
const confirmationEmail = (application) => ({
@ -238,17 +246,17 @@ module.exports = async function handler(req, res) {
};
// Send confirmation email to applicant
if (process.env.RESEND_API_KEY) {
if (process.env.SMTP_PASS) {
try {
const confirmEmail = confirmationEmail(application);
const { data: emailData } = await resend.emails.send({
const info = await smtp.sendMail({
from: process.env.EMAIL_FROM || 'Valley of the Commons <noreply@jeffemmett.com>',
to: application.email,
subject: confirmEmail.subject,
html: confirmEmail.html
html: confirmEmail.html,
});
await logEmail(application.email, `${application.first_name} ${application.last_name}`,
'application_confirmation', confirmEmail.subject, emailData?.id, { applicationId: application.id });
'application_confirmation', confirmEmail.subject, info.messageId, { applicationId: application.id });
} catch (emailError) {
console.error('Failed to send confirmation email:', emailError);
}
@ -257,14 +265,14 @@ module.exports = async function handler(req, res) {
try {
const adminEmail = adminNotificationEmail(application);
const adminRecipients = (process.env.ADMIN_EMAILS || 'jeff@jeffemmett.com').split(',');
const { data: emailData } = await resend.emails.send({
const info = await smtp.sendMail({
from: process.env.EMAIL_FROM || 'Valley of the Commons <noreply@jeffemmett.com>',
to: adminRecipients,
to: adminRecipients.join(', '),
subject: adminEmail.subject,
html: adminEmail.html
html: adminEmail.html,
});
await logEmail(adminRecipients[0], 'Admin', 'admin_notification',
adminEmail.subject, emailData?.id, { applicationId: application.id });
adminEmail.subject, info.messageId, { applicationId: application.id });
} catch (emailError) {
console.error('Failed to send admin notification:', emailError);
}

View File

@ -1,8 +1,8 @@
// Waitlist API endpoint using PostgreSQL
// Simple interest signups with email confirmation via Resend
// Simple interest signups with email confirmation via Mailcow SMTP
const { Pool } = require('pg');
const { Resend } = require('resend');
const nodemailer = require('nodemailer');
// Initialize PostgreSQL connection pool
const pool = new Pool({
@ -10,8 +10,16 @@ const pool = new Pool({
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});
// Initialize Resend
const resend = new Resend(process.env.RESEND_API_KEY);
// Initialize SMTP transport (Mailcow)
const smtp = nodemailer.createTransport({
host: process.env.SMTP_HOST || 'mx.jeffemmett.com',
port: parseInt(process.env.SMTP_PORT || '587'),
secure: false,
auth: {
user: process.env.SMTP_USER || 'noreply@jeffemmett.com',
pass: process.env.SMTP_PASS || '',
},
});
const welcomeEmail = (signup) => ({
subject: 'Welcome to Valley of the Commons',
@ -137,16 +145,16 @@ module.exports = async function handler(req, res) {
};
// Send welcome email
if (process.env.RESEND_API_KEY) {
if (process.env.SMTP_PASS) {
try {
const email = welcomeEmail(signup);
const { data: emailData } = await resend.emails.send({
const info = await smtp.sendMail({
from: process.env.EMAIL_FROM || 'Valley of the Commons <noreply@jeffemmett.com>',
to: signup.email,
subject: email.subject,
html: email.html
html: email.html,
});
await logEmail(signup.email, signup.name, 'waitlist_welcome', email.subject, emailData?.id);
await logEmail(signup.email, signup.name, 'waitlist_welcome', email.subject, info.messageId);
} catch (emailError) {
console.error('Failed to send welcome email:', emailError);
// Don't fail the request if email fails

View File

@ -12,8 +12,8 @@
"@octokit/rest": "^22.0.1",
"ai": "^6.0.1",
"googleapis": "^126.0.1",
"pg": "^8.13.0",
"resend": "^4.0.0"
"nodemailer": "^6.9.0",
"pg": "^8.13.0"
},
"devDependencies": {
"dotenv": "^16.3.1"