add scaleway email dispatcher; allow multiple dispatchers with priority handling
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Finch.Mails.Scaleway;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Finch.Mails;
|
||||
@@ -7,9 +8,11 @@ internal class FinchMailModule : FinchModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddHttpClient<ScalewayDispatcher>().RemoveAllLoggers();
|
||||
services.AddScoped<IMailProvider, MailProvider>();
|
||||
//services.AddScoped<IMailDispatcher, LoggerMailDispatcher>();
|
||||
services.AddScoped<IMailDispatcher, LoggerMailDispatcher>();
|
||||
services.AddScoped<IMailDispatcher, PostmarkDispatcher>();
|
||||
services.AddScoped<IMailDispatcher, ScalewayDispatcher>();
|
||||
|
||||
services.AddOptions<MailOptions>().Bind(configuration.GetSection("Finch:Mails")).Configure(opts =>
|
||||
{
|
||||
|
||||
@@ -3,17 +3,22 @@
|
||||
public interface IMailDispatcher : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new mail message to the outgoing queue
|
||||
/// If multiple dispatchers are available, the dispatcher with the highest priority will be used
|
||||
/// </summary>
|
||||
void Enqueue(Mail message);
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends all mails which have been added to the queue previously
|
||||
/// Whether this dispatcher is properly configured and can send mails
|
||||
/// </summary>
|
||||
Task Send(CancellationToken token = default);
|
||||
bool CanSend();
|
||||
|
||||
/// <summary>
|
||||
/// Sends a mail message
|
||||
/// </summary>
|
||||
Task Send(Mail message, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a certain sender signature is supported by this dispatcher
|
||||
/// </summary>
|
||||
Task<bool> IsSenderSupported(string email) => Task.FromResult(true);
|
||||
Task<bool> IsSenderSupported(string email, CancellationToken token = default) => Task.FromResult(true);
|
||||
}
|
||||
@@ -7,35 +7,19 @@ namespace Finch.Mails;
|
||||
/// and therefore not using the SMTP channel.
|
||||
/// Implementing real mail sending is up to the consuming application.
|
||||
/// </summary>
|
||||
public class LoggerMailDispatcher : IMailDispatcher
|
||||
public class LoggerMailDispatcher(ILogger<LoggerMailDispatcher> logger) : IMailDispatcher
|
||||
{
|
||||
protected Queue<Mail> Queue { get; } = new Queue<Mail>();
|
||||
/// <inheritdoc />
|
||||
public int Priority { get; } = -10;
|
||||
|
||||
protected ILogger<LoggerMailDispatcher> Logger { get; set; }
|
||||
|
||||
|
||||
public LoggerMailDispatcher(ILogger<LoggerMailDispatcher> logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public bool CanSend() => true;
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Enqueue(Mail message)
|
||||
public Task Send(Mail message, CancellationToken token = default)
|
||||
{
|
||||
Queue.Enqueue(message);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Send(CancellationToken token = default)
|
||||
{
|
||||
while (Queue.Count > 0)
|
||||
{
|
||||
Mail message = Queue.Dequeue();
|
||||
Logger.LogInformation("Mail to {to}. Subject: {subject}", message.To[0].Address, message.Subject);
|
||||
}
|
||||
|
||||
logger.LogInformation("Mail to {to}. Subject: {subject}", message.To[0].Address, message.Subject);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Finch.Mails;
|
||||
|
||||
public class MailDispatcherResolver(IEnumerable<IMailDispatcher> dispatchers, IOptionsMonitor<MailOptions> options)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IMailDispatcher Resolve()
|
||||
{
|
||||
return dispatchers
|
||||
.OrderByDescending(x => x.Priority)
|
||||
.FirstOrDefault(dispatcher => dispatcher.CanSend());
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,9 @@ public class MailOptions
|
||||
|
||||
public string SenderName { get; set; }
|
||||
|
||||
public PostmarkOptions Postmark { get; set; } = new();
|
||||
public PostmarkOptions Postmark { get; set; }
|
||||
|
||||
public Scaleway.ScalewayOptions Scaleway { get; set; }
|
||||
|
||||
public Func<Mail, string> BuildViewPath { get; set; }
|
||||
}
|
||||
+17
-19
@@ -6,35 +6,25 @@ using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Finch.Mails;
|
||||
|
||||
public class MailProvider : IMailProvider
|
||||
public class MailProvider(IFinchContext finch, IOptionsMonitor<MailOptions> mailOptions, ILogger<IMailProvider> logger, IEnumerable<IMailDispatcher> dispatchers, IRazorRenderer renderer) : IMailProvider
|
||||
{
|
||||
protected ILogger<IMailProvider> Logger { get; set; }
|
||||
protected ILogger<IMailProvider> Logger { get; set; } = logger;
|
||||
|
||||
protected IFinchContext Finch { get; set; }
|
||||
protected IFinchContext Finch { get; set; } = finch;
|
||||
|
||||
protected IMailDispatcher MailSender { get; set; }
|
||||
protected IEnumerable<IMailDispatcher> Dispatchers { get; set; } = dispatchers;
|
||||
|
||||
protected IRazorRenderer Renderer { get; set; }
|
||||
protected IRazorRenderer Renderer { get; set; } = renderer;
|
||||
|
||||
protected MailOptions Options { get; set; }
|
||||
protected MailOptions Options { get; set; } = mailOptions.CurrentValue;
|
||||
|
||||
private readonly Encoding _encoding = Encoding.UTF8;
|
||||
|
||||
|
||||
public MailProvider(IFinchContext finch, IOptionsMonitor<MailOptions> mailOptions, ILogger<IMailProvider> logger, IMailDispatcher mailSender, IRazorRenderer renderer)
|
||||
{
|
||||
Finch = finch;
|
||||
Logger = logger;
|
||||
MailSender = mailSender;
|
||||
Renderer = renderer;
|
||||
Options = mailOptions.CurrentValue;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task Send(Mail message, CancellationToken token = default)
|
||||
{
|
||||
await Send(message, MailSender, token);
|
||||
await Send(message, GetDispatcher(), token);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +39,7 @@ public class MailProvider : IMailProvider
|
||||
try
|
||||
{
|
||||
await Prepare(message);
|
||||
dispatcher.Enqueue(message);
|
||||
await dispatcher.Send(token);
|
||||
await dispatcher.Send(message, token);
|
||||
|
||||
Logger.LogInformation("Dispatched email to {recipient} (cc: {cc}, bcc: {bcc})", message.To, message.CC, message.Bcc);
|
||||
}
|
||||
@@ -99,6 +88,15 @@ public class MailProvider : IMailProvider
|
||||
message.IsRendered = true;
|
||||
return message.Body;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected IMailDispatcher GetDispatcher()
|
||||
{
|
||||
return Dispatchers
|
||||
.OrderByDescending(x => x.Priority)
|
||||
.FirstOrDefault(dispatcher => dispatcher.CanSend());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using PostmarkDotNet;
|
||||
using PostmarkDotNet.Model;
|
||||
|
||||
namespace Finch.Mails;
|
||||
|
||||
public class PostmarkDispatcher : IMailDispatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority { get; } = 3;
|
||||
|
||||
protected PostmarkClient Postmark { get; set; }
|
||||
|
||||
protected PostmarkAdminClient PostmarkAdmin { get; set; }
|
||||
|
||||
protected MailOptions Options { get; set; }
|
||||
|
||||
protected IWebHostEnvironment Env { get; set; }
|
||||
|
||||
|
||||
public PostmarkDispatcher(IOptionsMonitor<MailOptions> monitor, IWebHostEnvironment env)
|
||||
{
|
||||
Options = monitor.CurrentValue;
|
||||
PostmarkOptions opts = Options.Postmark ?? new();
|
||||
Postmark = new(opts.ServerToken);
|
||||
PostmarkAdmin = new(opts.AccountToken);
|
||||
Env = env;
|
||||
|
||||
monitor.OnChange(_opts =>
|
||||
{
|
||||
Options = _opts;
|
||||
Postmark = new(_opts.Postmark?.ServerToken);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanSend()
|
||||
{
|
||||
return Options.Postmark != null && !Options.Postmark.ServerToken.IsNullOrEmpty();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> IsSenderSupported(string email)
|
||||
{
|
||||
if (email.IsNullOrWhiteSpace() || Options.Postmark.AccountToken.IsNullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
email = email.FullTrim();
|
||||
PostmarkSenderSignatureList signatures = await PostmarkAdmin.GetSenderSignaturesAsync();
|
||||
return signatures.SenderSignatures.Any(x => x.Confirmed && x.EmailAddress.Equals(email, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Send(Mail message, CancellationToken token = default)
|
||||
{
|
||||
PostmarkMessage data = new()
|
||||
{
|
||||
// to addresses
|
||||
To = message.To.ToString(),
|
||||
Cc = message.CC.ToString(),
|
||||
Bcc = message.Bcc.ToString(),
|
||||
|
||||
// from address
|
||||
From = message.From.ToString(),
|
||||
ReplyTo = message.ReplyToList.ToString(),
|
||||
|
||||
// subject
|
||||
Subject = message.Subject,
|
||||
|
||||
// tracking
|
||||
TrackLinks = LinkTrackingOptions.None,
|
||||
TrackOpens = false,
|
||||
|
||||
// configuration
|
||||
MessageStream = Options.Postmark.MessageStream,
|
||||
//Tag = message.Template?.Key,
|
||||
Metadata = message.Metadata
|
||||
};
|
||||
|
||||
// set attachments
|
||||
foreach (System.Net.Mail.Attachment attachment in message.Attachments)
|
||||
{
|
||||
data.AddAttachment(attachment.ContentStream, attachment.Name, attachment.ContentType.MediaType, attachment.ContentId);
|
||||
}
|
||||
|
||||
// set body
|
||||
if (!message.IsBodyHtml)
|
||||
{
|
||||
data.TextBody = message.Body;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.HtmlBody = message.Body;
|
||||
}
|
||||
|
||||
// overwrite for debug mode
|
||||
if (Options.Debug || (Env != null && !Env.IsProduction()))
|
||||
{
|
||||
data.From = "noreply@post.swcs.pro";
|
||||
|
||||
string[] allowedTlds = ["swcs.pro", "alias.swcs.pro"];
|
||||
string tld = data.To.TrimEnd('>').Split('@').LastOrDefault();
|
||||
|
||||
data.Cc = null; // "cee-maildev@gmx.at,anaheimcore@gmail.com,ceemaildev@yahoo.com";
|
||||
data.Bcc = null;
|
||||
|
||||
if (!allowedTlds.Contains(tld, StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
data.Subject = $"{data.Subject} (test; für {data.To})";
|
||||
data.To = "maildev@alias.swcs.pro";
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Subject = $"{data.Subject} (test)";
|
||||
}
|
||||
}
|
||||
|
||||
// finally sends the message
|
||||
PostmarkResponse response = await Postmark.SendMessageAsync(data);
|
||||
|
||||
if (response.ErrorCode > 0)
|
||||
{
|
||||
throw new PostmarkSendException($"Could not send message via Postmark API. Code: {response.ErrorCode}, Message: {response.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
|
||||
public class PostmarkSendException : Exception
|
||||
{
|
||||
public PostmarkSendException() : base() { }
|
||||
|
||||
public PostmarkSendException(string message) : base(message) { }
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using PostmarkDotNet;
|
||||
using PostmarkDotNet.Model;
|
||||
|
||||
namespace Finch.Mails;
|
||||
|
||||
public class PostmarkDispatcher : IMailDispatcher
|
||||
{
|
||||
protected Queue<Mail> Queue { get; } = new();
|
||||
|
||||
protected PostmarkClient Postmark { get; set; }
|
||||
|
||||
protected PostmarkAdminClient PostmarkAdmin { get; set; }
|
||||
|
||||
protected MailOptions Options { get; set; }
|
||||
|
||||
protected IWebHostEnvironment Env { get; set; }
|
||||
|
||||
|
||||
public PostmarkDispatcher(IOptionsMonitor<MailOptions> monitor, IWebHostEnvironment env)
|
||||
{
|
||||
Options = monitor.CurrentValue;
|
||||
Postmark = new(Options.Postmark.ServerToken);
|
||||
PostmarkAdmin = new(Options.Postmark.AccountToken);
|
||||
Env = env;
|
||||
|
||||
monitor.OnChange(opts =>
|
||||
{
|
||||
Options = opts;
|
||||
Postmark = new(Options.Postmark.ServerToken);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> IsSenderSupported(string email)
|
||||
{
|
||||
if (email.IsNullOrWhiteSpace() || Options.Postmark.AccountToken.IsNullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
email = email.FullTrim();
|
||||
PostmarkSenderSignatureList signatures = await PostmarkAdmin.GetSenderSignaturesAsync();
|
||||
return signatures.SenderSignatures.Any(x => x.Confirmed && x.EmailAddress.Equals(email, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Enqueue(Mail message)
|
||||
{
|
||||
Queue.Enqueue(message);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Send(CancellationToken token = default)
|
||||
{
|
||||
HashSet<PostmarkMessage> messages = [];
|
||||
|
||||
while (Queue.Count > 0)
|
||||
{
|
||||
Mail message = Queue.Dequeue();
|
||||
|
||||
PostmarkMessage data = new()
|
||||
{
|
||||
// to addresses
|
||||
To = message.To.ToString(),
|
||||
Cc = message.CC.ToString(),
|
||||
Bcc = message.Bcc.ToString(),
|
||||
|
||||
// from address
|
||||
From = message.From.ToString(),
|
||||
ReplyTo = message.ReplyToList.ToString(),
|
||||
|
||||
// subject
|
||||
Subject = message.Subject,
|
||||
|
||||
// tracking
|
||||
TrackLinks = LinkTrackingOptions.None,
|
||||
TrackOpens = false,
|
||||
|
||||
// configuration
|
||||
MessageStream = Options.Postmark.MessageStream,
|
||||
//Tag = message.Template?.Key,
|
||||
Metadata = message.Metadata
|
||||
};
|
||||
|
||||
// set attachments
|
||||
foreach (System.Net.Mail.Attachment attachment in message.Attachments)
|
||||
{
|
||||
data.AddAttachment(attachment.ContentStream, attachment.Name, attachment.ContentType.MediaType, attachment.ContentId);
|
||||
}
|
||||
|
||||
// set body
|
||||
if (!message.IsBodyHtml)
|
||||
{
|
||||
data.TextBody = message.Body;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.HtmlBody = message.Body;
|
||||
}
|
||||
|
||||
// overwrite for debug mode
|
||||
if (Options.Debug || (Env != null && !Env.IsProduction()))
|
||||
{
|
||||
data.From = "noreply@post.swcs.pro";
|
||||
|
||||
string[] allowedTlds = ["swcs.pro", "alias.swcs.pro"];
|
||||
string tld = data.To.TrimEnd('>').Split('@').LastOrDefault();
|
||||
|
||||
data.Cc = null; // "cee-maildev@gmx.at,anaheimcore@gmail.com,ceemaildev@yahoo.com";
|
||||
data.Bcc = null;
|
||||
|
||||
if (!allowedTlds.Contains(tld, StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
data.Subject = $"{data.Subject} (test; für {data.To})";
|
||||
data.To = "maildev@alias.swcs.pro";
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Subject = $"{data.Subject} (test)";
|
||||
}
|
||||
}
|
||||
|
||||
messages.Add(data);
|
||||
}
|
||||
|
||||
// finally sends the message
|
||||
IEnumerable<PostmarkResponse> responses = await Postmark.SendMessagesAsync(messages);
|
||||
|
||||
foreach (PostmarkResponse response in responses)
|
||||
{
|
||||
if (response.ErrorCode > 0)
|
||||
{
|
||||
throw new PostmarkSendException($"Could not send message via Postmark API. Code: {response.ErrorCode}, Message: {response.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
|
||||
public class PostmarkSendException : Exception
|
||||
{
|
||||
public PostmarkSendException() : base() { }
|
||||
|
||||
public PostmarkSendException(string message) : base(message) { }
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
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.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Attachment = System.Net.Mail.Attachment;
|
||||
|
||||
namespace Finch.Mails.Scaleway;
|
||||
|
||||
public class ScalewayDispatcher : IMailDispatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority { get; } = 3;
|
||||
|
||||
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<ScalewayDispatcher> Logger { get; }
|
||||
|
||||
|
||||
public ScalewayDispatcher(IOptionsMonitor<MailOptions> monitor, IWebHostEnvironment env, HttpClient http, ILogger<ScalewayDispatcher> logger)
|
||||
{
|
||||
Options = monitor.CurrentValue;
|
||||
Env = env;
|
||||
Http = http;
|
||||
Http.DefaultRequestHeaders.Add("X-Auth-Token", Options.Scaleway.SecretKey);
|
||||
JsonSerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
};
|
||||
Logger = logger;
|
||||
|
||||
monitor.OnChange(opts => Options = opts);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanSend()
|
||||
{
|
||||
return Options.Scaleway != null && !Options.Scaleway.ProjectId.IsNullOrEmpty() && !Options.Scaleway.SecretKey.IsNullOrEmpty();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> IsSenderSupported(string email, CancellationToken token = default)
|
||||
{
|
||||
if (email.IsNullOrWhiteSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string domain = email.FullTrim().Split('@').LastOrDefault();
|
||||
|
||||
string uri = Options.Scaleway.ApiUrl + $"/transactional-email/v1alpha1/regions/{Options.Scaleway.Region}/domains?project_id={Options.Scaleway.ProjectId}&status=checked";
|
||||
ScalewayResponse.ListDomains response = await Http.GetFromJsonAsync<ScalewayResponse.ListDomains>(uri, JsonSerializerOptions, token);
|
||||
return response.Domains.Any(x => x.Name.Equals(domain, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Send(Mail message, CancellationToken token = default)
|
||||
{
|
||||
string uri = Options.Scaleway.ApiUrl + $"/transactional-email/v1alpha1/regions/{Options.Scaleway.Region}/emails";
|
||||
|
||||
ScalewayRequest.SendEmail data = new()
|
||||
{
|
||||
// to addresses
|
||||
To = Convert(message.To),
|
||||
Cc = Convert(message.CC),
|
||||
Bcc = Convert(message.Bcc),
|
||||
|
||||
// from address
|
||||
From = new ScalewayRequest.EmailAddress()
|
||||
{
|
||||
Email = message.From!.Address,
|
||||
Name = message.From.DisplayName
|
||||
},
|
||||
|
||||
// subject
|
||||
Subject = message.Subject
|
||||
};
|
||||
|
||||
// add reply-to header
|
||||
if (message.ReplyToList.Any())
|
||||
{
|
||||
data.AdditionalHeaders.Add(new()
|
||||
{
|
||||
Key = "Reply-To",
|
||||
Value = message.ReplyToList.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
// add additional headers
|
||||
if (message.Metadata.Any())
|
||||
{
|
||||
foreach (KeyValuePair<string, string> item in message.Metadata)
|
||||
{
|
||||
data.AdditionalHeaders.Add(new()
|
||||
{
|
||||
Key = item.Key,
|
||||
Value = item.Value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// overwrite for debug mode
|
||||
if (Options.Debug || (Env != null && !Env.IsProduction()))
|
||||
{
|
||||
data.From = new ScalewayRequest.EmailAddress()
|
||||
{
|
||||
Email = "noreply@post.swcs.pro"
|
||||
};
|
||||
data.Cc = null; // "cee-maildev@gmx.at,anaheimcore@gmail.com,ceemaildev@yahoo.com";
|
||||
data.Bcc = null;
|
||||
data.To = [new ScalewayRequest.EmailAddress()
|
||||
{
|
||||
Email = "maildev@alias.swcs.pro"
|
||||
}];
|
||||
data.Subject = $"{data.Subject} (test)";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using HttpResponseMessage responseMessage = await Http.PostAsJsonAsync(uri, data, JsonSerializerOptions, token);
|
||||
ScalewayResponse.Email response = await responseMessage.Content.ReadFromJsonAsync<ScalewayResponse.Email>(JsonSerializerOptions, token);
|
||||
Logger.LogDebug("Email {id} sent via Scaleway API with status {status}", response.Id, response.Status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not send message via Scaleway API");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert a collection of addresses to a scaleway email addresses
|
||||
/// </summary>
|
||||
protected List<ScalewayRequest.EmailAddress> Convert(MailAddressCollection addresses)
|
||||
{
|
||||
return addresses
|
||||
.Select(address => new ScalewayRequest.EmailAddress()
|
||||
{
|
||||
Email = address.Address,
|
||||
Name = address.DisplayName
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert an attachment to a scaleway email attachment
|
||||
/// </summary>
|
||||
protected ScalewayRequest.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()
|
||||
{
|
||||
Name = attachment.Name,
|
||||
Type = attachment.ContentType.MediaType,
|
||||
Content = base64String
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Finch.Mails.Scaleway;
|
||||
|
||||
public class ScalewayOptions
|
||||
{
|
||||
public string ApiUrl { get; set; } = "https://api.scaleway.com";
|
||||
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
public string Region { get; set; } = "fr-par";
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Finch.Mails.Scaleway;
|
||||
|
||||
public class ScalewayRequest
|
||||
{
|
||||
public class SendEmail
|
||||
{
|
||||
public EmailAddress From { get; set; }
|
||||
|
||||
public List<EmailAddress> To { get; set; } = [];
|
||||
|
||||
public List<EmailAddress> Cc { get; set; } = [];
|
||||
|
||||
public List<EmailAddress> Bcc { get; set; } = [];
|
||||
|
||||
public string Subject { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
public string Html { get; set; }
|
||||
|
||||
[JsonPropertyName("project_id")]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
public List<EmailAttachment> Attachments { get; set; } = [];
|
||||
|
||||
[JsonPropertyName("additional_headers")]
|
||||
public List<EmailHeader> AdditionalHeaders { get; set; } = [];
|
||||
}
|
||||
|
||||
|
||||
public class EmailAddress
|
||||
{
|
||||
public string Email { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class EmailAttachment
|
||||
{
|
||||
/// <summary>
|
||||
/// Filename of the attachment.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MIME type of the attachment.
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content of the attachment encoded in base64.
|
||||
/// </summary>
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class EmailHeader
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
namespace Finch.Mails.Scaleway;
|
||||
|
||||
public class ScalewayResponse
|
||||
{
|
||||
public class ListDomains
|
||||
{
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
public Domain[] Domains { get; set; } = [];
|
||||
}
|
||||
|
||||
public class Domain
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class Email
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string MessageId { get; set; }
|
||||
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
public string MailFrom { get; set; }
|
||||
|
||||
public string RcptTo { get; set; }
|
||||
|
||||
public string MailRcpt { get; set; }
|
||||
|
||||
public string RcptType { get; set; }
|
||||
|
||||
public string Subject { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public string StatusDetails { get; set; }
|
||||
|
||||
public int TryCount { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user