Fix email templates for Outlook compatibility and move HTML out of code
- Replace div-based layouts with table-based HTML throughout (max-width/border-radius/display:inline-block ignored by Outlook) - email.match.body: width:100% table with per-cell borders and fixed 130px label column - email.match.job-search-footer: table-based button with bgcolor attribute - email.search-results.empty: div replaced with full-width table - email.search-results.body: remove div wrapper around items - Add email.search-results.scan-summary and email.search-results.item templates - CvSearchEmailSender: remove all hardcoded HTML; render via IEmailTemplateService Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,7 +89,7 @@ public sealed class CvSearchEmailSender
|
||||
/// </summary>
|
||||
private string BuildBody(IReadOnlyList<JobSearchResultEntity> results, IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language)
|
||||
{
|
||||
var scanSummary = BuildScanSummary(keywords, providerNames);
|
||||
var scanSummary = BuildScanSummary(keywords, providerNames, language);
|
||||
|
||||
if (results.Count == 0)
|
||||
return scanSummary + _emailTemplates.Get("email.search-results.empty", language);
|
||||
@@ -98,18 +98,18 @@ public sealed class CvSearchEmailSender
|
||||
for (int i = 0; i < results.Count; i++)
|
||||
{
|
||||
var r = results[i];
|
||||
var matchResp = TryParseResult(r.ResultJson);
|
||||
var summary = matchResp?.Summary;
|
||||
var summary = TryParseResult(r.ResultJson)?.Summary;
|
||||
var summaryHtml = string.IsNullOrWhiteSpace(summary)
|
||||
? ""
|
||||
: $"<p style=\"margin:8px 0 0;color:#495057;font-size:14px;line-height:1.5;\">{summary}</p>";
|
||||
|
||||
items.Append($"""
|
||||
<div style="border:1px solid #dee2e6;border-radius:6px;padding:16px;margin-bottom:12px">
|
||||
<strong style="color:#212529">{i + 1}. {r.JobTitle}</strong>
|
||||
<span style="background:#28a745;color:#fff;padding:2px 8px;border-radius:12px;font-size:12px;margin-left:8px">{r.Score}% match</span>
|
||||
<span style="color:#6c757d;font-size:12px;margin-left:4px">[{r.ProviderName}]</span><br>
|
||||
<a href="{r.JobUrl}" style="color:#2c5282;font-size:13px">{r.JobUrl}</a>
|
||||
{(string.IsNullOrWhiteSpace(summary) ? "" : $"<p style=\"margin:8px 0 0;color:#495057;font-size:14px;line-height:1.5\">{summary}</p>")}
|
||||
</div>
|
||||
""");
|
||||
items.Append(_emailTemplates.Render("email.search-results.item", language,
|
||||
("index", (i + 1).ToString()),
|
||||
("jobTitle", r.JobTitle),
|
||||
("score", r.Score.ToString()),
|
||||
("providerName", r.ProviderName),
|
||||
("jobUrl", r.JobUrl),
|
||||
("summary", summaryHtml)));
|
||||
}
|
||||
|
||||
return _emailTemplates.Render("email.search-results.body", language,
|
||||
@@ -118,25 +118,23 @@ public sealed class CvSearchEmailSender
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the scan summary block showing the CV keywords and providers used for the search.
|
||||
/// Renders the scan summary block via template, passing keyword tags and provider list as data.
|
||||
/// Keyword tags are built here because they are variable-count inline elements, not structural HTML.
|
||||
/// </summary>
|
||||
private static string BuildScanSummary(IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames)
|
||||
private string BuildScanSummary(IReadOnlyList<string> keywords, IReadOnlyList<string> providerNames, string language)
|
||||
{
|
||||
var keywordsHtml = keywords.Count > 0
|
||||
? string.Join(" ", keywords.Select(k =>
|
||||
$"<span style=\"display:inline-block;background:#e9ecef;border-radius:4px;padding:2px 8px;margin:2px 2px 2px 0;font-size:12px\">{k}</span>"))
|
||||
: "<span style=\"color:#6c757d;font-size:12px;font-style:italic\">none detected</span>";
|
||||
$"<span style=\"display:inline-block;background:#e9ecef;padding:2px 8px;margin:2px 2px 2px 0;font-size:12px;\">{k}</span>"))
|
||||
: "<span style=\"color:#6c757d;font-size:12px;font-style:italic;\">none detected</span>";
|
||||
|
||||
var providersText = providerNames.Count > 0
|
||||
var providers = providerNames.Count > 0
|
||||
? string.Join(", ", providerNames)
|
||||
: "none";
|
||||
|
||||
return $"""
|
||||
<div style="background:#f8f9fa;border:1px solid #dee2e6;border-radius:6px;padding:14px 16px;margin-bottom:18px;font-size:13px;color:#495057">
|
||||
<div style="margin-bottom:8px"><strong>Keywords used:</strong> {keywordsHtml}</div>
|
||||
<div><strong>Providers scanned:</strong> {providersText}</div>
|
||||
</div>
|
||||
""";
|
||||
return _emailTemplates.Render("email.search-results.scan-summary", language,
|
||||
("keywordsHtml", keywordsHtml),
|
||||
("providers", providers));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user