MailSharp / MailSharp.MailClient / Services / MimeMessageFactory.cs
Code · 74 lines · 2792 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74using MailSharp.MailClient.Models;
using MimeKit;
using ModelPriority = MailSharp.MailClient.Models.MessagePriority;

namespace MailSharp.MailClient.Services;

public static class MimeMessageFactory
{
	public static MimeMessage Build(Account account, ComposeModel compose, IEnumerable<(string FileName, Stream Content)> attachments) =>
		Build(account, compose, attachments, []);

	// inlineImages are pictures the user inserted into the editor via file upload rather than a
	// URL (see compose.js's upload-image button) - the composer already rewrote each <img> tag's
	// src to "cid:<Cid>" before posting HtmlBody, so all that's needed here is attaching the actual
	// bytes as an inline MimePart whose ContentId matches, so mail clients resolve the reference
	// instead of showing a broken image.
	public static MimeMessage Build(Account account, ComposeModel compose, IEnumerable<(string FileName, Stream Content)> attachments, IEnumerable<(string Cid, string FileName, Stream Content)> inlineImages)
	{
		var message = new MimeMessage();
		message.From.Add(new MailboxAddress(account.DisplayName, account.EmailAddress));
		AddAddresses(message.To, compose.To);
		AddAddresses(message.Cc, compose.Cc);
		AddAddresses(message.Bcc, compose.Bcc);
		message.Subject = compose.Subject;

		message.Priority = compose.Priority switch
		{
			ModelPriority.Low => MimeKit.MessagePriority.NonUrgent,
			ModelPriority.High => MimeKit.MessagePriority.Urgent,
			_ => MimeKit.MessagePriority.Normal
		};

		if (compose.Sensitivity != MessageSensitivity.Nothing)
		{
			message.Headers.Add("Sensitivity", compose.Sensitivity switch
			{
				MessageSensitivity.Personal => "Personal",
				MessageSensitivity.Private => "Private",
				MessageSensitivity.Confidential => "Company-Confidential",
				_ => "Normal"
			});
		}

		if (compose.RequestReadReceipt)
		{
			message.Headers.Add("Disposition-Notification-To", account.EmailAddress);
			message.Headers.Add("Return-Receipt-To", account.EmailAddress);
		}

		var builder = new BodyBuilder { HtmlBody = compose.HtmlBody };
		foreach (var (fileName, content) in attachments)
		{
			builder.Attachments.Add(fileName, content);
		}
		foreach (var (cid, fileName, content) in inlineImages)
		{
			var part = builder.LinkedResources.Add(fileName, content);
			part.ContentId = cid;
			part.ContentDisposition = new ContentDisposition(ContentDisposition.Inline);
		}
		message.Body = builder.ToMessageBody();
		return message;
	}

	private static void AddAddresses(InternetAddressList list, string raw)
	{
		if (string.IsNullOrWhiteSpace(raw)) return;
		foreach (var part in raw.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
		{
			if (MailboxAddress.TryParse(part, out var addr))
				list.Add(addr);
		}
	}
}