♻️ Refactored email localization & template engine

This commit is contained in:
2026-02-05 00:34:11 +08:00
parent 9b6a62ec66
commit ace65db980
19 changed files with 1077 additions and 0 deletions

View File

@@ -137,5 +137,6 @@
<ItemGroup>
<EmbeddedResource Include="Resources\Locales\*.json" />
<EmbeddedResource Include="Resources\Templates\**\*.cshtml" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Templating;
using Microsoft.AspNetCore.Components;
namespace DysonNetwork.Pass.Mailer;
@@ -6,6 +7,7 @@ namespace DysonNetwork.Pass.Mailer;
public class EmailService(
RingService.RingServiceClient pusher,
RazorViewRenderer viewRenderer,
ITemplateService templateService,
ILogger<EmailService> logger
)
{
@@ -45,4 +47,30 @@ public class EmailService(
throw;
}
}
/// <summary>
/// Sends an email using a RazorLight template with locale support.
/// </summary>
/// <typeparam name="TModel">The model type for the template.</typeparam>
/// <param name="recipientName">The recipient's display name.</param>
/// <param name="recipientEmail">The recipient's email address.</param>
/// <param name="subject">The email subject.</param>
/// <param name="templateName">The template name (e.g., "welcome", "factor-code").</param>
/// <param name="model">The model data for the template.</param>
/// <param name="locale">Optional locale override (defaults to CurrentUICulture).</param>
public async Task SendRazorTemplateEmailAsync<TModel>(string? recipientName, string recipientEmail,
string subject, string templateName, TModel model, string? locale = null)
{
try
{
var htmlBody = await templateService.RenderAsync(templateName, model, locale);
await SendEmailAsync(recipientName, recipientEmail, subject, htmlBody);
}
catch (Exception err)
{
logger.LogError(err, "Failed to render RazorLight email template {TemplateName} for locale {Locale}",
templateName, locale ?? "default");
throw;
}
}
}

View File

@@ -0,0 +1,73 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<AccountDeletionEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var accountDeletionBody = localizer?.Get("accountDeletionBody") ?? "We've received a request to delete your Solar Network account. We're sorry to see you go. To confirm your account deletion, please click the button below. Please note that this action is permanent and cannot be undone.";
var accountDeletionButton = localizer?.Get("accountDeletionButton") ?? "Confirm Account Deletion";
var accountDeletionHint = localizer?.Get("accountDeletionHint") ?? "If you did not request to delete your account, please ignore this email or contact our support team immediately.";
}
<div style="display: none">
Account Deletion Confirmation
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@accountDeletionBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #dc2626"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@accountDeletionButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@accountDeletionHint
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,73 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<ContactVerificationEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var contactVerificationBody = localizer?.Get("contactVerificationBody") ?? "Thank you for updating your contact method on the Solar Network. To ensure your account security, we need to verify this change. Please click the button below to verify your contact method:";
var contactVerificationButton = localizer?.Get("contactVerificationButton") ?? "Verify";
var contactVerificationHint = localizer?.Get("contactVerificationHint") ?? "If you didn't request this change, please contact our support team immediately.";
}
<div style="display: none">
Verify Contact Method
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@contactVerificationBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #020617"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@contactVerificationButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@contactVerificationHint
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,110 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<VerificationEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var codeEmailBody = localizer?.Get("codeEmailBody") ?? "Someone trying to use email auth factor to authorize an access request. If that is you, enter the code below to continue.";
var codeEmailHint = localizer?.Get("codeEmailHint") ?? "This code will expire in 30 minutes.";
var codeEmailHintSecondary = localizer?.Get("codeEmailHintSecondary") ?? "If you didn't request this, you can ignore this email safely.";
}
<div style="display: none">
Email One-time-password
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@codeEmailBody
</p>
<p style="margin: 0; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 16px; line-height: 24px; font-weight: 700; letter-spacing: 8px; color: #475569">
@Model.Code
</p>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0 0 16px; font-size: 12px; line-height: 20px; color: #475569">
@codeEmailHint
</p>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@codeEmailHintSecondary
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,73 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<PasswordResetEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var passwordResetBody = localizer?.Get("passwordResetBody") ?? "We received a request to reset your Solar Network account password. Click the button below to continue and reset it.";
var passwordResetButton = localizer?.Get("passwordResetButton") ?? "Reset Password";
var passwordResetHint = localizer?.Get("passwordResetHint") ?? "If you didn't request this, you can ignore this email safely.";
}
<div style="display: none">
Password Reset Request
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@passwordResetBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #020617"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@passwordResetButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@passwordResetHint
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,74 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<LandingEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var regConfirmBody = localizer?.Get("regConfirmBody") ?? "We're happy to have you joining our community! Please confirm your registration in order to activate your account to unlock all the features available.";
var regConfirmButton = localizer?.Get("regConfirmButton") ?? "Confirm Registration";
var alternativeLinkHint = localizer?.Get("alternativeLinkHint") ?? "If you're having trouble clicking the button, copy and paste the following URL into your web browser:";
}
<div style="display: none">
Welcome to the Solar Network!
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@regConfirmBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #020617"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@regConfirmButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@alternativeLinkHint
<a href="@Model.Link" style="color: #1e293b; text-decoration: underline">@Model.Link</a>
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,56 @@
@using RazorLight
@inherits TemplatePage<object>
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf-8">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<style>
td, th, div, p, a, h1, h2, h3, h4, h5, h6 {
font-family: "Segoe UI", sans-serif;
mso-line-height-rule: exactly;
}
.mso-break-all {
word-break: break-all;
}
</style>
<![endif]-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" media="screen">
<style>
.hover-bg-slate-800:hover {
background-color: #1e293b !important
}
@@media (max-width: 600px) {
.sm-p-6 {
padding: 24px !important
}
.sm-px-4 {
padding-left: 16px !important;
padding-right: 16px !important
}
.sm-px-6 {
padding-left: 24px !important;
padding-right: 24px !important
}
}
</style>
</head>
<body style="margin: 0; width: 100%; background-color: #f8fafc; padding: 0; -webkit-font-smoothing: antialiased; word-break: break-word">
@RenderBody()
</body>
</html>

View File

@@ -0,0 +1,73 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<AccountDeletionEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var accountDeletionBody = localizer?.Get("accountDeletionBody") ?? "We've received a request to delete your Solar Network account. We're sorry to see you go. To confirm your account deletion, please click the button below. Please note that this action is permanent and cannot be undone.";
var accountDeletionButton = localizer?.Get("accountDeletionButton") ?? "Confirm Account Deletion";
var accountDeletionHint = localizer?.Get("accountDeletionHint") ?? "If you did not request to delete your account, please ignore this email or contact our support team immediately.";
}
<div style="display: none">
Account Deletion Confirmation
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@accountDeletionBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #dc2626"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@accountDeletionButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@accountDeletionHint
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,73 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<ContactVerificationEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var contactVerificationBody = localizer?.Get("contactVerificationBody") ?? "Thank you for updating your contact method on the Solar Network. To ensure your account security, we need to verify this change. Please click the button below to verify your contact method:";
var contactVerificationButton = localizer?.Get("contactVerificationButton") ?? "Verify";
var contactVerificationHint = localizer?.Get("contactVerificationHint") ?? "If you didn't request this change, please contact our support team immediately.";
}
<div style="display: none">
Verify Contact Method
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@contactVerificationBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #020617"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@contactVerificationButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@contactVerificationHint
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,110 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<VerificationEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var codeEmailBody = localizer?.Get("codeEmailBody") ?? "Someone trying to use email auth factor to authorize an access request. If that is you, enter the code below to continue.";
var codeEmailHint = localizer?.Get("codeEmailHint") ?? "This code will expire in 30 minutes.";
var codeEmailHintSecondary = localizer?.Get("codeEmailHintSecondary") ?? "If you didn't request this, you can ignore this email safely.";
}
<div style="display: none">
Email One-time-password
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@codeEmailBody
</p>
<p style="margin: 0; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 16px; line-height: 24px; font-weight: 700; letter-spacing: 8px; color: #475569">
@Model.Code
</p>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0 0 16px; font-size: 12px; line-height: 20px; color: #475569">
@codeEmailHint
</p>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@codeEmailHintSecondary
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,73 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<PasswordResetEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var passwordResetBody = localizer?.Get("passwordResetBody") ?? "We received a request to reset your Solar Network account password. Click the button below to continue and reset it.";
var passwordResetButton = localizer?.Get("passwordResetButton") ?? "Reset Password";
var passwordResetHint = localizer?.Get("passwordResetHint") ?? "If you didn't request this, you can ignore this email safely.";
}
<div style="display: none">
Password Reset Request
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@passwordResetBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #020617"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@passwordResetButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@passwordResetHint
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,74 @@
@using DysonNetwork.Pass.Mailer
@using DysonNetwork.Shared.Localization
@using RazorLight
@inherits TemplatePage<LandingEmailModel>
@{
Layout = "_Layout";
var localizer = LocalizationServiceLocator.Service;
var name = localizer?.Get("usernameFormat", args: new { name = Model.Name }) ?? $"Dear {Model.Name}";
var regConfirmBody = localizer?.Get("regConfirmBody") ?? "We're happy to have you joining our community! Please confirm your registration in order to activate your account to unlock all the features available.";
var regConfirmButton = localizer?.Get("regConfirmButton") ?? "Confirm Registration";
var alternativeLinkHint = localizer?.Get("alternativeLinkHint") ?? "If you're having trouble clicking the button, copy and paste the following URL into your web browser:";
}
<div style="display: none">
Welcome to the Solar Network!
</div>
<div role="article" aria-roledescription="email" aria-label lang="en">
<div class="sm-px-4" style="background-color: #f8fafc; font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" style="margin: 0 auto" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div role="separator" style="line-height: 24px">&zwj;</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-p-6" style="border-radius: 8px; background-color: #fffffe; padding: 24px 36px; border: 1px solid #e2e8f0">
<a href="https://solian.app">
<img src="https://solian.app/favicon.png" width="70" alt="Solar Network Logo" style="max-width: 100%; vertical-align: middle">
</a>
<div role="separator" style="line-height: 24px">&zwj;</div>
<h1 style="margin: 0 0 24px; font-size: 24px; line-height: 32px; font-weight: 600; color: #0f172a">
@name
</h1>
<p style="margin: 0 0 24px; font-size: 16px; line-height: 24px; color: #475569">
@regConfirmBody
</p>
<div>
<a href="@Model.Link"
style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 4px; color: #fffffe; background-color: #020617"
class="hover-bg-slate-800">
<span style="mso-text-raise: 16px">@regConfirmButton</span>
</a>
</div>
<div role="separator" style="line-height: 24px">&zwj;</div>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #475569">
Thanks,
<br>
<span style="font-weight: 600">Solar Network Team</span>
</p>
<div role="separator" style="height: 1px; line-height: 1px; background-color: #cbd5e1; margin-top: 24px; margin-bottom: 24px">
&zwj;
</div>
<p class="mso-break-all" style="margin: 0; font-size: 12px; line-height: 20px; color: #475569">
@alternativeLinkHint
<a href="@Model.Link" style="color: #1e293b; text-decoration: underline">@Model.Link</a>
</p>
</td>
</tr>
</table>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="padding: 24px 36px">
<p style="margin: 0; font-size: 12px; color: #64748b">
&copy; 2025 Solsynth LLC. All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,56 @@
@using RazorLight
@inherits TemplatePage<object>
<!DOCTYPE html>
<html lang="zh-CN" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf-8">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<style>
td, th, div, p, a, h1, h2, h3, h4, h5, h6 {
font-family: "Segoe UI", sans-serif;
mso-line-height-rule: exactly;
}
.mso-break-all {
word-break: break-all;
}
</style>
<![endif]-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" media="screen">
<style>
.hover-bg-slate-800:hover {
background-color: #1e293b !important
}
@@media (max-width: 600px) {
.sm-p-6 {
padding: 24px !important
}
.sm-px-4 {
padding-left: 16px !important;
padding-right: 16px !important
}
.sm-px-6 {
padding-left: 24px !important;
padding-right: 24px !important
}
}
</style>
</head>
<body style="margin: 0; width: 100%; background-color: #f8fafc; padding: 0; -webkit-font-smoothing: antialiased; word-break: break-word">
@RenderBody()
</body>
</html>

View File

@@ -134,6 +134,12 @@ public static class ServiceCollectionExtensions
IConfiguration configuration)
{
services.AddScoped<RazorViewRenderer>();
services.AddScoped<DysonNetwork.Shared.Templating.ITemplateService, DysonNetwork.Shared.Templating.RazorLightTemplateService>(sp =>
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var resourceNamespace = "DysonNetwork.Pass.Resources.Templates";
return new DysonNetwork.Shared.Templating.RazorLightTemplateService(assembly, resourceNamespace);
});
services.Configure<GeoOptions>(configuration.GetSection("GeoIP"));
services.AddScoped<GeoService>();
services.AddScoped<EmailService>();