refactor(mana-auth): route emails through mana-notify instead of Nodemailer

Replace direct Brevo SMTP sending with HTTP calls to mana-notify's
notification API. This centralizes all email configuration in one
service (mana-notify) and removes the nodemailer dependency from
mana-auth. SMTP provider is now swappable via a single env var.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-03 15:01:27 +02:00
parent 62d9eb1f2b
commit b2adaaa30e
5 changed files with 26 additions and 48 deletions

View file

@ -271,10 +271,7 @@ services:
MANA_SUBSCRIPTIONS_URL: http://mana-subscriptions:3063
SYNC_DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_platform
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-${JWT_SECRET:-your-jwt-secret-change-me}}
SMTP_HOST: smtp-relay.brevo.com
SMTP_PORT: 587
SMTP_USER: ${SMTP_USER:-94cde5002@smtp-brevo.com}
SMTP_PASS: ${SMTP_PASSWORD}
MANA_NOTIFY_URL: http://mana-notify:3013
SYNAPSE_OIDC_CLIENT_SECRET: ${SYNAPSE_OIDC_CLIENT_SECRET:-}
MAX_DAILY_SIGNUPS: ${MAX_DAILY_SIGNUPS:-0}
CORS_ORIGINS: https://mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://docs.mana.how,https://element.mana.how,https://inventar.mana.how,https://link.mana.how,https://cards.mana.how,https://matrix.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://playground.mana.how,https://presi.mana.how,https://questions.mana.how,https://skilltree.mana.how,https://storage.mana.how,https://times.mana.how,https://todo.mana.how,https://traces.mana.how,https://zitare.mana.how

View file

@ -16,12 +16,10 @@
"drizzle-orm": "^0.38.3",
"postgres": "^3.4.5",
"jose": "^6.1.2",
"nodemailer": "^7.0.12",
"bcryptjs": "^3.0.2",
"zod": "^3.24.0"
},
"devDependencies": {
"@types/nodemailer": "^6.4.17",
"@types/bcryptjs": "^2.4.6",
"drizzle-kit": "^0.30.4",
"typescript": "^5.9.3"

View file

@ -7,12 +7,7 @@ export interface Config {
nodeEnv: string;
serviceKey: string;
cors: { origins: string[] };
smtp: {
host: string;
port: number;
user: string;
pass: string;
};
manaNotifyUrl: string;
manaCreditsUrl: string;
manaSubscriptionsUrl: string;
synapseOidcClientSecret: string;
@ -35,12 +30,7 @@ export function loadConfig(): Config {
nodeEnv: env('NODE_ENV', 'development'),
serviceKey: env('MANA_CORE_SERVICE_KEY', 'dev-service-key'),
cors: { origins: env('CORS_ORIGINS', 'http://localhost:5173').split(',') },
smtp: {
host: env('SMTP_HOST', 'smtp-relay.brevo.com'),
port: parseInt(env('SMTP_PORT', '587'), 10),
user: env('SMTP_USER'),
pass: env('SMTP_PASS'),
},
manaNotifyUrl: env('MANA_NOTIFY_URL', 'http://localhost:3013'),
manaCreditsUrl: env('MANA_CREDITS_URL', 'http://localhost:3061'),
manaSubscriptionsUrl: env('MANA_SUBSCRIPTIONS_URL', 'http://localhost:3063'),
synapseOidcClientSecret: env('SYNAPSE_OIDC_CLIENT_SECRET'),

View file

@ -1,40 +1,35 @@
/**
* Email sending functions using nodemailer.
* German language emails with Brevo SMTP.
* Email sending via mana-notify service.
* All emails are routed through the central notification service
* which handles SMTP, retries, and queuing.
*/
import nodemailer from 'nodemailer';
let transporter: nodemailer.Transporter | null = null;
export function initializeEmail(smtp: { host: string; port: number; user: string; pass: string }) {
if (!smtp.host || !smtp.user) {
console.warn('SMTP not configured — emails will be logged to console');
return;
}
transporter = nodemailer.createTransport({
host: smtp.host,
port: smtp.port,
secure: false,
auth: { user: smtp.user, pass: smtp.pass },
});
}
const NOTIFY_URL = process.env.MANA_NOTIFY_URL || 'http://localhost:3013';
const SERVICE_KEY = process.env.MANA_CORE_SERVICE_KEY || 'dev-service-key';
async function send(to: string, subject: string, html: string): Promise<boolean> {
if (!transporter) {
console.log(`[EMAIL] To: ${to} | Subject: ${subject}`);
return true;
}
try {
await transporter.sendMail({
from: '"ManaCore" <noreply@mana.how>',
to,
subject,
html,
const res = await fetch(`${NOTIFY_URL}/api/v1/notifications/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Service-Key': SERVICE_KEY,
},
body: JSON.stringify({
channel: 'email',
appId: 'mana-auth',
recipient: to,
subject,
body: html,
}),
});
if (!res.ok) {
console.error('mana-notify error:', res.status, await res.text());
return false;
}
return true;
} catch (error) {
console.error('Failed to send email:', error);
console.error('Failed to send via mana-notify:', error);
return false;
}
}

View file

@ -13,7 +13,6 @@ import { createBetterAuth } from './auth/better-auth.config';
import { errorHandler } from './middleware/error-handler';
import { jwtAuth } from './middleware/jwt-auth';
import { serviceAuth } from './middleware/service-auth';
import { initializeEmail } from './email/send';
import { SecurityEventsService, AccountLockoutService } from './services/security';
import { SignupLimitService } from './services/signup-limit';
import { ApiKeysService } from './services/api-keys';
@ -31,7 +30,6 @@ const db = getDb(config.databaseUrl);
const auth = createBetterAuth(config.databaseUrl);
// Initialize services
initializeEmail(config.smtp);
const security = new SecurityEventsService(db);
const lockout = new AccountLockoutService(db);
const signupLimit = new SignupLimitService(db);