feat(backend): mail module + send personal Gemeinde-Teamer invites

New global mail/ module mirroring the files/storage/ provider pattern:
- MailProvider abstraction; default LogMailProvider only logs (no delivery),
  MAIL_PROVIDER=smtp switches to a nodemailer SMTP transport (SMTP_*,
  MAIL_FROM).
- MailService.sendTeamerInvite() composes the invite email with a link
  built from APP_BASE_URL.

TeamerService.createInvite() now mails personal invites (those with an
email) best-effort and returns `emailSent`; group links are unchanged.
Delivery failures are logged and swallowed, never blocking invite creation.

New env: APP_BASE_URL, MAIL_PROVIDER, MAIL_FROM, SMTP_HOST/PORT/SECURE/
USER/PASS. Tests: teamer spec covers mail-on-personal-invite,
no-mail-on-group-link, and transport-drop; npm test green at 56. Docs
updated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 08:17:53 +02:00
co-authored by Claude Sonnet 5
parent eb6f64a0c5
commit da76f8dc96
12 changed files with 243 additions and 13 deletions
+2
View File
@@ -3,6 +3,7 @@ import { ConfigModule } from '@nestjs/config';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { PrismaModule } from './prisma/prisma.module';
import { MailModule } from './mail/mail.module';
import { AuthModule } from './auth/auth.module';
import { KcModule } from './kc/kc.module';
import { GemeindeModule } from './gemeinde/gemeinde.module';
@@ -23,6 +24,7 @@ import { SyncModule } from './sync/sync.module';
exclude: ['/api*'],
}),
PrismaModule,
MailModule,
SyncModule,
AuthModule,
KcModule,