diff --git a/src/core/DigitalData.MessagingService.Abstraction/EmailAttachmentContext.cs b/src/core/DigitalData.MessagingService.Abstraction/EmailAttachmentContext.cs new file mode 100644 index 0000000..720d87d --- /dev/null +++ b/src/core/DigitalData.MessagingService.Abstraction/EmailAttachmentContext.cs @@ -0,0 +1,45 @@ +namespace DigitalData.MessagingService.Abstraction; + +/// +/// Represents a single email attachment. +/// +public sealed class EmailAttachmentContext +{ + /// + /// Display name of the attachment (e.g. "invoice.pdf"). + /// +#if NETFRAMEWORK + public string FileName { get; set; } = null!; +#else + public required string FileName { get; init; } +#endif + + /// + /// Raw content of the attachment. + /// +#if NETFRAMEWORK + public byte[] Content { get; set; } = null!; +#else + public required byte[] Content { get; init; } +#endif + + /// + /// MIME content-type (e.g. "application/pdf", "image/png"). + /// Defaults to "application/octet-stream" when not specified. + /// + public string ContentType { get; set; } = "application/octet-stream"; + + /// + /// When the attachment is embedded inline and displayed + /// directly inside the email body via a CID reference (e.g. <img src="cid:logo">). + /// When (default) it appears as a regular downloadable attachment. + /// + public bool IsInline { get; set; } = false; + + /// + /// Content-ID used when is . + /// Reference it in HTML body as cid:{ContentId}. + /// Auto-generated from when left empty. + /// + public string? ContentId { get; set; } +} diff --git a/src/core/DigitalData.MessagingService.Abstraction/EmailContext.cs b/src/core/DigitalData.MessagingService.Abstraction/EmailContext.cs index 65d6127..919e444 100644 --- a/src/core/DigitalData.MessagingService.Abstraction/EmailContext.cs +++ b/src/core/DigitalData.MessagingService.Abstraction/EmailContext.cs @@ -45,4 +45,11 @@ public record EmailContext #else public bool IsHtml { get; init; } = true; #endif + + /// + /// Optional list of attachments to include with the email. + /// Each entry may carry its content as a byte array () + /// or reference a file on disk via . + /// + public IEnumerable Attachments { get; set; } = []; } \ No newline at end of file diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs index 17e1afb..7616fbc 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/LimilabsEmailService.cs @@ -46,6 +46,8 @@ public class LimilabsEmailService( else builder.Text = context.Body; + AddAttachments(builder, context.Attachments); + var mail = builder.Create(); result = await smtp.SendMessageAsync(mail, cancellationToken); @@ -108,4 +110,33 @@ public class LimilabsEmailService( return message.ToString(); } + + private static void AddAttachments(MailBuilder builder, IEnumerable attachments) + { + foreach (var attachment in attachments) + { + if (attachment.IsInline) + { + var visual = builder.AddVisual(attachment.Content); + visual.FileName = attachment.FileName; + visual.ContentId = string.IsNullOrWhiteSpace(attachment.ContentId) + ? attachment.FileName + : attachment.ContentId; + + if (!string.IsNullOrWhiteSpace(attachment.ContentType)) + visual.ContentType = ContentType.Parse(attachment.ContentType); + } + else + { + var part = builder.AddAttachment(attachment.Content); + part.FileName = attachment.FileName; + + if (!string.IsNullOrWhiteSpace(attachment.ContentType)) + part.ContentType = ContentType.Parse(attachment.ContentType); + + if (!string.IsNullOrWhiteSpace(attachment.ContentId)) + part.ContentId = attachment.ContentId; + } + } + } } \ No newline at end of file