From 658630078be9b36d8ee9696122c81c7ee2621397 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 24 Jan 2025 23:06:29 +0100 Subject: [PATCH] Add SMTP server configuration and refactor email activity for improved error handling and attachment processing --- docker/docker-compose.yml | 12 +++ .../Elsa.Email/Activities/SendEmail.cs | 84 ++++++++++++------- .../Mappers/VariableDefinitionMapper.cs | 27 ++++-- 3 files changed, 84 insertions(+), 39 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 76d7d7b40..5340c7c54 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -72,6 +72,18 @@ environment: - PlantUml__RemoteUrl= - ConnectionStrings__DefaultConnection=USER ID=tracelens;PASSWORD=tracelenspass;HOST=postgres;PORT=5432;DATABASE=tracelens;POOLING=true; + + smtp4dev: # Mock SMTP server + image: rnwood/smtp4dev + container_name: smtp4dev + restart: always + ports: + - "3000:80" # Web interface + - "2525:25" # SMTP port + environment: + - ASPNETCORE_URLS=http://+:80 + - Logging__LogLevel__Default=Information + elsa-server: build: diff --git a/src/modules/Elsa.Email/Activities/SendEmail.cs b/src/modules/Elsa.Email/Activities/SendEmail.cs index c1e88afcb..4ec9ca02d 100644 --- a/src/modules/Elsa.Email/Activities/SendEmail.cs +++ b/src/modules/Elsa.Email/Activities/SendEmail.cs @@ -84,8 +84,7 @@ public class SendEmail : Activity /// /// The activity to execute when an error occurs while trying to send the email. /// - [Port] - public IActivity? Error { get; set; } + [Port] public IActivity? Error { get; set; } /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) @@ -99,7 +98,10 @@ public class SendEmail : Activity message.From.Add(MailboxAddress.Parse(from)); message.Subject = Subject.GetOrDefault(context) ?? ""; - var bodyBuilder = new BodyBuilder { HtmlBody = Body.GetOrDefault(context) }; + var bodyBuilder = new BodyBuilder + { + HtmlBody = Body.GetOrDefault(context) + }; await AddAttachmentsAsync(context, bodyBuilder, cancellationToken); message.Body = bodyBuilder.ToMessageBody(); @@ -119,7 +121,10 @@ public class SendEmail : Activity catch (Exception e) { logger.LogWarning(e, "Error while sending email message"); - context.AddExecutionLogEntry("Error", e.Message, payload: new { e.StackTrace }); + context.AddExecutionLogEntry("Error", e.Message, payload: new + { + e.StackTrace + }); await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); } } @@ -155,38 +160,38 @@ public class SendEmail : Activity await AttachLocalFileAsync(bodyBuilder, path, cancellationToken); break; case byte[] bytes: - { - var fileName = $"Attachment-{++index}"; - bodyBuilder.Attachments.Add(fileName, bytes, ContentType.Parse("application/binary")); - break; - } + { + 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; - } + { + 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); + { + 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); + 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); + else if (emailAttachment.Content is Stream stream) + await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken); - break; - } + break; + } default: - { - var json = JsonSerializer.Serialize(attachmentObject); - var fileName = $"Attachment-{++index}"; - bodyBuilder.Attachments.Add(fileName, Encoding.UTF8.GetBytes(json), ContentType.Parse("application/json")); - break; - } + { + var json = JsonSerializer.Serialize(attachmentObject); + var fileName = $"Attachment-{++index}"; + bodyBuilder.Attachments.Add(fileName, Encoding.UTF8.GetBytes(json), ContentType.Parse("application/json")); + break; + } } } } @@ -203,7 +208,24 @@ public class SendEmail : Activity 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 IEnumerable InterpretAttachmentsModel(object attachments) + { + if (attachments is byte[] bytes) + return new[] + { + bytes + }; + + return attachments is string text + ? new[] + { + text + } + : attachments as IEnumerable ?? new[] + { + attachments + }; + } private void SetRecipientsEmailAddresses(InternetAddressList list, IEnumerable? addresses) { diff --git a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs index 1d0e0e934..48c481de0 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs @@ -18,10 +18,16 @@ public class VariableDefinitionMapper(IWellKnownTypeRegistry wellKnownTypeRegist /// public Variable? Map(VariableDefinition source) { - if (!wellKnownTypeRegistry.TryGetTypeOrDefault(source.TypeName, out var type)) + var aliasedType = wellKnownTypeRegistry.TryGetType(source.TypeName, out var aliasedTypeValue) ? aliasedTypeValue : null; + var type = aliasedType ?? Type.GetType(source.TypeName); + + if(type == null) + { + logger.LogWarning("Failed to resolve the type {TypeName} of variable {VariableName}. Variable will not be mapped.", source.TypeName, source.Name); return null; + } - var valueType = source.IsArray ? type.MakeArrayType() : type; + var valueType = aliasedType ?? (source.IsArray ? type.MakeArrayType() : type); var variableGenericType = typeof(Variable<>).MakeGenericType(valueType); var variable = (Variable)Activator.CreateInstance(variableGenericType)!; @@ -58,15 +64,20 @@ public class VariableDefinitionMapper(IWellKnownTypeRegistry wellKnownTypeRegist { var variableType = source.GetType(); var valueType = variableType.IsConstructedGenericType ? variableType.GetGenericArguments().FirstOrDefault() ?? typeof(object) : typeof(object); + var valueTypeAlias = wellKnownTypeRegistry.TryGetAlias(valueType, out var alias) ? alias : null; + var value = source.Value; + var serializedValue = value.Format(); + var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); + + if(valueTypeAlias != null) + return new(source.Id, source.Name, valueTypeAlias, false, serializedValue, storageDriverTypeName); + var isArray = valueType.IsArray; var isCollection = valueType.IsCollectionType(); var elementValueType = isArray ? valueType.GetElementType() : isCollection ? valueType.GenericTypeArguments[0] : valueType; - var value = source.Value; - var valueTypeAlias = wellKnownTypeRegistry.GetAliasOrDefault(elementValueType); - var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); - var serializedValue = value.Format(); - - return new(source.Id, source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName); + var elementTypeAlias = wellKnownTypeRegistry.GetAliasOrDefault(elementValueType); + + return new(source.Id, source.Name, elementTypeAlias, isArray, serializedValue, storageDriverTypeName); } ///