apps/recallassess/recallassess-api/src/api/shared/dynamic-mail-fields/dynamic-mail-fields.service.ts
Dynamic Mail Fields Service
Provides template variables for email templates from Admin Settings (SystemSetting). Values can be configured in Admin PWA under System Settings.
Supported variables:
FRONTEND_URL, else default)
Properties |
|
Methods |
|
constructor(prisma: BNestPrismaService)
|
||||||
|
Parameters :
|
| Private Async getCachedSettings |
getCachedSettings()
|
|
Returns :
Promise<literal type>
|
| Private getDomainFromEnvironment |
getDomainFromEnvironment()
|
|
Returns :
string | null
|
| invalidateCache |
invalidateCache()
|
|
Invalidate cache (call after admin updates settings)
Returns :
void
|
| Private cache |
Type : literal type | null
|
Default value : null
|
| Private Readonly CACHE_TTL_MS |
Type : number
|
Default value : 60_000
|
| Private Readonly logger |
Type : unknown
|
Default value : new Logger(DynamicMailFieldsService.name)
|
import { requireEnv } from "@bish-nest/core";
import { BNestPrismaService } from "@bish-nest/core/services";
import { Injectable, Logger } from "@nestjs/common";
/**
* SystemSetting keys for dynamic mail fields (configurable via Admin Settings)
*/
export const DYNAMIC_MAIL_SETTING_KEYS = {
COMPANY_NAME: "mail.company_name",
DOMAIN_NAME: "mail.domain_name",
FOOTER_TEXT: "mail.footer_text",
CONTACT_EMAIL: "mail.contact_email",
} as const;
/**
* Default values when SystemSetting is not configured
*/
const DEFAULTS = {
COMPANY_NAME: "Recall",
DOMAIN_NAME: "recallsolutions.ai",
FOOTER_TEXT: "Development Through Action and Reflection",
CONTACT_EMAIL: "enquiries@recallsolutions.ai",
} as const;
/**
* Dynamic Mail Fields Service
*
* Provides template variables for email templates from Admin Settings (SystemSetting).
* Values can be configured in Admin PWA under System Settings.
*
* Supported variables:
* - mail.current_year: Auto-updated from system date
* - mail.company_name: From admin settings (fallback: company from context or default)
* - mail.domain_name: From admin settings (fallback: hostname from required `FRONTEND_URL`, else default)
* - mail.footer_text: From admin settings
*/
@Injectable()
export class DynamicMailFieldsService {
private readonly logger = new Logger(DynamicMailFieldsService.name);
private cache: {
companyName: string | null;
domainName: string | null;
footerText: string | null;
contactEmail: string | null;
fetchedAt: Date;
} | null = null;
private readonly CACHE_TTL_MS = 60_000; // 1 minute
constructor(private readonly prisma: BNestPrismaService) {}
/**
* Get dynamic mail variables for template replacement.
* Use these with {{variable.name}} placeholders in email templates.
*
* @param options.companyNameFallback - Fallback when setting not configured (e.g. Company.name from account)
*/
async getDynamicMailVariables(options?: {
companyNameFallback?: string;
}): Promise<Record<string, string>> {
const settings = await this.getCachedSettings();
const domainFromEnv = this.getDomainFromEnvironment();
const companyName =
settings.companyName ||
options?.companyNameFallback ||
DEFAULTS.COMPANY_NAME;
const domainName = settings.domainName || domainFromEnv || DEFAULTS.DOMAIN_NAME;
const footerText = settings.footerText || DEFAULTS.FOOTER_TEXT;
const currentYear = new Date().getFullYear().toString();
const domainUrl = domainName.startsWith("http")
? domainName
: `https://${domainName}`;
const domainHost = domainName.replace(/^https?:\/\//, "").split("/")[0] || domainName;
const contactEmail =
settings.contactEmail || `enquiries@${domainHost}` || DEFAULTS.CONTACT_EMAIL;
return {
"mail.current_year": currentYear,
"mail.company_name": companyName,
"mail.domain_name": domainName,
"mail.footer_text": footerText,
"mail.domain_url": domainUrl,
"mail.contact_email": contactEmail,
};
}
/**
* Invalidate cache (call after admin updates settings)
*/
invalidateCache(): void {
this.cache = null;
}
private async getCachedSettings(): Promise<{
companyName: string | null;
domainName: string | null;
footerText: string | null;
contactEmail: string | null;
}> {
const now = new Date();
if (
this.cache &&
now.getTime() - this.cache.fetchedAt.getTime() < this.CACHE_TTL_MS
) {
return this.cache;
}
const settings = await this.prisma.client.systemSetting.findMany({
where: {
key: {
in: [
DYNAMIC_MAIL_SETTING_KEYS.COMPANY_NAME,
DYNAMIC_MAIL_SETTING_KEYS.DOMAIN_NAME,
DYNAMIC_MAIL_SETTING_KEYS.FOOTER_TEXT,
DYNAMIC_MAIL_SETTING_KEYS.CONTACT_EMAIL,
],
},
},
});
const byKey = Object.fromEntries(
settings.map((s) => [s.key, s.value?.trim() || ""])
);
const companyName =
byKey[DYNAMIC_MAIL_SETTING_KEYS.COMPANY_NAME] || null;
const domainName = byKey[DYNAMIC_MAIL_SETTING_KEYS.DOMAIN_NAME] || null;
const footerText = byKey[DYNAMIC_MAIL_SETTING_KEYS.FOOTER_TEXT] || null;
const contactEmail = byKey[DYNAMIC_MAIL_SETTING_KEYS.CONTACT_EMAIL] || null;
this.cache = {
companyName: companyName || null,
domainName: domainName || null,
footerText: footerText || null,
contactEmail: contactEmail || null,
fetchedAt: now,
};
return this.cache;
}
private getDomainFromEnvironment(): string | null {
const url = requireEnv("FRONTEND_URL");
try {
const parsed = new URL(url);
return parsed.hostname || null;
} catch {
return null;
}
}
}