Files
tessera-ctl/apps/api/src/mail/mail.service.ts
T
schalli d9f1422d6f fix(api): Umlaute in Passwort-Mail und Modulbeschreibung
Korrigiert die deutschen Zeichenketten in beiden Mails aus
mail.service.ts (Betreff, Passwort-Reset-Fliesstext,
Willkommens-Mail) sowie die domaincheck-Modulbeschreibung in
domaincheck.seed.ts auf echte Umlaute. Wortlaut identisch, nur
Zeichen korrigiert; die englischen Zweige bleiben unveraendert.

seedModule() ist ein upsert mit update-Zweig und laeuft in
onModuleInit — ein API-Neustart schreibt die korrigierte
Beschreibung ueber bestehende Datenbankzeilen, ohne Migration oder
Backfill.

pnpm --filter @tessera/api run build: sauber.
pnpm --filter @tessera/api run type-check: sauber.
pnpm --filter @tessera/api run test: 46 Testdateien, 642 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:07:38 +02:00

135 lines
3.7 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MailerService } from '@nestjs-modules/mailer';
@Injectable()
export class MailService {
private readonly logger = new Logger(MailService.name);
private readonly appUrl: string;
constructor(
private mailerService: MailerService,
private configService: ConfigService,
) {
this.appUrl = this.configService.get<string>(
'TESSERA_APP_URL',
'http://localhost:3000',
);
}
/**
* Send a password reset email with a time-limited token link.
* T-02-12: The caller always returns 200 regardless of whether this succeeds
* (no email enumeration).
*/
async sendPasswordResetEmail(
email: string,
token: string,
locale: string = 'de',
): Promise<void> {
const resetLink = `${this.appUrl}/reset-password/${token}`;
const isGerman = locale === 'de';
const subject = isGerman
? 'Passwort zurücksetzen - Tessera'
: 'Reset your password - Tessera';
const text = isGerman
? [
'Hallo,',
'',
'Sie haben eine Passwortzurücksetzung für Ihren Tessera-Account angefordert.',
'',
`Klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen:`,
resetLink,
'',
'Dieser Link ist 1 Stunde gültig und kann nur einmal verwendet werden.',
'',
'Falls Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren.',
'',
'Mit freundlichen Grüßen,',
'Ihr Tessera-Team',
].join('\n')
: [
'Hello,',
'',
'You have requested a password reset for your Tessera account.',
'',
'Click the following link to reset your password:',
resetLink,
'',
'This link is valid for 1 hour and can only be used once.',
'',
'If you did not request this, you can safely ignore this email.',
'',
'Best regards,',
'The Tessera Team',
].join('\n');
try {
await this.mailerService.sendMail({
to: email,
subject,
text,
});
this.logger.log(`Password reset email sent to ${email}`);
} catch (error) {
// Log but don't throw -- caller returns 200 regardless (T-02-12)
this.logger.error(
`Failed to send password reset email to ${email}`,
error instanceof Error ? error.stack : String(error),
);
}
}
/**
* Send a welcome email to a newly created user (optional).
*/
async sendWelcomeEmail(
email: string,
username: string,
locale: string = 'de',
): Promise<void> {
const isGerman = locale === 'de';
const subject = isGerman
? 'Willkommen bei Tessera'
: 'Welcome to Tessera';
const text = isGerman
? [
`Hallo ${username},`,
'',
'Ihr Tessera-Account wurde erstellt.',
'',
`Sie können sich unter ${this.appUrl}/login anmelden.`,
'',
'Mit freundlichen Grüßen,',
'Ihr Tessera-Team',
].join('\n')
: [
`Hello ${username},`,
'',
'Your Tessera account has been created.',
'',
`You can sign in at ${this.appUrl}/login.`,
'',
'Best regards,',
'The Tessera Team',
].join('\n');
try {
await this.mailerService.sendMail({
to: email,
subject,
text,
});
this.logger.log(`Welcome email sent to ${email}`);
} catch (error) {
this.logger.error(
`Failed to send welcome email to ${email}`,
error instanceof Error ? error.stack : String(error),
);
}
}
}