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:54 -07:00
parent c32070806a
commit c3e67b9cae
2 changed files with 18 additions and 10 deletions

View File

@ -19,8 +19,8 @@
"dependencies": {
"express": "^4.18.2",
"googleapis": "^144.0.0",
"pg": "^8.11.3",
"resend": "^4.0.0"
"nodemailer": "^6.9.0",
"pg": "^8.11.3"
},
"engines": {
"node": ">=18.0.0"

View File

@ -2,7 +2,7 @@ const express = require('express');
const fs = require('fs').promises;
const path = require('path');
const { google } = require('googleapis');
const { Resend } = require('resend');
const nodemailer = require('nodemailer');
const { Pool } = require('pg');
const app = express();
@ -10,8 +10,16 @@ const PORT = process.env.PORT || 3000;
const DATA_DIR = process.env.DATA_DIR || './data';
const REGISTRATIONS_FILE = path.join(DATA_DIR, 'registrations.json');
// Initialize Resend for emails
const resend = process.env.RESEND_API_KEY ? new Resend(process.env.RESEND_API_KEY) : null;
// Initialize SMTP transport (Mailcow)
const smtp = process.env.SMTP_PASS ? 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,
},
}) : null;
// Listmonk PostgreSQL configuration for newsletter management
const LISTMONK_LIST_ID = parseInt(process.env.LISTMONK_LIST_ID) || 20; // WORLDPLAY list
@ -109,8 +117,8 @@ async function appendToGoogleSheet(registration) {
// Send confirmation email
async function sendConfirmationEmail(registration) {
if (!resend) {
console.log('Resend not configured, skipping email...');
if (!smtp) {
console.log('SMTP not configured, skipping email...');
return;
}
@ -156,8 +164,8 @@ async function sendConfirmationEmail(registration) {
? registration.contribute.map(c => contributeLabels[c] || c).join(', ')
: 'Not specified';
await resend.emails.send({
from: 'WORLDPLAY <hello@worldplay.art>',
await smtp.sendMail({
from: process.env.EMAIL_FROM || 'WORLDPLAY <noreply@jeffemmett.com>',
to: registration.email,
subject: '🎭 Welcome to WORLDPLAY Registration Confirmed',
html: `
@ -513,7 +521,7 @@ ensureDataDir().then(() => {
console.log(`WORLDPLAY server running on port ${PORT}`);
console.log(`Admin token: ${process.env.ADMIN_TOKEN || 'worldplay-admin-2026'}`);
console.log(`Google Sheets: ${sheets ? 'enabled' : 'disabled'}`);
console.log(`Email notifications: ${resend ? 'enabled' : 'disabled'}`);
console.log(`Email notifications: ${smtp ? 'enabled (Mailcow SMTP)' : 'disabled (no SMTP_PASS)'}`);
console.log(`Listmonk newsletter sync: ${listmonkPool ? 'enabled' : 'disabled'} (list ID: ${LISTMONK_LIST_ID})`);
});
});