feat: Add CID (Content-ID) support for inline/embedded images in emai…#54
feat: Add CID (Content-ID) support for inline/embedded images in emai…#54
Conversation
…ls (v2.2.1) - Added ContentId property to AttachmentModel - Added LinkedResources property to IMailerContextResult for inline attachments - Added AddLinkedResource methods to IAttachmentCollection (file, bytes, URL) - Updated MailClient.PrepareMessage to handle linked resources with proper Content-Disposition: inline - Added integration tests for CID functionality - Added example usage in example project - Updated README with CID documentation and examples - Updated CHANGELOG and package metadata
There was a problem hiding this comment.
Pull request overview
This PR adds Content-ID (CID) support to the MailKitMailer library, enabling inline/embedded images in HTML emails. This is a valuable feature that allows images to be embedded directly in emails and referenced via cid: URLs, ensuring they display correctly even when external image loading is disabled.
Key Changes
- Added
ContentIdproperty toAttachmentModelandLinkedResourcescollection toIMailerContextResultfor managing inline attachments - Implemented
AddLinkedResourcemethods onIAttachmentCollectionwith support for file paths, byte arrays, and URLs - Updated
MailClient.PrepareMessageto process linked resources with properContent-Disposition: inlineheaders - Added comprehensive integration tests and example usage demonstrating the new CID functionality
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/AspNetCore.MailKitMailer/Models/AttachmentModel.cs | Added ContentId property for CID references |
| src/AspNetCore.MailKitMailer/Domain/IMailerContextResult.cs | Added LinkedResources property interface definition |
| src/AspNetCore.MailKitMailer/Data/MailerContextResult.cs | Implemented LinkedResources property |
| src/AspNetCore.MailKitMailer/Domain/IAttachmentCollection.cs | Added five AddLinkedResource method signatures for different source types |
| src/AspNetCore.MailKitMailer/Data/AttachmentCollection.cs | Implemented AddLinkedResource methods |
| src/AspNetCore.MailKitMailer/Data/MailClient.cs | Added linked resource processing logic and helper method |
| src/AspNetCore.MailKitMailer/AspNetCore.MailKitMailer.csproj | Updated version to 2.2.1 and release notes |
| tests/AspNetCore.MailKitMailerIntegrationTests/LinkedResourceTests.cs | Added integration tests for all CID scenarios |
| tests/Test-Apps/IntegrationTestsWebApp/Mailer/TestMailer.cs | Added test mailer methods for linked resources |
| tests/Test-Apps/IntegrationTestsWebApp/Controllers/AttachmentTestController.cs | Added controller endpoints for testing |
| tests/Test-Apps/IntegrationTestsWebApp/Mailer-Views/TestMailer/*.cshtml | Added Razor views with CID references |
| tests/Test-Apps/IntegrationTestsWebApp/TestData/TestImage.png | Added test image for integration tests |
| examples/MailKitMailerExample/Mailer/TestMailer.cs | Added example implementation with inline logo |
| examples/MailKitMailerExample/Mailer/ITestMailer.cs | Added interface definition for example |
| examples/MailKitMailerExample/Mailer-Views/TestMailer/WelcomeMailWithLogo.cshtml | Added view template using CID |
| examples/MailKitMailerExample/Controllers/TestController.cs | Added example controller endpoint |
| README.md | Added comprehensive CID documentation with examples and API reference |
| CHANGELOG.md | Documented the new features in version 2.2.1 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
netDumbster's SMTP test server has limited support for parsing multipart/related MIME messages with linked resources. The tests now verify that emails with linked resources are sent successfully without asserting on the parsed MIME structure.
- Added AddLinkedResource methods on IAttachmentCollection for file paths, byte arrays, and URLs - Added ContentId property to AttachmentModel - Attachments with ContentId are automatically treated as linked resources (inline MIME disposition) - Updated documentation with step-by-step examples - Updated tests to use Shouldly assertions - Removed unused LinkedResources property from IMailerContextResult
af542ca to
54814de
Compare
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Remove unused fileName variable in MailClient.cs - Remove extra blank line in TestMailer.cs - Add cid:testimage verification to all LinkedResource tests for consistency
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Remove deprecated AddLinkedResource(byte[], string, string, string) overload - Update signature to (byte[], string contentType, string contentId, string? fileName) - Update callers to use new parameter order
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (isLinkedResource) | ||
| { | ||
| att = new MimePart(attachment.ContenType); | ||
| } else | ||
| var linkedResource = bodyBuilder.LinkedResources.Add(attachment.FilePath); | ||
| linkedResource.ContentId = attachment.ContentId; | ||
| if (attachment.FileName != null) | ||
| { | ||
| linkedResource.ContentDisposition!.FileName = attachment.FileName; | ||
| } |
There was a problem hiding this comment.
The FilePath handling for linked resources ignores the ContentType if it's set. When a user calls AddLinkedResource(string filePath, string contentType, string contentId, string? fileName = null), the contentType parameter is stored in attachment.ContenType but never used when creating the linked resource.
The code should check if attachment.ContenType is set and use it, similar to how FileBytes and FileUrl are handled. For example, it should parse the contentType and pass it to the BodyBuilder's LinkedResources.Add method.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@copilot open a new pull request to apply changes based on the comments in this thread |
Co-authored-by: nfMalde <5366984+nfMalde@users.noreply.github.com>
fix: Honor ContentType parameter for FilePath-based linked resources
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [HttpGet("linked-resource-url")] | ||
| public IActionResult TestLinkedResourceUrl() | ||
| { | ||
| string testuri = "http://localhost:3333/dl/TestImage.png"; | ||
| Uri downloadUri = new Uri(testuri); | ||
|
|
||
| this.client.Send<ITestMailer>(x => | ||
| x.Test_LinkedResource_Url(downloadUri, "testimage") | ||
| ); | ||
|
|
||
| return Ok(); | ||
| } | ||
|
|
||
| [HttpGet("linked-resource-url-async")] | ||
| public async Task<IActionResult> TestLinkedResourceUrlAsync() | ||
| { | ||
| string testuri = "http://localhost:3333/dl/TestImage.png"; | ||
| Uri downloadUri = new Uri(testuri); | ||
|
|
||
| await this.client.SendAsync<ITestMailer>(x => | ||
| x.Test_LinkedResource_UrlAsync(downloadUri, "testimage") | ||
| ); | ||
|
|
||
| return Ok(); | ||
| } |
There was a problem hiding this comment.
The download server endpoint at /dl/{name}.{ext} serves text content ("TestDownload"), not actual image files. This means these URL-based tests are downloading text but treating it as an image file. The tests may pass due to lenient MIME handling, but they don't properly validate that actual image content is being downloaded and embedded. Consider updating the download server to serve the actual TestImage.png file, or add assertions that verify the downloaded content type and size match expected image data.
| else if (attachment.FilePath != null) | ||
| { | ||
| MimePart? att = null; | ||
|
|
||
| if (attachment.ContenType != null) | ||
| if (isLinkedResource) | ||
| { | ||
| att = new MimePart(attachment.ContenType); | ||
| } else | ||
| var fname = Path.GetFileName(attachment.FilePath); | ||
| var contentType = attachment.ContenType != null | ||
| ? ContentType.Parse(attachment.ContenType) | ||
| : ContentType.Parse(MimeKit.MimeTypes.GetMimeType(fname)); | ||
| var fileName = attachment.FileName ?? fname; | ||
| var data = await File.ReadAllBytesAsync(attachment.FilePath); | ||
|
|
||
| var linkedResource = bodyBuilder.LinkedResources.Add(fileName, data, contentType); | ||
| linkedResource.ContentId = attachment.ContentId; | ||
| } | ||
| else | ||
| { | ||
| att = new MimePart(MimeKit.MimeTypes.GetMimeType(attachment.FilePath)); | ||
| if (!string.IsNullOrEmpty(attachment.ContenType)) | ||
| { | ||
| var contentType = ContentType.Parse(attachment.ContenType); | ||
| var fileName = attachment.FileName ?? Path.GetFileName(attachment.FilePath); | ||
| var data = await File.ReadAllBytesAsync(attachment.FilePath); | ||
| bodyBuilder.Attachments.Add(fileName, data, contentType); | ||
| } | ||
| else | ||
| { | ||
| bodyBuilder.Attachments.Add(attachment.FilePath); | ||
| if (attachment.FileName != null) | ||
| { | ||
| var att = bodyBuilder.Attachments[bodyBuilder.Attachments.Count - 1]; | ||
| att.ContentDisposition!.FileName = attachment.FileName; | ||
| } | ||
| } |
There was a problem hiding this comment.
The refactored code now loads entire files into memory using File.ReadAllBytesAsync when handling file path attachments with an explicit content type (lines 328-331) or for linked resources (lines 314-322). The original code used File.OpenRead which created a stream, allowing MailKit to handle large files more efficiently without loading them entirely into memory. This change could cause memory issues when attaching large files. Consider using the stream-based approach for consistency with line 335 where bodyBuilder.Attachments.Add(attachment.FilePath) is still used for files without an explicit content type.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
Co-authored-by: nfMalde <5366984+nfMalde@users.noreply.github.com>
Co-authored-by: nfMalde <5366984+nfMalde@users.noreply.github.com>
Co-authored-by: nfMalde <5366984+nfMalde@users.noreply.github.com>
Co-authored-by: nfMalde <5366984+nfMalde@users.noreply.github.com>
fix: Use stream-based file handling and serve actual test images
Version (v2.2.1)
Added ContentId property to AttachmentModel
Added LinkedResources property to IMailerContextResult for inline attachments
Added AddLinkedResource methods to IAttachmentCollection (file, bytes, URL)
Updated MailClient.PrepareMessage to handle linked resources with proper Content-Disposition: inline
Added integration tests for CID functionality
Added example usage in example project
Updated README with CID documentation and examples
Updated CHANGELOG and package metadata