How do you reply to emails in C#/.NET? For that matter, how do you forward emails in C#/.NET?
As you may know, emails are sent using the Simple Mail Transfer Protocol (SMTP), and received using Internet Message Access Protocol (IMAP) - which replaced POP3. To learn more about sending emails, see my previous article about SMTP. To learn more about receiving emails, see my previous article about IMAP. The rest of this article will assume you have read both of the previous two posts and understand how to receive and send emails.
MailKit and MimeKit
Emails are actually made up of MIME components in a tree structure, which means you have to rebuild that tree with a new base if you want to build a new email and add on the reply.
Before we get to that, the simplest method to forward a message is to just attach one email to another (credit goes to Jeffrey Stedfast):
public static MimeMessage Forward (MimeMessage original, MailboxAddress from, IEnumerable<InternetAddress> to)
{
var message = new MimeMessage ();
message.From.Add (from);
message.To.AddRange (to);
// set the forwarded subject
if (!original.Subject.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase))
message.Subject = "FW: " + original.Subject;
else
message.Subject = original.Subject;
// quote the original message text
using (var text = new StringWriter ()) {
text.WriteLine ();
text.WriteLine ("-------- Original Message --------");
text.WriteLine ("Subject: {0}", original.Subject);
text.WriteLine ("Date: {0}", DateUtils.FormatDate (original.Date));
text.WriteLine ("From: {0}", original.From);
text.WriteLine ("To: {0}", original.To);
text.WriteLine ();
text.Write (original.TextBody);
message.Body = new TextPart ("plain") {
Text = text.ToString ()
};
}
// create the message/rfc822 attachment for the original message
var rfc822 = new MessagePart { Message = original };
// create a multipart/mixed container for the text body and the forwarded message
var multipart = new Multipart ("mixed");
multipart.Add (text);
multipart.Add (rfc822);
// set the multipart as the body of the message
message.Body = multipart;
return message;
}
You now have the original email attached to your message, but not in the format that people are used to seeing. If you thought that looked convoluted, wait until you see the reply examples.
Replying to a MIME-based email requires the following things (forwarding does too by the way):
- If the prefix doesn't already exist in the message you are replying to then prefix the reply subject with "RE:" or "Re:".
- Set the reply message's
In-Reply-To
header to the value of the Message-Id
header in the original message.
- Copy the original message's
References
header into the reply message's References
header and then append the original message's Message-Id
header.
- "Quote" the original message's text in the reply.
Here's how to reply to a basic email using MailKit and the MimeKit dependency (credit goes to Jeffrey Stedfast):
public static MimeMessage Reply (MimeMessage message, MailboxAddress from, bool replyToAll)
{
var reply = new MimeMessage ();
reply.From.Add (from);
// reply to the sender of the message
if (message.ReplyTo.Count > 0) {
reply.To.AddRange (message.ReplyTo);
} else if (message.From.Count > 0) {
reply.To.AddRange (message.From);
} else if (message.Sender != null) {
reply.To.Add (message.Sender);
}
if (replyToAll) {
// include all of the other original recipients - TODO: remove ourselves from these lists
reply.To.AddRange (message.To);
reply.Cc.AddRange (message.Cc);
}
// set the reply subject
if (!message.Subject.StartsWith ("Re:", StringComparison.OrdinalIgnoreCase))
reply.Subject = "Re: " + message.Subject;
else
reply.Subject = message.Subject;
// construct the In-Reply-To and References headers
if (!string.IsNullOrEmpty (message.MessageId)) {
reply.InReplyTo = message.MessageId;
foreach (var id in message.References)
reply.References.Add (id);
reply.References.Add (message.MessageId);
}
// quote the original message text
using (var quoted = new StringWriter ()) {
var sender = message.Sender ?? message.From.Mailboxes.FirstOrDefault ();
quoted.WriteLine ("On {0}, {1} wrote:", message.Date.ToString ("f"), !string.IsNullOrEmpty (sender.Name) ? sender.Name : sender.Address);
using (var reader = new StringReader (message.TextBody)) {
string line;
while ((line = reader.ReadLine ()) != null) {
quoted.Write ("> ");
quoted.WriteLine (line);
}
}
reply.Body = new TextPart ("plain") {
Text = quoted.ToString ()
};
}
return reply;
}
That doesn't look too bad, until you realise it doesn't work for HTML formatted emails. To do that requires 230 lines of code as you can see here.
Now you can build an HTML reply, but you still don't have any attachments or embedded resources. Wait a minute, 230 lines of code but no attachments or embedded resources?
Yes, to include attachments requires more lines of code to be added to the MimeVisitor that I won't write out here.
Why is it such a convoluted process just to reply to an email? Remember MIME is a tree structure so you have to rebuild that tree to reply. Isn't there a simpler way?
There is.
MailKitSimplified.Receiver and MailKitSimplified.Sender
By adding the MailKitSimplified.Receiver package from NuGet, you can do everything MailKit does as that's used under the hood, but all of the pain points go away:
using var imapReceiver = ImapReceiver.Create("imap.example.com:993")
.SetCredential("username", "password");
var mimeMessages = await imapReceiver.ReadMail.Take(1)
.GetMimeMessagesAsync(cancellationToken);
var mimeReply = mimeMessages.Single().GetReplyMessage("Reply here.");
Hundreds of lines of code to write a reply down to one new line! (Hint: cancellationToken is optional, but better to use it if you can.)
From this point on I'll use dependency injection in the examples below to get IImapReceiver and ISmtpSender directly by defining the sender and receiver options in an application configuration file. See my MailKitSimplified.Receiver article for instructions on how to do that.
You can do the same with IMessageSummaries too: (Hint: you can use GetAwaiter().GetResult() instead of await if you're not ready to make the transition to async yet, but async is better.)
var messageSummaries = await _imapReceiver.ReadMail.Take(1)
.GetMessageSummariesAsync(cancellationToken);
var mimeReply = await messageSummaries.Single().GetReplyMessageAsync("Reply here.");
Console.WriteLine($"Reply: \r\n{mimeReply.HtmlBody}");
The example above only downloaded the message envelope (from, to, etc.), so to download all parts of a message summary:
var messageSummaries = await _imapReceiver.ReadMail.Take(1)
.Items(MailFolderReader.CoreMessageItems)
.GetMessageSummariesAsync(cancellationToken);
var mimeReply = await messageSummaries.Single().GetReplyMessageAsync("Reply here.");
mimeReply.From.Add("from@localhost");
mimeReply.To.Add("to@localhost");
If you then want to send that using MailKitSimplified.Sender it's easy as:
await _smtpSender.SendAsync(mimeReply, cancellationToken);
Replying to an email in C# doesn't get much easier than this!
Next Steps
To see more examples and request new features, head over to https://github.com/danzuep/MailKitSimplified.