logger)
{
_emailService = emailService;
_platformSettings = platformSettings;
_logger = logger;
}
///
/// Sends an email notification to all configured SuperAdmin addresses when a new tenant
/// company completes registration, including company ID, plan name, and primary contact.
///
/// Database ID of the newly created company.
/// Display name of the new company.
/// Subscription plan the company signed up for.
/// Full name of the registration contact.
/// Email address of the registration contact.
public async Task NotifyNewCompanyRegisteredAsync(
int companyId, string companyName, string planName,
string contactName, string contactEmail)
{
var adminEmails = await GetAdminEmailsAsync();
if (adminEmails == null) return;
var subject = $"[New Signup] {companyName}";
var html = $"""
New Company Registered
| Company: | {Encode(companyName)} (ID: {companyId}) |
| Plan: | {Encode(planName)} |
| Contact Name: | {Encode(contactName)} |
| Contact Email: | {Encode(contactEmail)} |
| Registered: | {DateTime.UtcNow:MM/dd/yyyy h:mm tt} UTC |
""";
var plain = $"New Company Registered\n\n" +
$"Company: {companyName} (ID: {companyId})\n" +
$"Plan: {planName}\n" +
$"Contact: {contactName} <{contactEmail}>\n" +
$"Registered: {DateTime.UtcNow:MM/dd/yyyy h:mm tt} UTC";
await SendAsync(adminEmails, subject, plain, html);
}
///
/// Sends an email notification to all configured SuperAdmin addresses when a user submits
/// a bug report, including the report ID, title, priority, submitting user, and full
/// description body so admins can triage without logging in.
///
/// Database ID of the new bug report.
/// Short title of the bug report.
/// Full description text entered by the user.
/// Priority label (e.g. Low, Medium, High, Critical).
/// Display name of the user who submitted the report.
/// Name of the company the submitting user belongs to.
public async Task NotifyBugReportSubmittedAsync(
int bugReportId, string title, string description,
string priority, string submittedByName, string companyName)
{
var adminEmails = await GetAdminEmailsAsync();
if (adminEmails == null) return;
var subject = $"[Bug Report] {title}";
var html = $"""
New Bug Report Submitted
| Report #: | {bugReportId} |
| Title: | {Encode(title)} |
| Priority: | {Encode(priority)} |
| Submitted By: | {Encode(submittedByName)} |
| Company: | {Encode(companyName)} |
| Submitted: | {DateTime.UtcNow:MM/dd/yyyy h:mm tt} UTC |
Description
{Encode(description)}
""";
var plain = $"New Bug Report #{bugReportId}\n\n" +
$"Title: {title}\n" +
$"Priority: {priority}\n" +
$"Submitted By: {submittedByName}\n" +
$"Company: {companyName}\n" +
$"Submitted: {DateTime.UtcNow:MM/dd/yyyy h:mm tt} UTC\n\n" +
$"Description:\n{description}";
await SendAsync(adminEmails, subject, plain, html);
}
///
/// Sends an email notification to all configured SuperAdmin addresses when a company's
/// subscription has expired and its account has been deactivated.
///
/// Database ID of the expired company.
/// Display name of the company.
/// Primary contact email for the company.
/// The UTC date on which the subscription expired.
public async Task NotifyCompanyExpiredAsync(
int companyId, string companyName, string contactEmail, DateTime expiredOn)
{
var adminEmails = await GetAdminEmailsAsync();
if (adminEmails == null) return;
var subject = $"[Subscription Expired] {companyName}";
var html = $"""
Company Subscription Expired
| Company: | {Encode(companyName)} (ID: {companyId}) |
| Contact Email: | {Encode(contactEmail)} |
| Expired On: | {expiredOn:MMMM d, yyyy} |
This company has been deactivated and is no longer able to log in.
""";
var plain = $"Company Subscription Expired\n\n" +
$"Company: {companyName} (ID: {companyId})\n" +
$"Contact Email: {contactEmail}\n" +
$"Expired On: {expiredOn:MMMM d, yyyy}\n\n" +
$"This company has been deactivated.";
await SendAsync(adminEmails, subject, plain, html);
}
///
/// Sends an email notification to all configured SuperAdmin addresses when a company
/// enters a subscription grace period, including the grace-period end date so admins
/// know when the account will auto-deactivate if not renewed.
///
/// Database ID of the company entering grace period.
/// Display name of the company.
/// Primary contact email for the company.
/// UTC date on which the grace period expires.
public async Task NotifyCompanyGracePeriodAsync(
int companyId, string companyName, string contactEmail, DateTime gracePeriodEndsOn)
{
var adminEmails = await GetAdminEmailsAsync();
if (adminEmails == null) return;
var subject = $"[Grace Period Started] {companyName}";
var html = $"""
Company Entered Grace Period
| Company: | {Encode(companyName)} (ID: {companyId}) |
| Contact Email: | {Encode(contactEmail)} |
| Grace Period Ends: | {gracePeriodEndsOn:MMMM d, yyyy} |
If not renewed by the grace period end date, this company will be automatically deactivated.
""";
var plain = $"Company Entered Grace Period\n\n" +
$"Company: {companyName} (ID: {companyId})\n" +
$"Contact Email: {contactEmail}\n" +
$"Grace Period Ends: {gracePeriodEndsOn:MMMM d, yyyy}\n\n" +
$"If not renewed, this company will be automatically deactivated.";
await SendAsync(adminEmails, subject, plain, html);
}
///
/// Sends an email to all configured SuperAdmin addresses when a user submits the
/// Contact Us form, including the sender's name, email, company, category, subject,
/// and full message so admins can respond directly from their email client.
///
public async Task NotifyContactFormSubmittedAsync(
string senderName, string senderEmail, string companyName,
string category, string subject, string message)
{
var adminEmails = await GetAdminEmailsAsync();
if (adminEmails == null) return;
var emailSubject = $"[Contact Us] {category} — {subject}";
var html = $"""
Contact Us Form Submission
| Name: | {Encode(senderName)} |
| Email: | {Encode(senderEmail)} |
| Company: | {Encode(companyName)} |
| Category: | {Encode(category)} |
| Subject: | {Encode(subject)} |
| Submitted: | {DateTime.UtcNow:MM/dd/yyyy h:mm tt} UTC |
Message
{Encode(message)}
""";
var plain = $"Contact Us Form Submission\n\n" +
$"Name: {senderName}\n" +
$"Email: {senderEmail}\n" +
$"Company: {companyName}\n" +
$"Category: {category}\n" +
$"Subject: {subject}\n" +
$"Submitted: {DateTime.UtcNow:MM/dd/yyyy h:mm tt} UTC\n\n" +
$"Message:\n{message}";
await SendAsync(adminEmails, emailSubject, plain, html, replyToEmail: senderEmail, replyToName: senderName);
}
// ─── Helpers ─────────────────────────────────────────────────────────────
///
/// Reads the AdminNotificationEmail platform setting and returns a list of valid
/// email addresses, or null if the setting is absent, blank, or contains no
/// parseable addresses.
///
///
/// Returning null (rather than an empty list) is intentional: callers use a
/// null-check early-return pattern (if (adminEmails == null) return;) which is
/// cleaner than checking Count == 0 in each notification method. A DEBUG-level
/// log entry is written so that missing configuration is detectable without generating
/// noise at WARNING or above in normal operation.
///
/// Non-empty list of email strings, or null if none are configured.
private async Task?> GetAdminEmailsAsync()
{
var raw = await _platformSettings.GetAsync(PlatformSettingKeys.AdminNotificationEmail);
if (string.IsNullOrWhiteSpace(raw))
{
_logger.LogDebug("PlatformSetting '{Key}' is not configured — skipping admin notification.",
PlatformSettingKeys.AdminNotificationEmail);
return null;
}
var emails = raw
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(e => e.Contains('@'))
.ToList();
if (emails.Count == 0)
{
_logger.LogDebug("PlatformSetting '{Key}' contained no valid addresses — skipping.",
PlatformSettingKeys.AdminNotificationEmail);
return null;
}
return emails;
}
///
/// Sends the notification email to each address in via
/// , logging a warning for any individual send failure without
/// re-throwing so that a bad address does not prevent delivery to the remaining recipients.
///
/// Validated list of recipient email addresses.
/// Email subject line.
/// Plain-text fallback body.
/// HTML-formatted body shown by modern email clients.
private async Task SendAsync(List adminEmails, string subject, string plain, string html,
string? replyToEmail = null, string? replyToName = null)
{
foreach (var email in adminEmails)
{
var (success, error) = await _emailService.SendEmailAsync(
toEmail: email,
toName: "PCL Admin",
subject: subject,
plainTextBody: plain,
htmlBody: html,
replyToEmail: replyToEmail,
replyToName: replyToName);
if (!success)
_logger.LogWarning("Admin notification email failed for {Email} ({Subject}): {Error}", email, subject, error);
else
_logger.LogInformation("Admin notification sent to {Email}: {Subject}", email, subject);
}
}
///
/// HTML-encodes a string for safe embedding in email HTML bodies, guarding against
/// XSS if user-supplied values (company names, titles, descriptions) contain angle
/// brackets or other special characters. Null input is treated as empty string.
///
private static string Encode(string? value) =>
System.Net.WebUtility.HtmlEncode(value ?? string.Empty);
}