Some checks failed
CI / Lint, typecheck, test (push) Successful in 3m41s
CD / Build and push images (push) Successful in 3m44s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Failing after 2m53s
CI / Import/export fidelity gate (push) Has been skipped
New per-user digestFrequency (hourly default | daily | off) on the profile and in the settings UI. A scheduler job (15 min cadence) mails a user once their oldest unread, unmailed notification exceeds the cadence window: one localized mail per batch, grouped per pond then per page with actor names and change/comment counts, enqueued through the mail outbox. Sending marks the batch mailed — never read — and re-checks page read permission per entry at send time; entries the user can no longer read are dropped from the mail but still marked handled, so revoked content cannot queue forever. Every mail carries a signed, single-purpose unsubscribe link: it only flips the setting to off, renders a session-free confirmation page, and sets no cookie. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
31 lines
999 B
TypeScript
31 lines
999 B
TypeScript
import { Injectable } from '@nestjs/common';
|
|
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { MailTemplate, renderMail } from './mail-templates';
|
|
|
|
/**
|
|
* Enqueues transactional mail into the outbox. Actual delivery happens
|
|
* asynchronously in MailWorker — callers never wait on SMTP.
|
|
*/
|
|
@Injectable()
|
|
export class MailService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async enqueue(
|
|
to: string,
|
|
template: MailTemplate,
|
|
params: { displayName: string; link: string },
|
|
locale: 'de' | 'en',
|
|
): Promise<void> {
|
|
const rendered = renderMail(template, params, locale);
|
|
await this.enqueueRaw(to, rendered.subject, rendered.text, rendered.html);
|
|
}
|
|
|
|
/** Pre-rendered mails (the #95 digests build their own body). */
|
|
async enqueueRaw(to: string, subject: string, text: string, html: string): Promise<void> {
|
|
await this.prisma.mailOutbox.create({
|
|
data: { toAddress: to, subject, textBody: text, htmlBody: html },
|
|
});
|
|
}
|
|
}
|