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 24f8070b8a
commit 320a41142b
13 changed files with 250 additions and 18 deletions
+2
View File
@@ -39,6 +39,7 @@
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"multer": "^2.0.1",
"nodemailer": "^7.0.13",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
@@ -56,6 +57,7 @@
"@types/jsonwebtoken": "^9.0.7",
"@types/multer": "^1.4.12",
"@types/node": "^20.17.9",
"@types/nodemailer": "^6.4.24",
"@types/passport": "^1.0.17",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.2",