lettermint mail dispatcher
This commit is contained in:
@@ -0,0 +1,157 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Net.Mail;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Attachment = System.Net.Mail.Attachment;
|
||||||
|
|
||||||
|
namespace Mixtape.Mails.Dispatchers.Lettermint;
|
||||||
|
|
||||||
|
public class LettermintDispatcher : IMailDispatcher
|
||||||
|
{
|
||||||
|
protected Queue<Mail> Queue { get; } = new();
|
||||||
|
|
||||||
|
protected MailOptions Options { get; set; }
|
||||||
|
|
||||||
|
protected IWebHostEnvironment Env { get; set; }
|
||||||
|
|
||||||
|
protected HttpClient Http { get; }
|
||||||
|
|
||||||
|
protected JsonSerializerOptions JsonSerializerOptions { get; }
|
||||||
|
|
||||||
|
protected ILogger<LettermintDispatcher> Logger { get; }
|
||||||
|
|
||||||
|
|
||||||
|
public LettermintDispatcher(IOptionsMonitor<MailOptions> monitor, IWebHostEnvironment env, HttpClient http, ILogger<LettermintDispatcher> logger)
|
||||||
|
{
|
||||||
|
Options = monitor.CurrentValue;
|
||||||
|
Env = env;
|
||||||
|
Http = http;
|
||||||
|
Http.DefaultRequestHeaders.Add("x-lettermint-token", Options.Lettermint.Token);
|
||||||
|
JsonSerializerOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||||
|
};
|
||||||
|
Logger = logger;
|
||||||
|
|
||||||
|
monitor.OnChange(opts => Options = opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<bool> IsSenderSupported(string email, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Send(Mail message, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
string uri = Options.Lettermint.ApiUrl + $"/v1/send";
|
||||||
|
|
||||||
|
// add app name as tag
|
||||||
|
if (message.Metadata.TryGetValue("application", out string value) && !message.Tag.HasValue())
|
||||||
|
{
|
||||||
|
message.Tag = value;
|
||||||
|
message.Metadata.Remove("application");
|
||||||
|
}
|
||||||
|
|
||||||
|
LettermintRequest.SendEmail data = new()
|
||||||
|
{
|
||||||
|
// to addresses
|
||||||
|
To = Convert(message.To),
|
||||||
|
ReplyTo = Convert(message.ReplyToList),
|
||||||
|
Cc = Convert(message.CC),
|
||||||
|
Bcc = Convert(message.Bcc),
|
||||||
|
|
||||||
|
// from address
|
||||||
|
From = message.From!.ToString(),
|
||||||
|
|
||||||
|
// subject
|
||||||
|
Subject = message.Subject,
|
||||||
|
|
||||||
|
// tag/group
|
||||||
|
Tag = message.Tag,
|
||||||
|
|
||||||
|
// metadata
|
||||||
|
Metadata = message.Metadata,
|
||||||
|
Route = Options.Lettermint.Route
|
||||||
|
};
|
||||||
|
|
||||||
|
// set attachments
|
||||||
|
foreach (Attachment attachment in message.Attachments)
|
||||||
|
{
|
||||||
|
data.Attachments.Add(Convert(attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set body
|
||||||
|
if (!message.IsBodyHtml)
|
||||||
|
{
|
||||||
|
data.Text = message.Body;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data.Html = message.Body;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using HttpResponseMessage responseMessage = await Http.PostAsJsonAsync(uri, data, JsonSerializerOptions, token);
|
||||||
|
LettermintResponse.SendEmail response = await responseMessage.Content.ReadFromJsonAsync<LettermintResponse.SendEmail>(JsonSerializerOptions, token);
|
||||||
|
|
||||||
|
if (!responseMessage.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new Exception($"Could not send message via Lettermint API. Status code: {responseMessage.StatusCode}, Message: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Email {id} sent via Lettermint API", response.MessageId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Could not send message via Lettermint API");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose() { }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a collection of addresses to a Lettermint email addresses
|
||||||
|
/// </summary>
|
||||||
|
protected string[] Convert(MailAddressCollection addresses)
|
||||||
|
{
|
||||||
|
return addresses
|
||||||
|
.Select(address => address.ToString())
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert an attachment to a Lettermint email attachment
|
||||||
|
/// </summary>
|
||||||
|
protected LettermintRequest.EmailAttachment Convert(Attachment attachment)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[8067];
|
||||||
|
using MemoryStream memoryStream = new();
|
||||||
|
int count;
|
||||||
|
while ((count = attachment.ContentStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
memoryStream.Write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
string base64String = System.Convert.ToBase64String(memoryStream.ToArray());
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Filename = attachment.Name,
|
||||||
|
ContentType = attachment.ContentType.MediaType,
|
||||||
|
Content = base64String,
|
||||||
|
ContentId = attachment.ContentId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Mixtape.Mails.Dispatchers.Lettermint;
|
||||||
|
|
||||||
|
public class LettermintOptions
|
||||||
|
{
|
||||||
|
public string ApiUrl { get; set; } = "https://api.lettermint.co";
|
||||||
|
|
||||||
|
public string Token { get; set; }
|
||||||
|
|
||||||
|
public string Route { get; set; } = "outgoing";
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Mixtape.Mails.Dispatchers.Lettermint;
|
||||||
|
|
||||||
|
public class LettermintRequest
|
||||||
|
{
|
||||||
|
public class SendEmail
|
||||||
|
{
|
||||||
|
public string From { get; set; }
|
||||||
|
|
||||||
|
public string[] To { get; set; } = [];
|
||||||
|
|
||||||
|
public string[] Cc { get; set; } = [];
|
||||||
|
|
||||||
|
public string[] Bcc { get; set; } = [];
|
||||||
|
|
||||||
|
public string[] ReplyTo { get; set; }
|
||||||
|
|
||||||
|
public string Subject { get; set; }
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public string Html { get; set; }
|
||||||
|
|
||||||
|
public string Route { get; set; }
|
||||||
|
|
||||||
|
public string Tag { get; set; }
|
||||||
|
|
||||||
|
public List<EmailAttachment> Attachments { get; set; } = [];
|
||||||
|
|
||||||
|
public Dictionary<string, string> Metadata { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EmailAttachment
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Filename of the attachment.
|
||||||
|
/// </summary>
|
||||||
|
public string Filename { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MIME type of the attachment.
|
||||||
|
/// </summary>
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content of the attachment encoded in base64.
|
||||||
|
/// </summary>
|
||||||
|
public string Content { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content ID for inline attachments, referenced via cid: in HTML body
|
||||||
|
/// </summary>
|
||||||
|
public string ContentId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Mixtape.Mails.Dispatchers.Lettermint;
|
||||||
|
|
||||||
|
public class LettermintResponse
|
||||||
|
{
|
||||||
|
public class SendEmail
|
||||||
|
{
|
||||||
|
public string MessageId { get; set; }
|
||||||
|
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("message")]
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Mixtape.Mails.Dispatchers.Postmark;
|
using Mixtape.Mails.Dispatchers.Lettermint;
|
||||||
|
using Mixtape.Mails.Dispatchers.Postmark;
|
||||||
using Mixtape.Mails.Dispatchers.Scaleway;
|
using Mixtape.Mails.Dispatchers.Scaleway;
|
||||||
|
|
||||||
namespace Mixtape.Mails;
|
namespace Mixtape.Mails;
|
||||||
@@ -25,5 +26,7 @@ public class MailOptions
|
|||||||
|
|
||||||
public ScalewayOptions Scaleway { get; set; }
|
public ScalewayOptions Scaleway { get; set; }
|
||||||
|
|
||||||
|
public LettermintOptions Lettermint { get; set; }
|
||||||
|
|
||||||
public Func<Mail, string> BuildViewPath { get; set; }
|
public Func<Mail, string> BuildViewPath { get; set; }
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using Mixtape.Mails.Dispatchers.Postmark;
|
|||||||
using Mixtape.Mails.Dispatchers.Scaleway;
|
using Mixtape.Mails.Dispatchers.Scaleway;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Mixtape.Mails.Dispatchers.Lettermint;
|
||||||
|
|
||||||
namespace Mixtape.Mails;
|
namespace Mixtape.Mails;
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ internal class MixtapeMailModule : MixtapeModule
|
|||||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
services.AddHttpClient<ScalewayDispatcher>().RemoveAllLoggers();
|
services.AddHttpClient<ScalewayDispatcher>().RemoveAllLoggers();
|
||||||
|
services.AddHttpClient<LettermintDispatcher>().RemoveAllLoggers();
|
||||||
|
|
||||||
services.AddScoped<IMailProvider, MailProvider>();
|
services.AddScoped<IMailProvider, MailProvider>();
|
||||||
|
|
||||||
// use logger mail dispatcher as default implementation
|
// use logger mail dispatcher as default implementation
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="10.0.7" />
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="10.0.7" />
|
||||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||||
<PackageReference Include="Postmark" Version="5.3.0" />
|
<PackageReference Include="Postmark" Version="5.3.0" />
|
||||||
<PackageReference Include="PowCapServer.Core" Version="2.0.2" />
|
<PackageReference Include="PowCapServer.Core" Version="2.0.1" />
|
||||||
<PackageReference Include="Seq.Extensions.Logging" Version="9.0.0" />
|
<PackageReference Include="Seq.Extensions.Logging" Version="9.0.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
|
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user