Thursday, January 26, 2017

Sending email to an alternate / secondary email address

Small plugin to send email to an alternate / secondary email address


Those who have worked with Dynamics365:CRM for a while has undoubtedly encounter this issue, which is that whatever address you enter as a recipient the system will resolve the record and substitute the address with the primary email address of the record.
There are several different ways of working around this issue, like creating leads that can be merged into existing contacts, duplicate contacts with links, etc. but I have created a small plugin which allows you to send an email to the address you want.
All you have to do is to compile the following code and register it as a synchronous, pre-operation plugin, and add a pre-entity image with the "to" field included. The secret here is to register it on the "send" message for the email entity.

Just add the microsoft.crmsdk from nuget to your project, and you'll have all the libraries you need to compile this little thing.

public class SendToSafeEmail : IPlugin
{
    public void Execute(IServiceProvider sp)
    {
        var context = (IPluginExecutionContext)sp.GetService(typeof(IPluginExecutionContext));
        var factory = (IOrganizationServiceFactory)sp.GetService(typeof(IOrganizationServiceFactory));
        var service = factory.CreateOrganizationService(context.UserId);

        Entity email = context.PreEntityImages.Values.First();
        var recipient = ((EntityCollection)email.Attributes["to"]).Entities.FirstOrDefault<Entity>();
        var to = (EntityReference)recipient.Attributes["partyid"];
        var contact = service.Retrieve("contact", to.Id, new ColumnSet("emailaddress3"));
        ((EntityCollection)email.Attributes["to"]).Entities[0].Attributes["addressused"] = contact.Attributes["emailaddress3"];
        service.Update(email);
    }
}

As you can see this isn't particularly advanced, and I would like to start with pointing out that I've removed exception handling and tracing for illustration purposes, I'm usually a lot more tidy (it's true!).
First off we retrieve the usual suspects, the execution context and the orgservice factory. Then we generate a new org-service connection based on the user ID of the user which triggered the plugin. Next up we retrieve the email from the pre entity images. This imlies that this is a pre-operation, synchronous plugin, and we need to add a pre-entity image when we register the plugin.

Next up we get the recipient from the "to" list. My code example does not take into account several recipients, it doesn't do any exception handling, and it's hard-coded for contacts. You'll have to make it work for your purpose on your own :)
The contact is retrieved from the system, and finally we do the following:
From the to recipient list, we update the "addressused" field on the first one, to the "emailaddress3" of the contact collected.
Next we update the entity back into the system, and continue the execution context.

That's all it takes, and some of your own logic of course.

A few thought on this solution before I wrap up:
It seems like a small, simple plugin for sending to alternate email addresses, but notice how we're hooking up to the "send" message for the email entity? That means that this plugin will trigger for all emails sent from the system. This doesn't need to have a large impact on your system, because there are a lot of optimizations in place in MSDYN365, but every line of code that has to execute causes a tiny bit of extra load on the system, and then multiply it by the number of emails sent from the system.

Wednesday, January 25, 2017

getEventArgs().getSaveMode() does not work for send email

getSaveMode() does not display the correct value

So I'm working on a customer who wants to perform some logic before sending an email. We used to hook onto the save event on the email form to perform this, but using global variables to store whether the user had been prompted earlier and doing several look ups made the form too slow.

I looked into the details for the save event, and found this bit from the SDK which explains the different save modes used.

To get this object, all you need to do is check the box for "pass execution context as the first parameter" and you're good to go. The code I'm using includes something like this (don't worry, the actual code uses namespaces)

var performSaveActions = function(context) {
    if (context.getEventArgs().getSaveMode() === 7) {
        console.log("This never executes");
};

This looks OK, right? Well sadly it doesn't. Even though the SDK clearly states that when sending an email the getSaveMode() method should return 7, but it doesn't, it returns 1.

More specifically, when you hit the send button, the function executes 2 times, one time to save the content of the record, and one time before actually sending it. So I put a breakpoint in the code just to check the event arguments, and both of the times the breakpoint hits the getSaveMode() method returns 1, and never a 7.
So I tested for autosave, but that one actually returns 70, so that works as expected. I tried getting help from the different community channels, but it seems like this is a bug that hasn't gotten any attention yet.

So that's it really, if you're here looking for the same answer I was looking for then I'm afraid you're out of luck this time. What I ended up doing was hooking onto the normal save event (getSaveMode() === 1).

Also, I prevented autosave from triggering on the email form. There is a bug with autosave on the email form which can be quite annoying. If you've triggered a change on the email form and then start editing the email text field, when autosave triggers it will not include the text you've written since the dirty bit was triggered. Not only that, when the autosave is finished the text box will refresh and everything you've written will disappear.

So I implemented this snippet in the same method which stops autosave from happening (relax, this will present the user with a warning when leaving or refreshing the form if the changes haven't been saved):

if (context.getEventArgs().getSaveMode() === 70) {
    context.getEventArgs().preventDefault();
    return;
}