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