Browse Source

Migrate to mailkit.

pull/613/head
Sebastian 5 years ago
parent
commit
a2fc908f9f
  1. 4
      backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs
  2. 60
      backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs
  3. 3
      backend/src/Squidex.Infrastructure/Email/IEmailSender.cs
  4. 80
      backend/src/Squidex.Infrastructure/Email/SmtpEmailSender.cs
  5. 1
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

4
backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs

@ -31,10 +31,6 @@ namespace Squidex.Extensions.Actions.Email
[DataType(DataType.Text)] [DataType(DataType.Text)]
public int ServerPort { get; set; } public int ServerPort { get; set; }
[Display(Name = "Use SSL", Description = "Specify whether the SMPT client uses Secure Sockets Layer (SSL) to encrypt the connection.")]
[DataType(DataType.Text)]
public bool ServerUseSsl { get; set; }
[LocalizedRequired] [LocalizedRequired]
[Display(Name = "Username", Description = "The username for the SMTP server.")] [Display(Name = "Username", Description = "The username for the SMTP server.")]
[DataType(DataType.Text)] [DataType(DataType.Text)]

60
backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs

@ -5,12 +5,11 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Text;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
@ -28,7 +27,6 @@ namespace Squidex.Extensions.Actions.Email
var ruleJob = new EmailJob var ruleJob = new EmailJob
{ {
ServerHost = action.ServerHost, ServerHost = action.ServerHost,
ServerUseSsl = action.ServerUseSsl,
ServerPassword = action.ServerPassword, ServerPassword = action.ServerPassword,
ServerPort = action.ServerPort, ServerPort = action.ServerPort,
ServerUsername = await FormatAsync(action.ServerUsername, @event), ServerUsername = await FormatAsync(action.ServerUsername, @event),
@ -45,47 +43,31 @@ namespace Squidex.Extensions.Actions.Email
protected override async Task<Result> ExecuteJobAsync(EmailJob job, CancellationToken ct = default) protected override async Task<Result> ExecuteJobAsync(EmailJob job, CancellationToken ct = default)
{ {
await CheckConnectionAsync(job, ct); using (var smtpClient = new SmtpClient())
using (var client = new SmtpClient(job.ServerHost, job.ServerPort)
{ {
Credentials = new NetworkCredential( await smtpClient.ConnectAsync(job.ServerHost, job.ServerPort, cancellationToken: ct);
job.ServerUsername,
job.ServerPassword),
EnableSsl = job.ServerUseSsl await smtpClient.AuthenticateAsync(job.ServerUsername, job.ServerPassword, ct);
})
{
using (ct.Register(client.SendAsyncCancel))
{
await client.SendMailAsync(
job.MessageFrom,
job.MessageTo,
job.MessageSubject,
job.MessageBody,
ct);
}
}
return Result.Complete(); var smtpMessage = new MimeMessage();
}
private static async Task CheckConnectionAsync(EmailJob job, CancellationToken ct) smtpMessage.From.Add(MailboxAddress.Parse(
{ job.MessageFrom));
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
var tcs = new TaskCompletionSource<IAsyncResult>();
socket.BeginConnect(job.ServerHost, job.ServerPort, tcs.SetResult, null); smtpMessage.To.Add(MailboxAddress.Parse(
job.MessageTo));
using (ct.Register(() => smtpMessage.Body = new TextPart(TextFormat.Html)
{ {
tcs.TrySetException(new OperationCanceledException($"Failed to establish a connection to {job.ServerHost}:{job.ServerPort}")); Text = job.MessageBody
})) };
{
await tcs.Task; smtpMessage.Subject = job.MessageSubject;
}
await smtpClient.SendAsync(smtpMessage, ct);
} }
return Result.Complete();
} }
} }
@ -99,8 +81,6 @@ namespace Squidex.Extensions.Actions.Email
public string ServerPassword { get; set; } public string ServerPassword { get; set; }
public bool ServerUseSsl { get; set; }
public string MessageFrom { get; set; } public string MessageFrom { get; set; }
public string MessageTo { get; set; } public string MessageTo { get; set; }

3
backend/src/Squidex.Infrastructure/Email/IEmailSender.cs

@ -5,12 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Infrastructure.Email namespace Squidex.Infrastructure.Email
{ {
public interface IEmailSender public interface IEmailSender
{ {
Task SendAsync(string recipient, string subject, string body); Task SendAsync(string recipient, string subject, string body, CancellationToken ct = default);
} }
} }

80
backend/src/Squidex.Infrastructure/Email/SmtpEmailSender.cs

@ -5,15 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MimeKit;
using MimeKit.Text;
namespace Squidex.Infrastructure.Email namespace Squidex.Infrastructure.Email
{ {
@ -23,56 +22,42 @@ namespace Squidex.Infrastructure.Email
private readonly SmtpOptions options; private readonly SmtpOptions options;
private readonly ObjectPool<SmtpClient> clientPool; private readonly ObjectPool<SmtpClient> clientPool;
internal sealed class SmtpClientPolicy : PooledObjectPolicy<SmtpClient>
{
private readonly SmtpOptions options;
public SmtpClientPolicy(SmtpOptions options)
{
this.options = options;
}
public override SmtpClient Create()
{
return new SmtpClient(options.Server, options.Port)
{
Credentials = new NetworkCredential(
options.Username,
options.Password),
EnableSsl = options.EnableSsl,
Timeout = options.Timeout
};
}
public override bool Return(SmtpClient obj)
{
return true;
}
}
public SmtpEmailSender(IOptions<SmtpOptions> options) public SmtpEmailSender(IOptions<SmtpOptions> options)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
this.options = options.Value; this.options = options.Value;
clientPool = new DefaultObjectPoolProvider().Create(new SmtpClientPolicy(options.Value)); clientPool = new DefaultObjectPoolProvider().Create(new DefaultPooledObjectPolicy<SmtpClient>());
} }
public async Task SendAsync(string recipient, string subject, string body) public async Task SendAsync(string recipient, string subject, string body, CancellationToken ct = default)
{ {
var smtpClient = clientPool.Get(); var smtpClient = clientPool.Get();
try try
{ {
using (var cts = new CancellationTokenSource(options.Timeout)) using (var timeout = new CancellationTokenSource(options.Timeout))
{
using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct, timeout.Token))
{ {
await CheckConnectionAsync(cts.Token); await EnsureConnectedAsync(smtpClient, combined.Token);
var smtpMessage = new MimeMessage();
smtpMessage.From.Add(MailboxAddress.Parse(
options.Sender));
smtpMessage.To.Add(MailboxAddress.Parse(
recipient));
using (cts.Token.Register(smtpClient.SendAsyncCancel)) smtpMessage.Body = new TextPart(TextFormat.Html)
{ {
await smtpClient.SendMailAsync(options.Sender, recipient, subject, body); Text = body
};
smtpMessage.Subject = subject;
await smtpClient.SendAsync(smtpMessage, ct);
} }
} }
} }
@ -82,21 +67,16 @@ namespace Squidex.Infrastructure.Email
} }
} }
private async Task CheckConnectionAsync(CancellationToken ct) private async Task EnsureConnectedAsync(SmtpClient smtpClient, CancellationToken ct)
{ {
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) if (!smtpClient.IsConnected)
{ {
var tcs = new TaskCompletionSource<IAsyncResult>(); await smtpClient.ConnectAsync(options.Server, options.Port, cancellationToken: ct);
}
socket.BeginConnect(options.Server, options.Port, tcs.SetResult, null);
using (ct.Register(() => if (!smtpClient.IsAuthenticated)
{
tcs.TrySetException(new OperationCanceledException($"Failed to establish a connection to {options.Server}:{options.Port}"));
}))
{ {
await tcs.Task; await smtpClient.AuthenticateAsync(options.Username, options.Password, ct);
}
} }
} }
} }

1
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -14,6 +14,7 @@
<None Remove="NewFolder\**" /> <None Remove="NewFolder\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="2.10.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.1" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="5.0.1" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="5.0.1" />

Loading…
Cancel
Save