Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/Exceptionless.Core/Mail/Mailer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Text.Json;
using Exceptionless.Core.Extensions;
using Exceptionless.Core.Models;
using Exceptionless.Core.Plugins.Formatting;
Expand All @@ -8,12 +9,14 @@
using Foundatio.Serializer;
using HandlebarsDotNet;
using Microsoft.Extensions.Logging;
using NetMailAddress = System.Net.Mail.MailAddress;

namespace Exceptionless.Core.Mail;

public class Mailer : IMailer
{
private readonly ConcurrentDictionary<string, HandlebarsTemplate<object, object>> _cachedTemplates = new();
private readonly IHandlebars _handlebars = Handlebars.Create();
private readonly IQueue<MailMessage> _queue;
private readonly FormattingPluginManager _pluginManager;
private readonly AppOptions _appOptions;
Expand All @@ -29,6 +32,12 @@ public Mailer(IQueue<MailMessage> queue, FormattingPluginManager pluginManager,
_timeProvider = timeProvider;
_serializer = serializer;
_logger = logger;

_handlebars.RegisterHelper("json", (writer, _, parameters) =>
{
object? value = parameters.Length > 0 ? parameters[0] : null;
writer.WriteSafeString(JsonSerializer.Serialize(value));
});
}

public async Task<bool> SendContactRequestAsync(string name, string emailAddress, string? company, string? subject, string message, string? clientIpAddress, string? userAgent, string? referrer)
Expand Down Expand Up @@ -115,7 +124,10 @@ private void AddUserInfo(PersistentEvent ev, Dictionary<string, object?> data)
data["UserDescription"] = ud.Description;

if (!String.IsNullOrEmpty(ud?.EmailAddress))
{
data["UserEmail"] = ud.EmailAddress;
data["UserEmailHref"] = BuildMailtoHref(ud.EmailAddress, ud.Description);
}

string? displayName = null;
if (!String.IsNullOrEmpty(ui?.Identity))
Expand All @@ -135,6 +147,19 @@ private void AddUserInfo(PersistentEvent ev, Dictionary<string, object?> data)
data["HasUserInfo"] = ud is not null || ui is not null;
}

private static string BuildMailtoHref(string emailAddress, string? body)
{
string address = NetMailAddress.TryCreate(emailAddress, out NetMailAddress? parsedAddress)
? parsedAddress.Address
: emailAddress;
int atIndex = address.LastIndexOf('@');
string escapedAddress = atIndex > 0
? $"{Uri.EscapeDataString(address[..atIndex])}@{Uri.EscapeDataString(address[(atIndex + 1)..])}"
: Uri.EscapeDataString(address);
string href = $"mailto:{escapedAddress}";
return String.IsNullOrEmpty(body) ? href : $"{href}?body={Uri.EscapeDataString(body)}";
}

private static void AddDefaultFields(PersistentEvent ev, Dictionary<string, object?> data)
{
if (ev.Tags?.Count > 0)
Expand Down Expand Up @@ -269,7 +294,8 @@ private static IEnumerable<object> GetStackTemplateData(IEnumerable<Stack> stack
StackId = s.Id,
Title = s.Title.Truncate(50),
TypeName = s.GetTypeName()?.Truncate(50),
s.Status
s.Status,
IsRegressed = s.Status == StackStatus.Regressed
});
}

Expand Down Expand Up @@ -334,7 +360,7 @@ private HandlebarsTemplate<object, object> GetCompiledTemplate(string name)
using var reader = new StreamReader(stream ?? throw new InvalidOperationException());

string template = reader.ReadToEnd();
var compiledTemplateFunc = Handlebars.Compile(template);
var compiledTemplateFunc = _handlebars.Compile(template);
return compiledTemplateFunc;
});
}
Expand Down
6 changes: 3 additions & 3 deletions src/Exceptionless.Core/Mail/Templates/event-notice.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/Exceptionless.Core/Mail/Templates/organization-added.html

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" style="background:#f7f7f7!important"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport" content="width=device-width"><title>{{Subject}}</title><style>@media only screen{html{min-height:100%;background:#f7f7f7}}@media only screen and (max-width:596px){table.body img{width:auto;height:auto}table.body center{min-width:0!important}table.body .container{width:95%!important}table.body .columns{height:auto!important;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:16px!important;padding-right:16px!important}th.small-3{display:inline-block!important;width:25%!important}th.small-4{display:inline-block!important;width:33.33333%!important}th.small-12{display:inline-block!important;width:100%!important}table.menu{width:100%!important}table.menu td,table.menu th{width:auto!important;display:inline-block!important}table.menu.vertical td,table.menu.vertical th{display:block!important}table.menu[align=center]{width:auto!important}}</style></head><body style="-moz-box-sizing:border-box;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-text-size-adjust:100%;Margin:0;background:#f7f7f7!important;box-sizing:border-box;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;min-width:100%;padding:0;text-align:left;width:100%!important"><span class="preheader" style="color:#f7f7f7;display:none!important;font-size:1px;line-height:1px;max-height:0;max-width:0;mso-hide:all!important;opacity:0;overflow:hidden;visibility:hidden"></span><table class="body" style="Margin:0;background:#f7f7f7!important;border-collapse:collapse;border-spacing:0;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;height:100%;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;width:100%"><tr style="padding:0;text-align:left;vertical-align:top"><td class="center" align="center" valign="top" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"><center data-parsed style="min-width:580px;width:100%"><table align="center" class="wrapper header float-center" style="Margin:0 auto;background:#2c2c2c;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%"><tr style="padding:0;text-align:left;vertical-align:top"><td class="wrapper-inner" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:8px 15px;text-align:left;vertical-align:top;word-wrap:break-word"><table align="center" class="container" style="Margin:0 auto;background:0 0;border-collapse:collapse;border-spacing:0;margin:0 auto;padding:0;text-align:inherit;vertical-align:top;width:580px"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"><img src="https://be.exceptionless.io/img/exceptionless-logo.png" alt="Exceptionless" style="-ms-interpolation-mode:bicubic;clear:both;display:block;margin-left:15px;max-width:100%;outline:0;text-decoration:none;width:auto"></td></tr></tbody></table></td></tr></table><table align="center" class="container float-center" style="Margin:0 auto;background:#f7f7f7;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:580px"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"><table class="spacer" style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><td height="5px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:5px;font-weight:400;hyphens:auto;line-height:5px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td></tr></tbody></table><table class="row" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:16px;padding-left:16px;padding-right:16px;text-align:left;width:564px"><table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tr style="padding:0;text-align:left;vertical-align:top"><th style="Margin:0;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"><p class="lead" style="Margin:0;Margin-bottom:10px;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:20px;font-weight:400;line-height:1.6;margin:0;margin-bottom:10px;padding:0;text-align:left">{{#if IsOverMonthlyLimit}} {{OrganizationName}} has reached its monthly plan limit. Upgrade now to to continue receiving events. {{else if IsOverHourlyLimit}} Events are currently being throttled for {{OrganizationName}} until {{ThrottledUntil}} UTC to prevent using up your plan limit in a small window of time. Upgrade now to increase your limits. {{/if}}</p><center data-parsed style="min-width:532px;width:100%"><table class="button button radius float-center" style="Margin:0 0 16px 0;border-collapse:collapse;border-spacing:0;float:none;margin:0 0 16px 0;padding:0;text-align:center;vertical-align:top;width:auto"><tr style="padding:0;text-align:left;vertical-align:top"><td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"><table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tr style="padding:0;text-align:left;vertical-align:top"><td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;background:#5E9A00;border:none;border-collapse:collapse!important;border-radius:3px;color:#fefefe;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"><a href="{{BaseUrl}}/organization/{{OrganizationId}}/upgrade" style="Margin:0;border:0 solid #5E9A00;border-radius:3px;color:#fefefe;display:inline-block;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:700;line-height:1.3;margin:0;padding:8px 16px 8px 16px;text-align:left;text-decoration:none">Upgrade Plan </a></td></tr></table></td></tr></table></center><table class="spacer" style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><td height="5px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:5px;font-weight:400;hyphens:auto;line-height:5px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td></tr></tbody></table><p style="Margin:0;Margin-bottom:10px;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;margin-bottom:10px;padding:0;text-align:left">{{#if IsOverMonthlyLimit}} <a href="https://github.com/exceptionless/Exceptionless/wiki/Frequently-Asked-Questions#q-what-happens-if-the-organization-plan-limit-is-reached" style="Margin:0;color:#5E9A00;font-family:Helvetica,Arial,sans-serif;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;text-decoration:none">Learn more about what happens when the plan limit is reached. </a>{{else if IsOverHourlyLimit}} <a href="https://github.com/exceptionless/Exceptionless/wiki/Frequently-Asked-Questions#q-why-is-my-organization-throttled" style="Margin:0;color:#5E9A00;font-family:Helvetica,Arial,sans-serif;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;text-decoration:none">Learn more about being throttled. </a>{{/if}} You can also view the <a href="{{BaseUrl}}/organization/{{OrganizationId}}/frequent" style="Margin:0;color:#5E9A00;font-family:Helvetica,Arial,sans-serif;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;text-decoration:none">most frequent events</a> to to see an overall picture of the events that are being counting against your plan limits.</p><table class="spacer" style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><td height="5px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:5px;font-weight:400;hyphens:auto;line-height:5px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td></tr></tbody></table><p style="Margin:0;Margin-bottom:10px;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;margin-bottom:10px;padding:0;text-align:left">Please send us an email at <a href="mailto:support@exceptionless.io" style="Margin:0;color:#5E9A00;font-family:Helvetica,Arial,sans-serif;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;text-decoration:none">support@exceptionless.io</a> if you have any questions or conserns.</p></th><th class="expander" style="Margin:0;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th></tr></table></th></tr></tbody></table><table class="wrapper actions" align="center" style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tr style="padding:0;text-align:left;vertical-align:top"><td class="wrapper-inner" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"><table class="row" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%"><tbody><tr style="padding:0;text-align:left;vertical-align:top"><th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:16px;padding-left:16px;padding-right:16px;text-align:left;width:564px"><table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"><tr style="padding:0;text-align:left;vertical-align:top"><th style="Margin:0;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"><h5 style="Margin:0;Margin-bottom:10px;color:#939393;font-family:Helvetica,Arial,sans-serif;font-size:20px;font-weight:400;line-height:1.3;margin:0;margin-bottom:5px;margin-top:0;padding:0;text-align:left;word-wrap:normal">Other Actions</h5><ul style="margin-top:0"><li style="margin-left:5px;margin-top:5px"><a href="{{BaseUrl}}/organization/{{OrganizationId}}/manage" style="Margin:0;color:#6EBC1A;font-family:Helvetica,Arial,sans-serif;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;text-decoration:none">View usage</a></li><li style="margin-left:5px;margin-top:5px"><a href="{{BaseUrl}}/account/manage?tab=notifications" style="Margin:0;color:#6EBC1A;font-family:Helvetica,Arial,sans-serif;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;text-decoration:none">Change your notification settings</a></li></ul></th><th class="expander" style="Margin:0;color:#2c2c2c;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th></tr></table></th></tr></tbody></table></td></tr></table></td></tr></tbody></table><script type="application/ld+json" align="center" class="float-center">{
"@context": "http://schema.org",
"@type": "EmailMessage",
"description": "{{Subject}}",
"description": {{json Subject}},
"potentialAction": {
"@type": "ViewAction",
"target": "{{BaseUrl}}/organization/{{OrganizationId}}/upgrade",
Expand All @@ -14,4 +14,4 @@
"url": "https://exceptionless.com",
"logo": "https://be.exceptionless.io/img/exceptionless-48.png"
}
}</script></center></td></tr></table><!-- prevent Gmail on iOS font size manipulation --><div style="display:none;white-space:nowrap;font:15px courier;line-height:0">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</div></body></html>
}</script></center></td></tr></table><!-- prevent Gmail on iOS font size manipulation --><div style="display:none;white-space:nowrap;font:15px courier;line-height:0">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</div></body></html>
Loading
Loading