Skip to content

Commit 11632fb

Browse files
authored
Add MailPace Sender - lukencode#377 (#15)
1 parent 3b44aed commit 11632fb

9 files changed

+254
-1
lines changed

FluentEmail.sln

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Ac
5454
.github\workflows\publish-packages.yml = .github\workflows\publish-packages.yml
5555
EndProjectSection
5656
EndProject
57+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentEmail.MailPace", "src\Senders\FluentEmail.MailPace\FluentEmail.MailPace.csproj", "{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}"
58+
EndProject
5759
Global
5860
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5961
Debug|Any CPU = Debug|Any CPU
@@ -116,6 +118,10 @@ Global
116118
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
117119
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
118120
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE}.Release|Any CPU.Build.0 = Release|Any CPU
121+
{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122+
{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
123+
{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
124+
{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9}.Release|Any CPU.Build.0 = Release|Any CPU
119125
EndGlobalSection
120126
GlobalSection(SolutionProperties) = preSolution
121127
HideSolutionNode = FALSE
@@ -139,6 +145,7 @@ Global
139145
{7A36357D-5CE6-4E90-BE5F-8E51553F6846} = {926C0980-31D9-4449-903F-3C756044C28A}
140146
{089AADB3-D9DF-4EAF-9D0E-AF343AF310DE} = {926C0980-31D9-4449-903F-3C756044C28A}
141147
{1B6987D8-3785-4A78-8637-40E321CC2877} = {7B3C8C77-C54A-4F9E-A241-676AC01E49BB}
148+
{B7A5D5CF-9804-41CA-BF0A-16D5252CE7A9} = {926C0980-31D9-4449-903F-3C756044C28A}
142149
EndGlobalSection
143150
GlobalSection(ExtensibilityGlobals) = postSolution
144151
SolutionGuid = {23736554-5288-4B30-9710-B4D9880BCF0B}

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Original blog post here for a detailed guide [A complete guide to send email in
3131
- [FluentEmail.SendGrid](src/Senders/FluentEmail.SendGrid) - Send email via the SendGrid API.
3232
- [FluentEmail.Mailtrap](src/Senders/FluentEmail.Mailtrap) - Send emails to Mailtrap. Uses [FluentEmail.Smtp](src/Senders/FluentEmail.Smtp) for delivery.
3333
- [FluentEmail.MailKit](src/Senders/FluentEmail.MailKit) - Send emails using the [MailKit](https://github.com/jstedfast/MailKit) email library.
34+
- [FluentEmail.MailPace](src/Senders/FluentEmail.MailPace) - Send emails via the [MailPace](https://www.mailpace.com/) REST API.
3435
- [FluentEmail.MailerSend](https://github.com/marcoatribeiro/FluentEmail.MailerSend) - Send email via [MailerSend](https://www.mailersend.com/)'s API.
3536

3637
## Basic Usage
@@ -166,4 +167,4 @@ var email = new Email("[email protected]")
166167
.UsingTemplateFromEmbedded("Example.Project.Namespace.template-name.cshtml",
167168
new { Name = "Bob" },
168169
TypeFromYourEmbeddedAssembly.GetType().GetTypeInfo().Assembly);
169-
```
170+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>Send emails via MailPace using their REST API</Description>
5+
<AssemblyTitle>Fluent Email - MailPace</AssemblyTitle>
6+
<PackageTags>$(PackageTags);mailpace</PackageTags>
7+
<TargetFramework>net6.0</TargetFramework>
8+
<PackageId>jcamp.$(AssemblyName)</PackageId>
9+
<Product>jcamp.$(AssemblyName)</Product>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\FluentEmail.Core\FluentEmail.Core.csproj" />
18+
</ItemGroup>
19+
20+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using FluentEmail.Core.Interfaces;
2+
using FluentEmail.MailPace;
3+
using Microsoft.Extensions.DependencyInjection.Extensions;
4+
5+
// ReSharper disable once CheckNamespace
6+
namespace Microsoft.Extensions.DependencyInjection
7+
{
8+
public static class FluentEmailMailPaceBuilderExtensions
9+
{
10+
public static FluentEmailServicesBuilder AddMailPaceSender(
11+
this FluentEmailServicesBuilder builder,
12+
string serverToken)
13+
{
14+
builder.Services.TryAdd(ServiceDescriptor.Scoped<ISender>(_ => new MailPaceSender(serverToken)));
15+
return builder;
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Newtonsoft.Json;
2+
3+
namespace FluentEmail.MailPace;
4+
5+
public class MailPaceAttachment
6+
{
7+
[JsonProperty("name")] public string Name { get; set; }
8+
[JsonProperty("content")] public string Content { get; set; }
9+
[JsonProperty("content_type")] public string ContentType { get; set; }
10+
[JsonProperty("cid", NullValueHandling = NullValueHandling.Ignore)] public string Cid { get; set; }
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace FluentEmail.MailPace;
5+
6+
public class MailPaceResponse
7+
{
8+
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] public string Id { get; set; }
9+
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] public string Status { get; set; }
10+
[JsonProperty("error")] public string Error { get; set; }
11+
[JsonProperty("errors")] public Dictionary<string, List<string>> Errors { get; set; } = new();
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace FluentEmail.MailPace;
5+
6+
public class MailPaceSendRequest
7+
{
8+
[JsonProperty("from")] public string From { get; set; }
9+
[JsonProperty("to")] public string To { get; set; }
10+
[JsonProperty("cc", NullValueHandling = NullValueHandling.Ignore)] public string Cc { get; set; }
11+
[JsonProperty("bcc", NullValueHandling = NullValueHandling.Ignore)] public string Bcc { get; set; }
12+
[JsonProperty("subject")] public string Subject { get; set; }
13+
[JsonProperty("htmlbody", NullValueHandling = NullValueHandling.Ignore)] public string HtmlBody { get; set; }
14+
[JsonProperty("textbody", NullValueHandling = NullValueHandling.Ignore)] public string TextBody { get; set; }
15+
[JsonProperty("replyto", NullValueHandling = NullValueHandling.Ignore)] public string ReplyTo { get; set; }
16+
[JsonProperty("attachments")] public List<MailPaceAttachment> Attachments { get; set; } = new(0);
17+
[JsonProperty("tags")] public List<string> Tags { get; set; } = new(0);
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Net.Http.Headers;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using FluentEmail.Core;
10+
using FluentEmail.Core.Interfaces;
11+
using FluentEmail.Core.Models;
12+
using Newtonsoft.Json;
13+
14+
namespace FluentEmail.MailPace;
15+
16+
public class MailPaceSender : ISender, IDisposable
17+
{
18+
private readonly HttpClient _httpClient;
19+
20+
public MailPaceSender(string serverToken)
21+
{
22+
_httpClient = new HttpClient();
23+
_httpClient.DefaultRequestHeaders.Add("MailPace-Server-Token", serverToken);
24+
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
25+
}
26+
27+
public SendResponse Send(IFluentEmail email, CancellationToken? token = null) =>
28+
SendAsync(email, token).GetAwaiter().GetResult();
29+
30+
public async Task<SendResponse> SendAsync(IFluentEmail email, CancellationToken? token = null)
31+
{
32+
var sendRequest = BuildSendRequestFor(email);
33+
34+
var content = new StringContent(JsonConvert.SerializeObject(sendRequest), Encoding.UTF8, "application/json");
35+
36+
var response = await _httpClient.PostAsync("https://app.mailpace.com/api/v1/send", content)
37+
.ConfigureAwait(false);
38+
39+
var mailPaceResponse = await TryOrNull(async () => JsonConvert.DeserializeObject<MailPaceResponse>(
40+
await response.Content.ReadAsStringAsync())) ?? new MailPaceResponse();
41+
42+
if (response.IsSuccessStatusCode)
43+
{
44+
return new SendResponse { MessageId = mailPaceResponse.Id };
45+
}
46+
else
47+
{
48+
var result = new SendResponse();
49+
50+
if (!string.IsNullOrEmpty(mailPaceResponse.Error))
51+
{
52+
result.ErrorMessages.Add(mailPaceResponse.Error);
53+
}
54+
55+
if (mailPaceResponse.Errors != null && mailPaceResponse.Errors.Count != 0)
56+
{
57+
result.ErrorMessages.AddRange(mailPaceResponse.Errors
58+
.Select(it => $"{it.Key}: {string.Join("; ", it.Value)}"));
59+
}
60+
61+
if (!result.ErrorMessages.Any())
62+
{
63+
result.ErrorMessages.Add(response.ReasonPhrase ?? "An unknown error has occurred.");
64+
}
65+
66+
return result;
67+
}
68+
}
69+
70+
private static MailPaceSendRequest BuildSendRequestFor(IFluentEmail email)
71+
{
72+
var sendRequest = new MailPaceSendRequest
73+
{
74+
From = $"{email.Data.FromAddress.Name} <{email.Data.FromAddress.EmailAddress}>",
75+
To = string.Join(",", email.Data.ToAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress)),
76+
Subject = email.Data.Subject
77+
};
78+
79+
if (email.Data.CcAddresses.Any())
80+
{
81+
sendRequest.Cc = string.Join(",", email.Data.CcAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress));
82+
}
83+
84+
if (email.Data.BccAddresses.Any())
85+
{
86+
sendRequest.Bcc = string.Join(",", email.Data.BccAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress));
87+
}
88+
89+
if (email.Data.ReplyToAddresses.Any())
90+
{
91+
sendRequest.ReplyTo = string.Join(",", email.Data.ReplyToAddresses.Select(it => !string.IsNullOrEmpty(it.Name) ? $"{it.Name} <{it.EmailAddress}>" : it.EmailAddress));
92+
}
93+
94+
if (email.Data.IsHtml)
95+
{
96+
sendRequest.HtmlBody = email.Data.Body;
97+
if (!string.IsNullOrEmpty(email.Data.PlaintextAlternativeBody))
98+
{
99+
sendRequest.TextBody = email.Data.PlaintextAlternativeBody;
100+
}
101+
}
102+
else
103+
{
104+
sendRequest.TextBody = email.Data.Body;
105+
}
106+
107+
if (email.Data.Tags.Any())
108+
{
109+
sendRequest.Tags.AddRange(email.Data.Tags);
110+
}
111+
112+
if (email.Data.Attachments.Any())
113+
{
114+
sendRequest.Attachments.AddRange(
115+
email.Data.Attachments.Select(it => new MailPaceAttachment
116+
{
117+
Name = it.Filename,
118+
Content = it.Data.ConvertToBase64(),
119+
ContentType = it.ContentType ?? Path.GetExtension(it.Filename), // jpeg, jpg, png, gif, txt, pdf, docx, xlsx, pptx, csv, att, ics, ical, html, zip
120+
Cid = it.IsInline ? it.ContentId : null
121+
}));
122+
}
123+
124+
return sendRequest;
125+
}
126+
127+
private async Task<T> TryOrNull<T>(Func<Task<T>> method)
128+
{
129+
try
130+
{
131+
return await method();
132+
}
133+
catch
134+
{
135+
return default;
136+
}
137+
}
138+
139+
public void Dispose()
140+
{
141+
_httpClient.Dispose();
142+
}
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace FluentEmail.MailPace;
5+
6+
public static class StreamExtensions
7+
{
8+
public static string ConvertToBase64(this Stream stream)
9+
{
10+
if (stream is MemoryStream memoryStream)
11+
{
12+
return Convert.ToBase64String(memoryStream.ToArray());
13+
}
14+
15+
var bytes = new Byte[(int)stream.Length];
16+
17+
stream.Seek(0, SeekOrigin.Begin);
18+
// ReSharper disable once MustUseReturnValue
19+
stream.Read(bytes, 0, (int)stream.Length);
20+
21+
return Convert.ToBase64String(bytes);
22+
}
23+
}

0 commit comments

Comments
 (0)