elsa-core/src/modules/Elsa.Email/Activities/SendEmail.cs
2022-11-24 14:28:29 +01:00

181 lines
7.4 KiB
C#

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));
}
}