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 // 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 { Pool } = require('pg');
const { Resend } = require('resend'); const nodemailer = require('nodemailer');
// Initialize PostgreSQL connection pool // Initialize PostgreSQL connection pool
const pool = new Pool({ const pool = new Pool({
@ -10,8 +10,16 @@ const pool = new Pool({
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
}); });
// Initialize Resend // Initialize SMTP transport (Mailcow)
const resend = new Resend(process.env.RESEND_API_KEY); 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 // Email templates
const confirmationEmail = (application) => ({ const confirmationEmail = (application) => ({
@ -238,17 +246,17 @@ module.exports = async function handler(req, res) {
}; };
// Send confirmation email to applicant // Send confirmation email to applicant
if (process.env.RESEND_API_KEY) { if (process.env.SMTP_PASS) {
try { try {
const confirmEmail = confirmationEmail(application); 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>', from: process.env.EMAIL_FROM || 'Valley of the Commons <noreply@jeffemmett.com>',
to: application.email, to: application.email,
subject: confirmEmail.subject, subject: confirmEmail.subject,
html: confirmEmail.html html: confirmEmail.html,
}); });
await logEmail(application.email, `${application.first_name} ${application.last_name}`, 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) { } catch (emailError) {
console.error('Failed to send confirmation email:', emailError); console.error('Failed to send confirmation email:', emailError);
} }
@ -257,14 +265,14 @@ module.exports = async function handler(req, res) {
try { try {
const adminEmail = adminNotificationEmail(application); const adminEmail = adminNotificationEmail(application);
const adminRecipients = (process.env.ADMIN_EMAILS || 'jeff@jeffemmett.com').split(','); 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>', from: process.env.EMAIL_FROM || 'Valley of the Commons <noreply@jeffemmett.com>',
to: adminRecipients, to: adminRecipients.join(', '),
subject: adminEmail.subject, subject: adminEmail.subject,
html: adminEmail.html html: adminEmail.html,
}); });
await logEmail(adminRecipients[0], 'Admin', 'admin_notification', await logEmail(adminRecipients[0], 'Admin', 'admin_notification',
adminEmail.subject, emailData?.id, { applicationId: application.id }); adminEmail.subject, info.messageId, { applicationId: application.id });
} catch (emailError) { } catch (emailError) {
console.error('Failed to send admin notification:', emailError); console.error('Failed to send admin notification:', emailError);
} }

View File

@ -1,8 +1,8 @@
// Waitlist API endpoint using PostgreSQL // 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 { Pool } = require('pg');
const { Resend } = require('resend'); const nodemailer = require('nodemailer');
// Initialize PostgreSQL connection pool // Initialize PostgreSQL connection pool
const pool = new Pool({ const pool = new Pool({
@ -10,8 +10,16 @@ const pool = new Pool({
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
}); });
// Initialize Resend // Initialize SMTP transport (Mailcow)
const resend = new Resend(process.env.RESEND_API_KEY); 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) => ({ const welcomeEmail = (signup) => ({
subject: 'Welcome to Valley of the Commons', subject: 'Welcome to Valley of the Commons',
@ -137,16 +145,16 @@ module.exports = async function handler(req, res) {
}; };
// Send welcome email // Send welcome email
if (process.env.RESEND_API_KEY) { if (process.env.SMTP_PASS) {
try { try {
const email = welcomeEmail(signup); 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>', from: process.env.EMAIL_FROM || 'Valley of the Commons <noreply@jeffemmett.com>',
to: signup.email, to: signup.email,
subject: email.subject, 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) { } catch (emailError) {
console.error('Failed to send welcome email:', emailError); console.error('Failed to send welcome email:', emailError);
// Don't fail the request if email fails // Don't fail the request if email fails

View File

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