How To Send an Email with Attachments Using EPiServer Forms
In case the File upload element is specified an EPiServer form will store all uploaded files in the EPiServer blob storage by default. The form can be configured to automatically send out an email to any specified email address with all the data that is entered in the form. However, the uploaded files are not sent as attachments but as links. If we want to change that, we would have to extend the existing functionality.
Custom Actors in EPiServer Forms
We need to create a custom Actor. By definition, Actors are server-side actions that are performed after the user’s form submission. Episerver forms comes with two pre-defined Actors: CallWebhookAfterSubmissionActor and SendEmailAfterSubmissionActor.
If we want to send uploaded files as attachments, we need to extend SendEmailAfterSubmissionActor. We can do this by creating a custom actor that will inherit SendEmailAfterSubmissionActor:
public class SendEmailWithMultipleFilesAsAttachment : SendEmailAfterSubmissionActor
After that, we need to create a model for the Uploaded file. This model will have 3 properties: Name, Type and Input stream.
public class UploadedFile { public string Name { get; set; } public string Type { get; set; } public MemoryStream InputStream { get; set; } }
Next, we need to implement a method for collecting uploaded files in our actor.
At this moment, the files are not stored in the EPiServer blob storage, because the file uploading might take a while. For that reason, we use HttpRequestContext. In this way, we will get files from the request, instead of waiting for them to be stored in the blob storage. Without that, it will send an email with incomplete attachments.
private List<UploadedFile> GetUploadedFiles() { var uploadedFiles = new List<UploadedFile>(); try { for (int i = 0; i < this.HttpRequestContext.Files.Count; i++) { HttpPostedFileBase postedFile = this.HttpRequestContext.Files[i]; postedFile.InputStream.Position = 0; postedFile.InputStream.Seek(0, SeekOrigin.Begin); MemoryStream memoryStream = new MemoryStream(); postedFile.InputStream.CopyTo(memoryStream); memoryStream.Position = 0; memoryStream.Seek(0, SeekOrigin.Begin); uploadedFiles.Add( new UploadedFile() { Name = postedFile.FileName, Type = MimeMapping.GetMimeMapping(postedFile.FileName), InputStream = memoryStream }); } } catch (Exception ex) { PostSubmissionActorBase._logger.Error("Failed to get uploaded files: {0}", ex); } return uploadedFiles; }
Now, we can override the Run method in our custom actor, and call the GetUploadedFiles() method from there.
public override object Run(object input) { IEnumerable<EmailTemplateActorModel> model = this.Model as IEnumerable<EmailTemplateActorModel>; if (model == null || !model.Any()) { return string.Empty; } var files = new List<UploadedFile>(); files = GetUploadedFiles(); if (model == null || model.Count() < 1) { return null; } foreach (EmailTemplateActorModel emailConfig in model) { this.SendMessage(emailConfig, files); } return null; }
Finally, we can create a method for sending an email with attachments. This method will be called from the SendMessage method. In this method, we will get all the uploaded files, and create attachments based on those files. When all the attachments are created, we need to include those attachments in an email message, and finally, send that message.
public bool SendEmailWithAttachments(IEnumerable<FriendlyNameInfo> friendlyNameInfos, HttpRequestBase httpRequestContext, Submission submissionData, MailMessage message, List<UploadedFile> files) { if (files.Any()) { foreach (var file in files) { string mime = MimeMapping.GetMimeMapping(file.Name); Attachment attachment = new Attachment(file.InputStream, file.Name, mime); message.Attachments.Add(attachment); } _smtpClient.Send(message); message.Dispose(); return true; } return false; }