Merge pull request #6332 from elsa-workflows/bug/email-attachment-bytes

Add SMTP server configuration and refactor email activity for improved error handling and attachment processing
This commit is contained in:
Sipke Schoorstra 2025-01-24 23:07:04 +01:00 committed by GitHub
commit f9330dfd0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 84 additions and 39 deletions

View file

@ -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:

View file

@ -84,8 +84,7 @@ public class SendEmail : Activity
/// <summary>
/// The activity to execute when an error occurs while trying to send the email.
/// </summary>
[Port]
public IActivity? Error { get; set; }
[Port] public IActivity? Error { get; set; }
/// <inheritdoc />
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<string>? addresses)
{

View file

@ -18,10 +18,16 @@ public class VariableDefinitionMapper(IWellKnownTypeRegistry wellKnownTypeRegist
/// </summary>
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);
}
/// <summary>