Add email module (#3469)
This commit is contained in:
parent
d14cd617f3
commit
cf6c80ab85
7
Elsa.sln
7
Elsa.sln
|
|
@ -116,6 +116,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Composition",
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Persistence.EntityFrameworkCore.SqlServer\Elsa.Persistence.EntityFrameworkCore.SqlServer.csproj", "{555A4306-3BAB-409E-8BB8-3171A5867B2C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Email", "src\modules\Elsa.Email\Elsa.Email.csproj", "{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -294,6 +296,10 @@ Global
|
|||
{555A4306-3BAB-409E-8BB8-3171A5867B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{555A4306-3BAB-409E-8BB8-3171A5867B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{555A4306-3BAB-409E-8BB8-3171A5867B2C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -346,5 +352,6 @@ Global
|
|||
{55AAF940-12DC-4793-805C-992AEF2C1E8D} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{1AA0AEFC-BD58-4284-A6C5-AD15C5C79782} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4}
|
||||
{555A4306-3BAB-409E-8BB8-3171A5867B2C} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
181
src/modules/Elsa.Email/Activities/SendEmail.cs
Normal file
181
src/modules/Elsa.Email/Activities/SendEmail.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
using System.Collections;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Elsa.Email.Models;
|
||||
using Elsa.Email.Options;
|
||||
using Elsa.Email.Services;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
|
||||
namespace Elsa.Email.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// Send an email message.
|
||||
/// </summary>
|
||||
[Activity("Email", "Send an email message.", Kind = ActivityKind.Task)]
|
||||
public class SendEmail : ActivityBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The sender's email address.
|
||||
/// </summary>
|
||||
[Input(Description = "The sender's email address.")]
|
||||
public Input<string?> From { get; set; }
|
||||
|
||||
[Input(Description = "The recipients email addresses.", UIHint = InputUIHints.MultiText)]
|
||||
public Input<ICollection<string>> To { get; set; } = default!;
|
||||
|
||||
[Input(
|
||||
Description = "The cc recipient email addresses.",
|
||||
UIHint = InputUIHints.MultiText,
|
||||
Category = "More")]
|
||||
public Input<ICollection<string>> Cc { get; set; } = default!;
|
||||
|
||||
[Input(
|
||||
Description = "The Bcc recipients email addresses.",
|
||||
UIHint = InputUIHints.MultiText,
|
||||
Category = "More")]
|
||||
public Input<ICollection<string>> Bcc { get; set; } = default!;
|
||||
|
||||
[Input(Description = "The subject of the email message.")]
|
||||
public Input<string?> Subject { get; set; } = default!;
|
||||
|
||||
[Input(
|
||||
Description = "The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.",
|
||||
UIHint = InputUIHints.MultiLine
|
||||
)]
|
||||
public Input<object?> Attachments { get; set; } = default!;
|
||||
|
||||
[Input(
|
||||
Description = "The body of the email message.",
|
||||
UIHint = InputUIHints.MultiLine
|
||||
)]
|
||||
public Input<string?> Body { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The activity to execute when an error occurs while trying to send the email.
|
||||
/// </summary>
|
||||
[Port]
|
||||
public IActivity? Error { get; set; }
|
||||
|
||||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var cancellationToken = context.CancellationToken;
|
||||
var message = new MimeMessage();
|
||||
var options = context.GetRequiredService<IOptions<SmtpOptions>>().Value;
|
||||
var from = string.IsNullOrWhiteSpace(From.TryGet(context)) ? options.DefaultSender : From.Get(context)!;
|
||||
|
||||
message.Sender = MailboxAddress.Parse(from);
|
||||
message.From.Add(MailboxAddress.Parse(from));
|
||||
message.Subject = Subject.TryGet(context);
|
||||
|
||||
var bodyBuilder = new BodyBuilder { HtmlBody = Body.TryGet(context) };
|
||||
await AddAttachmentsAsync(context, bodyBuilder, cancellationToken);
|
||||
|
||||
message.Body = bodyBuilder.ToMessageBody();
|
||||
|
||||
SetRecipientsEmailAddresses(message.To, To.Get(context));
|
||||
SetRecipientsEmailAddresses(message.Cc, Cc.TryGet(context));
|
||||
SetRecipientsEmailAddresses(message.Bcc, Bcc.TryGet(context));
|
||||
|
||||
var smtpService = context.GetRequiredService<ISmtpService>();
|
||||
|
||||
try
|
||||
{
|
||||
await smtpService.SendAsync(message, context.CancellationToken);
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask OnErrorCompletedAsync(ActivityExecutionContext context, ActivityExecutionContext childContext) => await context.CompleteActivityAsync();
|
||||
|
||||
private async Task AddAttachmentsAsync(ActivityExecutionContext context, BodyBuilder bodyBuilder, CancellationToken cancellationToken)
|
||||
{
|
||||
var attachments = Attachments.TryGet(context);
|
||||
|
||||
if (attachments == null || attachments is string s && string.IsNullOrWhiteSpace(s))
|
||||
return;
|
||||
|
||||
var index = 0;
|
||||
var attachmentObjects = InterpretAttachmentsModel(attachments);
|
||||
|
||||
foreach (var attachmentObject in attachmentObjects)
|
||||
{
|
||||
switch (attachmentObject)
|
||||
{
|
||||
case Uri url:
|
||||
await AttachOnlineFileAsync(context, bodyBuilder, url, cancellationToken);
|
||||
break;
|
||||
case string path when path?.Contains("://") == true:
|
||||
await AttachOnlineFileAsync(context, bodyBuilder, new Uri(path), cancellationToken);
|
||||
break;
|
||||
case string path when !string.IsNullOrWhiteSpace(path):
|
||||
await AttachLocalFileAsync(bodyBuilder, path, cancellationToken);
|
||||
break;
|
||||
case byte[] bytes:
|
||||
{
|
||||
var fileName = $"Attachment-{++index}";
|
||||
bodyBuilder.Attachments.Add(fileName, bytes, ContentType.Parse("application/binary"));
|
||||
break;
|
||||
}
|
||||
case Stream stream:
|
||||
{
|
||||
var fileName = $"Attachment-{++index}";
|
||||
await bodyBuilder.Attachments.AddAsync(fileName, stream, ContentType.Parse("application/binary"), cancellationToken);
|
||||
break;
|
||||
}
|
||||
case EmailAttachment emailAttachment:
|
||||
{
|
||||
var fileName = emailAttachment.FileName ?? $"Attachment-{++index}";
|
||||
var contentType = emailAttachment.ContentType ?? "application/binary";
|
||||
var parsedContentType = ContentType.Parse(contentType);
|
||||
|
||||
if (emailAttachment.Content is byte[] bytes)
|
||||
bodyBuilder.Attachments.Add(fileName, bytes, parsedContentType);
|
||||
|
||||
else if (emailAttachment.Content is Stream stream)
|
||||
await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var json = JsonSerializer.Serialize(attachmentObject);
|
||||
var fileName = $"Attachment-{++index}";
|
||||
bodyBuilder.Attachments.Add(fileName, Encoding.UTF8.GetBytes(json), ContentType.Parse("application/json"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AttachLocalFileAsync(BodyBuilder bodyBuilder, string path, CancellationToken cancellationToken) => await bodyBuilder.Attachments.AddAsync(path, cancellationToken);
|
||||
|
||||
private async Task AttachOnlineFileAsync(ActivityExecutionContext context, BodyBuilder bodyBuilder, Uri url, CancellationToken cancellationToken)
|
||||
{
|
||||
var fileName = Path.GetFileName(url.LocalPath);
|
||||
var downloader = context.GetRequiredService<IDownloader>();
|
||||
var response = await downloader.DownloadAsync(url, cancellationToken);
|
||||
var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
var contentType = response.Content.Headers.ContentType?.MediaType ?? "application/binary";
|
||||
await bodyBuilder.Attachments.AddAsync(fileName, contentStream, ContentType.Parse(contentType), cancellationToken);
|
||||
}
|
||||
|
||||
private IEnumerable InterpretAttachmentsModel(object attachments) => attachments is string text ? new[] { text } : attachments is IEnumerable enumerable ? enumerable : new[] { attachments };
|
||||
|
||||
private void SetRecipientsEmailAddresses(InternetAddressList list, IEnumerable<string>? addresses)
|
||||
{
|
||||
if (addresses == null)
|
||||
return;
|
||||
|
||||
list.AddRange(addresses.Select(MailboxAddress.Parse));
|
||||
}
|
||||
}
|
||||
25
src/modules/Elsa.Email/Elsa.Email.csproj
Normal file
25
src/modules/Elsa.Email/Elsa.Email.csproj
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props"/>
|
||||
<Import Project="..\..\..\configureawait.props"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Description>
|
||||
Provides a SendEmail activity that lets you send emails from a workflow.
|
||||
</Description>
|
||||
<PackageTags>elsa module activities email</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailKit" Version="3.4.2"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Mediator\Elsa.Mediator.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
19
src/modules/Elsa.Email/Extensions/ModuleExtensions.cs
Normal file
19
src/modules/Elsa.Email/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Email.Features;
|
||||
using Elsa.Features.Services;
|
||||
|
||||
namespace Elsa.Email.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to install and configure email related features.
|
||||
/// </summary>
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="EmailFeature"/> feature to the system.
|
||||
/// </summary>
|
||||
public static IModule UseEmail(this IModule configuration, Action<EmailFeature>? configure = default)
|
||||
{
|
||||
configuration.Configure(configure);
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
37
src/modules/Elsa.Email/Features/EmailFeature.cs
Normal file
37
src/modules/Elsa.Email/Features/EmailFeature.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Elsa.Email.Implementations;
|
||||
using Elsa.Email.Options;
|
||||
using Elsa.Email.Services;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Email.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Setup email features.
|
||||
/// </summary>
|
||||
public class EmailFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public EmailFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a callback to configure <see cref="SmtpOptions"/>.
|
||||
/// </summary>
|
||||
public Action<SmtpOptions> ConfigureOptions { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// Set a callback for configuring the HTTP client use by the default implementation of <see cref="IDownloader"/>.
|
||||
/// </summary>
|
||||
public Action<IServiceProvider, HttpClient> ConfigureDownloaderHttpClient { get; set; } = (_, _) => { };
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services.Configure(ConfigureOptions);
|
||||
Services.AddSingleton<ISmtpService, MailKitSmtpService>();
|
||||
Services.AddHttpClient<IDownloader, DefaultDownloader>(ConfigureDownloaderHttpClient);
|
||||
}
|
||||
}
|
||||
3
src/modules/Elsa.Email/FodyWeavers.xml
Normal file
3
src/modules/Elsa.Email/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
24
src/modules/Elsa.Email/Implementations/DefaultDownloader.cs
Normal file
24
src/modules/Elsa.Email/Implementations/DefaultDownloader.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.Email.Services;
|
||||
|
||||
namespace Elsa.Email.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Download files from the Internet.
|
||||
/// </summary>
|
||||
public class DefaultDownloader : IDownloader
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public DefaultDownloader(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download whatever is returned at the specified URL.
|
||||
/// </summary>
|
||||
public async Task<HttpResponseMessage> DownloadAsync(Uri url, CancellationToken cancellationToken = default) => await _httpClient.GetAsync(url, cancellationToken);
|
||||
}
|
||||
73
src/modules/Elsa.Email/Implementations/MailKitSmtpService.cs
Normal file
73
src/modules/Elsa.Email/Implementations/MailKitSmtpService.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Elsa.Email.Options;
|
||||
using Elsa.Email.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Elsa.Email.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// A MailKit implementation of <see cref="ISmtpService"/>.
|
||||
/// </summary>
|
||||
public class MailKitSmtpService : ISmtpService
|
||||
{
|
||||
private readonly SmtpOptions _options;
|
||||
private readonly ILogger<MailKitSmtpService> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public MailKitSmtpService(
|
||||
IOptions<SmtpOptions> options,
|
||||
ILogger<MailKitSmtpService> logger
|
||||
)
|
||||
{
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the specified message.
|
||||
/// </summary>
|
||||
public async Task SendAsync(MimeMessage message, CancellationToken cancellationToken) => await SendMessage(message, cancellationToken);
|
||||
|
||||
private async Task SendMessage(MimeMessage message, CancellationToken cancellationToken)
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
client.ServerCertificateValidationCallback = CertificateValidationCallback;
|
||||
|
||||
await client.ConnectAsync(_options.Host, _options.Port, _options.SecureSocketOptions, cancellationToken);
|
||||
|
||||
if (_options.RequireCredentials)
|
||||
await client.AuthenticateAsync(_options.UserName ?? "", _options.Password ?? "", cancellationToken);
|
||||
|
||||
await client.SendAsync(message, cancellationToken);
|
||||
await client.DisconnectAsync(true, cancellationToken);
|
||||
}
|
||||
|
||||
private bool CertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
return true;
|
||||
|
||||
_logger.LogError(
|
||||
"SMTP Server's certificate {CertificateSubject} issued by {CertificateIssuer} with thumbprint {CertificateThumbprint} and expiration date {CertificateExpirationDate} is considered invalid with {SslPolicyErrors} policy errors",
|
||||
certificate?.Subject,
|
||||
certificate?.Issuer,
|
||||
certificate?.GetCertHashString(),
|
||||
certificate?.GetExpirationDateString(),
|
||||
sslPolicyErrors);
|
||||
|
||||
if (!sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) || chain?.ChainStatus == null)
|
||||
return false;
|
||||
|
||||
foreach (var chainStatus in chain.ChainStatus)
|
||||
_logger.LogError("Status: {Status} - {StatusInformation}", chainStatus.Status, chainStatus.StatusInformation);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
4
src/modules/Elsa.Email/Models/EmailAttachment.cs
Normal file
4
src/modules/Elsa.Email/Models/EmailAttachment.cs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
namespace Elsa.Email.Models
|
||||
{
|
||||
public record EmailAttachment(object Content, string? FileName, string? ContentType);
|
||||
}
|
||||
8
src/modules/Elsa.Email/Options/SmtpEncryptionMethod.cs
Normal file
8
src/modules/Elsa.Email/Options/SmtpEncryptionMethod.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace Elsa.Email.Options;
|
||||
|
||||
public enum SmtpEncryptionMethod
|
||||
{
|
||||
None = 0,
|
||||
SslTlS = 1,
|
||||
StartTls = 2
|
||||
}
|
||||
44
src/modules/Elsa.Email/Options/SmtpOptions.cs
Normal file
44
src/modules/Elsa.Email/Options/SmtpOptions.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using MailKit.Security;
|
||||
|
||||
namespace Elsa.Email.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Options to configure the SMTP client with.
|
||||
/// </summary>
|
||||
public class SmtpOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The default sender address when no sender is specified while sending emails.
|
||||
/// </summary>
|
||||
public string DefaultSender { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The SMTP server IP address or hostname.
|
||||
/// </summary>
|
||||
public string? Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The SMTP server port
|
||||
/// </summary>
|
||||
public int Port { get; set; } = 25;
|
||||
|
||||
/// <summary>
|
||||
/// Secure socket options.
|
||||
/// </summary>
|
||||
public SecureSocketOptions SecureSocketOptions { get; set; } = SecureSocketOptions.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// True if the SMTP host requires credentials.
|
||||
/// </summary>
|
||||
public bool RequireCredentials { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username to authenticate with.
|
||||
/// </summary>
|
||||
public string? UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The password to authenticate with.
|
||||
/// </summary>
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
12
src/modules/Elsa.Email/Services/IDownloader.cs
Normal file
12
src/modules/Elsa.Email/Services/IDownloader.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace Elsa.Email.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Download files from the Internet.
|
||||
/// </summary>
|
||||
public interface IDownloader
|
||||
{
|
||||
/// <summary>
|
||||
/// Download whatever is returned at the specified URL.
|
||||
/// </summary>
|
||||
Task<HttpResponseMessage> DownloadAsync(Uri url, CancellationToken cancellationToken = default);
|
||||
}
|
||||
14
src/modules/Elsa.Email/Services/ISmtpService.cs
Normal file
14
src/modules/Elsa.Email/Services/ISmtpService.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using MimeKit;
|
||||
|
||||
namespace Elsa.Email.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Use this service to send emails.
|
||||
/// </summary>
|
||||
public interface ISmtpService
|
||||
{
|
||||
/// <summary>
|
||||
/// Send the specified <see cref="MimeMessage"/>.
|
||||
/// </summary>
|
||||
Task SendAsync(MimeMessage message, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||
|
||||
namespace Elsa.Expressions.Extensions;
|
||||
|
||||
public static class DependencyInjectionExtensions
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IModule UseExpressions(this IModule configuration, Action<ExpressionsFeature>? configure = default)
|
||||
{
|
||||
Loading…
Reference in a new issue