Replacing built-in email actor with custom implementation in Episerver Forms
The issue was that the form contained a field for selecting one or more email recipients of the form data using the built in "Selection" element.
Screenshot of the form
Screenshot of the configured email
Everything worked fine if the user only selected one recipient, but when two or more were selected it failed to send the email with the following exception:
PostSubmissionActorBase: Failed to send email System.InvalidOperationException: A recipient must be specified.
It turns out that the composing of the "To"-addresses looks something like this:
foreach (string email in toEmails) { string address = _placeHolderService.Service.Replace(email, placeholders, false); try { message.To.Add(new MailAddress(address)); } catch (Exception ex) { … } }
At first I thought that the issue was related to the replacement of tokens being done after the initial split and thus not supporting comma separated email addresses in token, but it turns out it was a lot simpler than that.
In the above code a new MailAddress is constructed with a comma separated string of email addresses which is not supported and throws an exception.
Instead it should look like this:
… message.To.Add(address) …
This works since the MailAddressCollection has support for adding multiple emails using a comma separated string.
I solved the issue by implementing my own SendEmailAfterSubmissionActor by inheriting from the built-in one. Unfortunately I had to write a lot of the logic for composing and sending the email just to fix this small issue. In my case it would have been nice if the functionality for adding recipients was extracted to a virtual method that I could override in my own implementation and re-use as much as possible from the built-in functionality.
public class CustomSendEmailAfterSubmissionActor : SendEmailAfterSubmissionActor { ... public override object Run(object input) { foreach (EmailTemplateActorModel emailConfig in model) this.SendMessage(emailConfig); return null; } private void SendMessage(EmailTemplateActorModel emailConfig) { ... foreach (string email in toEmails) { string address = _placeHolderService.Service.Replace(email, bodyPlaceHolders, false); try { message.To.Add(address); } catch (Exception ex) { ... } } ... } }
After that I used an EditorDescriptor to hide the default EmailTemplate-editor from the "Form container" since we don’t want to show both the built-in one and my new implementation. Remember that this only hides the property from the editor, all existing forms that is created and configured using the old actor is still persisted and not visible, make sure to move the email templates from existing forms to the new actor before hiding it.
[EditorDescriptorRegistration(TargetType = typeof(IEnumerable), UIHint = "EmailTemplateActorEditor")] public class HideDefaultSendEmailAfterSubmissionActor : CollectionEditorDescriptor { public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes) { if (metadata.PropertyName == "SendEmailAfterSubmissionActor") { metadata.ShowForEdit = false; } } }
This can probably be fixed more elegantly using other ways, but hopefully this small problem will be fixed in the near future so this works as a temporary solution. I have registered an incident about this with Episerver.
Read more about Placeholder API and custom actors on Episerver World