Send email from template via Javascript

Today we are going to send an email using an email template. I will use Typescript and assume that you have prior experience with this. In the client scripting SDK there is a method called Xrm.Utility.invokeProcessAction, but there are some bugs related to that method, (or I havent figured out how to call this correctly), which make it impossible to use for some actions. Instead we will use the Xrm.WebAPi.online.execute API to call the SendEmailFromTemplate action.

Find the correct parameters to use

First we need to find the correct parameters to use in the call. This can be found in the metadata-document, which is found on the url https://your_instance_name.api.crmX.dynamics.com/api/data/v9.1/$metadata or in the interface by clicking on the cog -> Advanced Settings -> Customizations -> Developer Resources, then copy the Service Root Url and append $metadata to the end. Before opening the metadata document I would advice you to installed some JSON extension to Chrome to get the data formatted. I like to use JSONView. When we open the metadata document we search for SendEmailFromTemplate to find something like this

<Action Name="SendEmailFromTemplate">
<Parameter Name="TemplateId" Type="Edm.Guid" Nullable="false"/>
<Parameter Name="Regarding" Type="mscrm.crmbaseentity" Nullable="false"/>
<Parameter Name="Target" Type="mscrm.crmbaseentity" Nullable="false"/>
<ReturnType Type="mscrm.email" Nullable="false"/>
</Action>

This tells us to call the action of name SendEmailParameter, we must supply it with three arguments, and expect a return type of mscrm.email. Non of the parameters are nullable, meaning that we can not omit any of them. The next step is to learn how to actually call the action.

Call the action from code

Start by opening Visual Studio code and create a new file called EmailService.ts. Open the terminal and write tsc –init to create an initial tsconfig.json file. We the start by defining our EmailService class and and empty method called sendEmailFromTemplate which takes to parameters, the template id and the form context object. How you aquire these to values is up to you, but I called this method from a button in the Ribbon. We are now left with the following code

class EmailService {
    public static sendEmailFromTemplate(templateId: string, formContext: any): void {
    }
}

Next up we will define some helper methods which will make it easier for us to call the actions. These methods are resuable to if you want to, you can extract them out to a utility class. In this example we keep all the code in our EmailService class.

// Wires the call to WebAPI. The docs for this can be fount
// in the link in the post.
private static callAction(actionName: string,
    ...params: Array<
        {
            key: string, // Parameter name
            value: any, // The value
            structuralProperty: number,
            typeName: string, // Type name
            isBoundParameter?: boolean // false if global action
        }>): PromiseLike<any> {
    const request: any = {};
    const metadata: any = { operationName: actionName, operationType: 0 };
    const parameterTypes: any = {};
    for (const param of params) {
        const key: string = param.key;
        request[param.key] = param.value;
        let parameterType: any = {};
        parameterType = {
            structuralProperty: param.structuralProperty,
            typeName: param.typeName
        };
        parameterTypes[key] = parameterType;
        if (param.isBoundParameter) {
            metadata.boundParameter = param.key;
        }
    }
    metadata.parameterTypes = parameterTypes;
    request.getMetadata = () => metadata;
    // Make the actual call to WebAPI
    return Xrm.WebApi.online.execute(request);
}
    // Removes the {} from the string. 
    //Cant use brackets in the calls to WebAPI
    private static removeCurlyBrackets(value: string): string {
        return value.replace(/{|}/gi, "");
    }

Our email that we are sending are from the owner of the Account record to the account.

private static createEmail(formContext: any): any {
    const owner = formContext.getAttribute("ownerid").getValue()![0];
    const accountId = formContext.data.entity.getEntityReference();
    const email: any = { "@odata.type": "Mirosoft.Dynamics.CRM.email" };
    email["regardingobjectid_account_email@odata.bind"] = 
        "/" + accountId.entityType + "s/" + accountId.id;
    const sender: any = {};
    sender["partyid_systemuser@odata.bind"] = 
        "/" + owner.entityType + "s(" + this.removeCurlyBrackets(owner.id) + ")";
    sender["participationtypemask"] = 1; // From
    const receiver: any = {};
    receiver["partyid_account@odata.bind"] = 
        "/" + accountId.entityType + "s(" + this.removeCurlyBrackets(accountId.id) + ")";
    receiver["participationtypemask"] = 2; // To
    email["email_activity_parties"] = [sender, receiver];
    return email;
}

Wrapping all the pieces together we are left with this content in the sendEmailFromTemplate method in our EmailService class.

const email = this.createEmail(formContext);
this.callAction("SendEmailFromTemplate",
    {
        key: "TemplateId",
        value: { guid: templateId },
        structuralProperty: 1,
        typeName: "Edm.Guid"
    },
    {
        key: "Target",
        value: email,
        structuralProperty: 5,
        typeName: "email"
    },
    {
        key: "Regarding",
        value: { entityType: "account", id: formContext.data.entity.getId() },
        structuralProperty: 5,
        typeName: "Edm.Guid"
    }
).then(console.log, (error: {
    code: string, message: string, innerror: { message: string, type: string, stacktrace: string }
}) => {
    const details = error?.innerror?.message + "\n" + error?.innerror?.stacktrace;
    Xrm.Navigation.openErrorDialog({
        details: details,
        message: error.message,
        errorCode: parseInt(error.code, undefined)
    });
});

If we write tsc in the terminal to compile the code to javascript and uploading it into our environment we can then call it like this

EmailService.sendEmailFromTemplate("our-template-guid-goes-here", formContext_goes_here);

I hope you learning something from this!.

Format input in Canvas App

Today there is really no great way to format the input of a text field in Canvas Apps. One solution to this shortcoming is to use two fields, and enter the text into an transparent field and format the text in another TextInput that lies below the transparent one.

In this example I will format a swedish cell phone number as we normally do here, which is AAA-BBB CC DD.

Start by creating a new Canvas App, in this example I used the Phone Layout. Start by dragging to TextInput-field to the canvas and rename them to PhoneRaw and PhoneFormatted.

As the default-value for PhoneFormatted, enter the following formula

Left(Concat(Filter(Split(PhoneRaw.Text, ""), Result in 12345678909), Result), 3) & "-" & Mid(Concat(Filter(Split(PhoneRaw.Text, ""), Result in 12345678909), Result), 4, 3) & " " & Mid(Concat(Filter(Split(PhoneRaw.Text, ""), Result in 12345678909), Result), 7, 2) & " " & Mid(Concat(Filter(Split(PhoneRaw.Text, ""), Result in 12345678909), Result), 9, 2)

There is a lot of repetion here, but as far as I know there is no other way to do this. We can now test this by entering some text into PhoneRaw, and see that it will filter away anything that isnt a number and also that it will format according to the specified format.

We now have a functionally formatting. The next step is to make the RawPhone field transparent and put in on top of phone formatted. Select PhoneRaw, and under properties scroll down to Color and make all attributes transparent.

When you are done the Color settings should look like below

Now the PhoneRaw field is transparent and can be hard to locate on the canvas, but if you select it from the tree view there shouldnt be any problems.

Grab the invisible element and drag in on top of PhoneFormatted. If the formatting doesnt seem to work, one issue I had were that the wrong element were put in front of the other. To solve this, right click on RawPhone, select Reorder and chose Bring to Front.

When you have done this, try again and you should now have working formatting. Another downside of this solution is if the user tries to select the text in the textbox, because it is the unformatted text that are in front and will be selected.

Nicely formatted text
Not so nice when selected

Happy formatting!