diff --git a/FluentEmail.sln b/FluentEmail.sln
index 7b3ce08e..00a68067 100644
--- a/FluentEmail.sln
+++ b/FluentEmail.sln
@@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentEmail.Liquid", "src\R
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentEmail.Liquid.Tests", "test\FluentEmail.Liquid.Tests\FluentEmail.Liquid.Tests.csproj", "{C8063CBA-D8F3-467A-A75C-63843F0DE862}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentEmail.MailPace", "src\Senders\FluentEmail.MailPace\FluentEmail.MailPace.csproj", "{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -97,6 +99,10 @@ Global
{C8063CBA-D8F3-467A-A75C-63843F0DE862}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8063CBA-D8F3-467A-A75C-63843F0DE862}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8063CBA-D8F3-467A-A75C-63843F0DE862}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -117,6 +123,7 @@ Global
{0C7819AD-BC76-465D-9B2A-BE2DA75042F2} = {926C0980-31D9-4449-903F-3C756044C28A}
{17100F47-A555-4756-A25F-4F05EDAFA74E} = {12F031E5-8DDC-40A0-9862-8764A6E190C0}
{C8063CBA-D8F3-467A-A75C-63843F0DE862} = {47CB89AC-9615-4FA8-90DE-2D849935C36D}
+ {B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9} = {926C0980-31D9-4449-903F-3C756044C28A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23736554-5288-4B30-9710-B4D9880BCF0B}
diff --git a/README.markdown b/README.markdown
index 7717ba80..3be53a00 100644
--- a/README.markdown
+++ b/README.markdown
@@ -22,6 +22,7 @@ Maintained by Luke Lowrey - follow me on twitter **[@lukencode](https://twitter
* [FluentEmail.Mailgun](src/Senders/FluentEmail.Mailgun) - Send emails via MailGun's REST API.
* [FluentEmail.SendGrid](src/Senders/FluentEmail.SendGrid) - Send email via the SendGrid API.
+* [FluentEmail.MailPace](src/Senders/FluentEmail.MailPace) - Send emails via the [MailPace](https://www.mailpace.com/) REST API.
* [FluentEmail.Mailtrap](src/Senders/FluentEmail.Mailtrap) - Send emails to Mailtrap. Uses [FluentEmail.Smtp](src/Senders/FluentEmail.Smtp) for delivery.
* [FluentEmail.MailKit](src/Senders/FluentEmail.MailKit) - Send emails using the [MailKit](https://github.com/jstedfast/MailKit) email library.
@@ -159,5 +160,4 @@ var email = new Email("bob@hotmail.com")
.UsingTemplateFromEmbedded("Example.Project.Namespace.template-name.cshtml",
new { Name = "Bob" },
TypeFromYourEmbeddedAssembly.GetType().GetTypeInfo().Assembly);
-```
-
+```
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/FluentEmail.MailPace.csproj b/src/Senders/FluentEmail.MailPace/FluentEmail.MailPace.csproj
new file mode 100644
index 00000000..dd3dc68a
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/FluentEmail.MailPace.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Send emails via MailPace using their REST API
+ Fluent Email - MailPace
+ Luke Lowrey;Ben Cull;Github Contributors
+ $(PackageTags);smtp
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/FluentEmailMailPaceBuilderExtensions.cs b/src/Senders/FluentEmail.MailPace/FluentEmailMailPaceBuilderExtensions.cs
new file mode 100644
index 00000000..6023605e
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/FluentEmailMailPaceBuilderExtensions.cs
@@ -0,0 +1,18 @@
+using FluentEmail.Core.Interfaces;
+using FluentEmail.MailPace;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class FluentEmailMailPaceBuilderExtensions
+ {
+ public static FluentEmailServicesBuilder AddMailPaceSender(
+ this FluentEmailServicesBuilder builder,
+ string serverToken)
+ {
+ builder.Services.TryAdd(ServiceDescriptor.Scoped(_ => new MailPaceSender(serverToken)));
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/MailPaceAttachment.cs b/src/Senders/FluentEmail.MailPace/MailPaceAttachment.cs
new file mode 100644
index 00000000..459712c4
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/MailPaceAttachment.cs
@@ -0,0 +1,11 @@
+using Newtonsoft.Json;
+
+namespace FluentEmail.MailPace;
+
+public class MailPaceAttachment
+{
+ [JsonProperty("name")] public string Name { get; set; }
+ [JsonProperty("content")] public string Content { get; set; }
+ [JsonProperty("content_type")] public string ContentType { get; set; }
+ [JsonProperty("cid", NullValueHandling = NullValueHandling.Ignore)] public string Cid { get; set; }
+}
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/MailPaceResponse.cs b/src/Senders/FluentEmail.MailPace/MailPaceResponse.cs
new file mode 100644
index 00000000..00ef2c0d
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/MailPaceResponse.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace FluentEmail.MailPace;
+
+public class MailPaceResponse
+{
+ [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] public string Id { get; set; }
+ [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] public string Status { get; set; }
+ [JsonProperty("error")] public string Error { get; set; }
+ [JsonProperty("errors")] public Dictionary> Errors { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/MailPaceSendRequest.cs b/src/Senders/FluentEmail.MailPace/MailPaceSendRequest.cs
new file mode 100644
index 00000000..97cd02de
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/MailPaceSendRequest.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace FluentEmail.MailPace;
+
+public class MailPaceSendRequest
+{
+ [JsonProperty("from")] public string From { get; set; }
+ [JsonProperty("to")] public string To { get; set; }
+ [JsonProperty("cc", NullValueHandling = NullValueHandling.Ignore)] public string Cc { get; set; }
+ [JsonProperty("bcc", NullValueHandling = NullValueHandling.Ignore)] public string Bcc { get; set; }
+ [JsonProperty("subject")] public string Subject { get; set; }
+ [JsonProperty("htmlbody", NullValueHandling = NullValueHandling.Ignore)] public string HtmlBody { get; set; }
+ [JsonProperty("textbody", NullValueHandling = NullValueHandling.Ignore)] public string TextBody { get; set; }
+ [JsonProperty("replyto", NullValueHandling = NullValueHandling.Ignore)] public string ReplyTo { get; set; }
+ [JsonProperty("attachments")] public List Attachments { get; set; } = new(0);
+ [JsonProperty("tags")] public List Tags { get; set; } = new(0);
+}
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/MailPaceSender.cs b/src/Senders/FluentEmail.MailPace/MailPaceSender.cs
new file mode 100644
index 00000000..8e13ad30
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/MailPaceSender.cs
@@ -0,0 +1,143 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentEmail.Core;
+using FluentEmail.Core.Interfaces;
+using FluentEmail.Core.Models;
+using Newtonsoft.Json;
+
+namespace FluentEmail.MailPace;
+
+public class MailPaceSender : ISender, IDisposable
+{
+ private readonly HttpClient _httpClient;
+
+ public MailPaceSender(string serverToken)
+ {
+ _httpClient = new HttpClient();
+ _httpClient.DefaultRequestHeaders.Add("MailPace-Server-Token", serverToken);
+ _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ }
+
+ public SendResponse Send(IFluentEmail email, CancellationToken? token = null) =>
+ SendAsync(email, token).GetAwaiter().GetResult();
+
+ public async Task SendAsync(IFluentEmail email, CancellationToken? token = null)
+ {
+ var sendRequest = BuildSendRequestFor(email);
+
+ var content = new StringContent(JsonConvert.SerializeObject(sendRequest), Encoding.UTF8, "application/json");
+
+ var response = await _httpClient.PostAsync("https://app.mailpace.com/api/v1/send", content)
+ .ConfigureAwait(false);
+
+ var mailPaceResponse = await TryOrNull(async () => JsonConvert.DeserializeObject(
+ await response.Content.ReadAsStringAsync())) ?? new MailPaceResponse();
+
+ if (response.IsSuccessStatusCode)
+ {
+ return new SendResponse { MessageId = mailPaceResponse.Id };
+ }
+ else
+ {
+ var result = new SendResponse();
+
+ if (!string.IsNullOrEmpty(mailPaceResponse.Error))
+ {
+ result.ErrorMessages.Add(mailPaceResponse.Error);
+ }
+
+ if (mailPaceResponse.Errors != null && mailPaceResponse.Errors.Count != 0)
+ {
+ result.ErrorMessages.AddRange(mailPaceResponse.Errors
+ .Select(it => $"{it.Key}: {string.Join("; ", it.Value)}"));
+ }
+
+ if (!result.ErrorMessages.Any())
+ {
+ result.ErrorMessages.Add(response.ReasonPhrase ?? "An unknown error has occurred.");
+ }
+
+ return result;
+ }
+ }
+
+ private static MailPaceSendRequest BuildSendRequestFor(IFluentEmail email)
+ {
+ var sendRequest = new MailPaceSendRequest
+ {
+ From = $"{email.Data.FromAddress.Name} <{email.Data.FromAddress.EmailAddress}>",
+ To = string.Join(",", email.Data.ToAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress)),
+ Subject = email.Data.Subject
+ };
+
+ if (email.Data.CcAddresses.Any())
+ {
+ sendRequest.Cc = string.Join(",", email.Data.CcAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress));
+ }
+
+ if (email.Data.BccAddresses.Any())
+ {
+ sendRequest.Bcc = string.Join(",", email.Data.BccAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress));
+ }
+
+ if (email.Data.ReplyToAddresses.Any())
+ {
+ sendRequest.ReplyTo = string.Join(",", email.Data.ReplyToAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress));
+ }
+
+ if (email.Data.IsHtml)
+ {
+ sendRequest.HtmlBody = email.Data.Body;
+ if (!string.IsNullOrEmpty(email.Data.PlaintextAlternativeBody))
+ {
+ sendRequest.TextBody = email.Data.PlaintextAlternativeBody;
+ }
+ }
+ else
+ {
+ sendRequest.TextBody = email.Data.Body;
+ }
+
+ if (email.Data.Tags.Any())
+ {
+ sendRequest.Tags.AddRange(email.Data.Tags);
+ }
+
+ if (email.Data.Attachments.Any())
+ {
+ sendRequest.Attachments.AddRange(
+ email.Data.Attachments.Select(it => new MailPaceAttachment
+ {
+ Name = it.Filename,
+ Content = it.Data.ConvertToBase64(),
+ ContentType = it.ContentType ?? Path.GetExtension(it.Filename), // jpeg, jpg, png, gif, txt, pdf, docx, xlsx, pptx, csv, att, ics, ical, html, zip
+ Cid = it.IsInline ? it.ContentId : null
+ }));
+ }
+
+ return sendRequest;
+ }
+
+ private async Task TryOrNull(Func> method)
+ {
+ try
+ {
+ return await method();
+ }
+ catch
+ {
+ return default;
+ }
+ }
+
+ public void Dispose()
+ {
+ _httpClient.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Senders/FluentEmail.MailPace/StreamExtensions.cs b/src/Senders/FluentEmail.MailPace/StreamExtensions.cs
new file mode 100644
index 00000000..420044a3
--- /dev/null
+++ b/src/Senders/FluentEmail.MailPace/StreamExtensions.cs
@@ -0,0 +1,23 @@
+using System;
+using System.IO;
+
+namespace FluentEmail.MailPace;
+
+public static class StreamExtensions
+{
+ public static string ConvertToBase64(this Stream stream)
+ {
+ if (stream is MemoryStream memoryStream)
+ {
+ return Convert.ToBase64String(memoryStream.ToArray());
+ }
+
+ var bytes = new Byte[(int)stream.Length];
+
+ stream.Seek(0, SeekOrigin.Begin);
+ // ReSharper disable once MustUseReturnValue
+ stream.Read(bytes, 0, (int)stream.Length);
+
+ return Convert.ToBase64String(bytes);
+ }
+}
\ No newline at end of file