elexis elexis - 3 months ago 36
C# Question

How to send a iCal invite with Mailgun Rest API (C#)

I am attempting to add a calendar invite in iCal format to an email sent via the MailGun API. This is what i have so far:

var request = new RestRequest();

request.AddParameter("domain", this.domain, ParameterType.UrlSegment);
request.Resource = "{domain}/messages";
request.AddParameter("from", contactDetails.SenderAddress);
request.AddParameter("to", contactDetails.RecipientAddress);
request.AddParameter("subject", message.Subject);
request.AddParameter("text", message.TextBody);
request.AddParameter("html", message.HtmlBody);

if (!string.IsNullOrWhiteSpace(message.IcalAttachment))
{
request.AddFileBytes("attachment",
Encoding.UTF8.GetBytes(message.IcalAttachment),
"invite.ics",
"text/calendar");
}

request.Method = Method.POST;
return request;


This results in the calendar being included in the email as an attachment, not an alternative view of the email. The attachment works fine in gmail however in Outlook it appears as an attachment file that you must first click on, then agree to adding the calendar to the Outlook calendar. Is there another way to use the REST api so that the calendar invites are sent correctly, as alternative email views?

To be clear, this is how I would send a calendar invite using .Net
SmtpClient
:

var contentType = new ContentType("text/calendar");
if (contentType.Parameters != null)
{
contentType.Parameters.Add("method", "REQUEST");
contentType.CharSet = "UTF-8";
}

// this is the same way you add a html view to the message
request.AlternateViews.Add(
AlternateView.CreateAlternateViewFromString(
message.IcalAttachment,
contentType));

Answer

Special thanks to Mailgun support for pointing me in the right direction. The relevant part or their response was:

You can use the /message.mime endpoint to construct the MIME for the calendar invite: https://documentation.mailgun.com/api-sending.html#sending

Creating a mime message isnt as easy as simply using their /message endpoint but there are several .net libraries available to do this. I used MimeKit in this example.

var request = new RestRequest();

request.AddParameter("domain", this.domain, ParameterType.UrlSegment);
request.Resource = "{domain}/messages.mime";
request.AddParameter("to", contactDetails.RecipientAddress);
request.AddFile(
    "message", 
    Encoding.UTF8.GetBytes(BuildMimeContent(message)), 
    "message.mime");

request.Method = Method.POST;
return request;

The mime content that I want to create will contain a multipart/mixed body, which will in turn contain a multipart/alternative as well as every attachment. The calendar invite will actually be attached twice, as a alternative view and as an attachment. This is to aid in compatibilitiy across different email clients.

The implementation of BuildMimeContent(message) looks like the following:

// create the alternative views
var textBody = new TextPart("plain") { Text = message.TextBody };
var htmlBody = new TextPart("html") { Text = message.HtmlBody };

// add views to the multipart/alternative
var alternative = new Multipart("alternative");
alternative.Add(textBody);
alternative.Add(htmlBody);

if (!string.IsNullOrWhiteSpace(message.CalendarInvite))
{
    // also add the calendar as an alternative view
    // encoded as base64, but 7bit will also work
    var calendarBody = new TextPart("calendar")
    {
        Text = message.CalendarInvite,
        ContentTransferEncoding = ContentEncoding.Base64
    };

    // most clients wont recognise the alternative view without the 
    // method=REQUEST header
    calendarBody.ContentType.Parameters.Add("method", "REQUEST");
    alternative.Add(calendarBody);
}

// create the multipart/mixed that will contain the multipart/alternative
// and all attachments
var multiPart = new Multipart("mixed") { alternative };
if (!string.IsNullOrWhiteSpace(message.CalendarInvite))
{
    // add the calendar as an attachment
    var calAttachment = new MimePart("application", "ics")
    {
        ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
        ContentTransferEncoding = ContentEncoding.Base64,
        FileName = "invite.ics",
        ContentObject = new ContentObject(GenerateStreamFromString(message.CalendarInvite))
    };

    multiPart.Add(calAttachment);
}

// TODO: Add any other attachements to 'multipart' here.

// build final mime message
var mimeMessage = new MimeMessage();
mimeMessage.From.Add(GetMimeAddress(message.MessageInfo.SenderName, message.MessageInfo.SenderAddress));
mimeMessage.To.Add(GetMimeAddress(message.MessageInfo.RecipientName, message.MessageInfo.RecipientAddress));
mimeMessage.Subject = message.Subject;
mimeMessage.Body = multiPart;

// parse and return mime message
return mimeMessage.ToString();

Warning for people testing with Office 365

Office365 is extremely picky when it comes to validating calendar invites. In order to not get a message like the one below, you will need to ensure that the vCal's organizer email address matches the email's from address. This is not possible if you are using mailgun's sandbox test environment.

not supported calendar mesage