Skip to content

Commit

Permalink
Add MailPace Sender - lukencode#377
Browse files Browse the repository at this point in the history
  • Loading branch information
maartenba committed Nov 20, 2023
1 parent 3b44aed commit 095b2d3
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 1 deletion.
7 changes: 7 additions & 0 deletions FluentEmail.sln
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Ac
.github\workflows\publish-packages.yml = .github\workflows\publish-packages.yml
EndProjectSection
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
Expand Down Expand Up @@ -116,6 +118,10 @@ Global
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE}.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
Expand All @@ -139,6 +145,7 @@ Global
{7A36357D-5CE6-4E90-BE5F-8E51553F6846} = {926C0980-31D9-4449-903F-3C756044C28A}
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE} = {926C0980-31D9-4449-903F-3C756044C28A}
{1B6987D8-3785-4A78-8637-40E321CC2877} = {7B3C8C77-C54A-4F9E-A241-676AC01E49BB}
{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9} = {926C0980-31D9-4449-903F-3C756044C28A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23736554-5288-4B30-9710-B4D9880BCF0B}
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Original blog post here for a detailed guide [A complete guide to send email in
- [FluentEmail.SendGrid](src/Senders/FluentEmail.SendGrid) - Send email via the SendGrid 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.
- [FluentEmail.MailPace](src/Senders/FluentEmail.MailPace) - Send emails via the [MailPace](https://www.mailpace.com/) REST API.
- [FluentEmail.MailerSend](https://github.com/marcoatribeiro/FluentEmail.MailerSend) - Send email via [MailerSend](https://www.mailersend.com/)'s API.

## Basic Usage
Expand Down Expand Up @@ -166,4 +167,4 @@ var email = new Email("[email protected]")
.UsingTemplateFromEmbedded("Example.Project.Namespace.template-name.cshtml",
new { Name = "Bob" },
TypeFromYourEmbeddedAssembly.GetType().GetTypeInfo().Assembly);
```
```
20 changes: 20 additions & 0 deletions src/Senders/FluentEmail.MailPace/FluentEmail.MailPace.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Send emails via MailPace using their REST API</Description>
<AssemblyTitle>Fluent Email - MailPace</AssemblyTitle>
<PackageTags>$(PackageTags);mailpace</PackageTags>
<TargetFramework>net6.0</TargetFramework>
<PackageId>jcamp.$(AssemblyName)</PackageId>
<Product>jcamp.$(AssemblyName)</Product>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\FluentEmail.Core\FluentEmail.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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<ISender>(_ => new MailPaceSender(serverToken)));
return builder;
}
}
}
11 changes: 11 additions & 0 deletions src/Senders/FluentEmail.MailPace/MailPaceAttachment.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
12 changes: 12 additions & 0 deletions src/Senders/FluentEmail.MailPace/MailPaceResponse.cs
Original file line number Diff line number Diff line change
@@ -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<string, List<string>> Errors { get; set; } = new();
}
18 changes: 18 additions & 0 deletions src/Senders/FluentEmail.MailPace/MailPaceSendRequest.cs
Original file line number Diff line number Diff line change
@@ -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<MailPaceAttachment> Attachments { get; set; } = new(0);
[JsonProperty("tags")] public List<string> Tags { get; set; } = new(0);
}
143 changes: 143 additions & 0 deletions src/Senders/FluentEmail.MailPace/MailPaceSender.cs
Original file line number Diff line number Diff line change
@@ -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<SendResponse> 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<MailPaceResponse>(
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<T> TryOrNull<T>(Func<Task<T>> method)
{
try
{
return await method();
}
catch
{
return default;
}
}

public void Dispose()
{
_httpClient.Dispose();
}
}
23 changes: 23 additions & 0 deletions src/Senders/FluentEmail.MailPace/StreamExtensions.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 095b2d3

Please sign in to comment.