15

We are facing a problem with sending trigger-emails from EXM. We have created a page with a custom HTML form used for meter readings. The business logic is as follows:

  1. The user receives an email with a link to the meter reading page
  2. He enters the page
  3. He enters data related to the reading, along with the meter reading itself
  4. He submits the form
  5. An email is triggered to be sent, based on the data he entered in the form
  6. He is redirected to a different page with

The challenge is to get the entered data from the form submission (3 and 4) to be available for EXM when creating the email (5).

We have on earlier occasions worked around the problem by saving the form data on the contact in the xDB, and making an assumption that the data is correct when EXM accesses it. In this case we cannot use that approach, because the user might enter multiple meter readings within a short period of time (opening up for challenges with timing, data storage, etc.).

After flushing data to the xDB, we currently use the following line of code to send the email through EXM:

Sitecore.Modules.EmailCampaign.ClientApi
    .SendStandardMessage(new Guid(MessageId), new XdbContactId(contact.ContactId), true);

Is there a better way to trigger the emails using EXM, where it is possible to pass data directly to EXM (i.e. not saving the data to xDB)?

5
  • Are you able to store the relevant data in your own datastore (e.g. in memory, SQL, Mongo) and then retrieve it during the token-building phase, based off the contact Id?
    – Kasaku
    Commented Nov 1, 2016 at 10:58
  • Yes, but then we would face the same problem during the token-building phase as we would with the xDB (e.g. which registered reading is the current mail being generated for). Commented Nov 1, 2016 at 11:00
  • It looks like you need to generate a MessageId when you create the message, why not key off that?
    – Kasaku
    Commented Nov 1, 2016 at 11:03
  • Actually my mistake, I'm guessing that MessageID is a reference to the email you want to send.
    – Kasaku
    Commented Nov 1, 2016 at 11:53
  • See Sitecore EXM custom mail from Engagement plan blog.roeermose.com/tag/sitecore-exm Commented Jun 5, 2018 at 10:51

2 Answers 2

11

Background information

The answer depends on your environment configuration. If you have a single server setup (i.e. the server is configured as both CD and CM) the solution is simple (read on), if the server roles are split on multiple servers e.g. one CD server, one CM etc. the solution is a bit more complicated.

That being said,

Sitecore.Modules.EmailCampaign.ClientApi()

is the right entry point for working (programmatically) with EXM. On a scaled environment this ensures that if you call e.g.

Sitecore.Modules.EmailCampaign.ClientApi
.SendStandardMessage(new Guid(MessageId), new XdbContactId(contact.ContactId), true);

from a CD server, the dispatch will be made from the CM server as the ClientAPI makes a request to the ecmclientservice.asmx (as defined in the connection string, see configuring EXM in a scaled environment). This is necessary as the CD role is not configured to dispatch, and in most cases you don't want it to be, as this can take up a lot of resources. Unfortunately the ClientAPI does not currently support custom tokens for automated messages.

Solution

Assuming you have created and activated the following automated (previously known as triggered) message (highlights added for tokens):

automated message

The method

Sitecore.Modules.EmailCampaign.Application.EmailDispatch.EmailDispatch.SendTriggered(Guid messageId, RecipientId recipientId, bool usePreferredLanguage = false, IDictionary<string, object> customPersonTokens = null)

supports exactly what you're trying to do, for example:

var customPersonTokens = new Dictionary<string, object>
{
    {"meterreading", "your reading" }
};
SendTriggered(new Guid(MessageId), new XdbContactId(contact.ContactId), true, customPersonTokens);

This will apply both the default tokens (e.g. fullname and email) and the custom tokens, in that order.

This can be executed directly on a single server setup, but for the reasons stated above you will need to create an endpoint on the CM server that you can call from the CD server on a scaled setup. The CM server should execute the SendTriggered() message, and the CD server should send the contact id, message id and any custom tokens.

Side note about default tokens

The default tokens are defined in

Sitecore.Modules.EmailCampaign.Core.Personalization.DefaultRecipientPropertyTokenMap()

You are unable to add your own facets to this, but you can replace it with your own implementation by changing the following element in Sitecore.EmailExperience.Core.config:

<recipientPropertyTokenMap type="Sitecore.Modules.EmailCampaign.Core.Personalization.DefaultRecipientPropertyTokenMap, Sitecore.EmailCampaign" singleInstance="true" />
4
  • Great answer! Jacob, followup question, do normal configured tokens also get applied in addition to the custom token dictionary passed along? Commented Nov 2, 2016 at 22:38
  • 1
    Yes. I've updated the answer to clarify Commented Nov 3, 2016 at 9:24
  • 1
    In version 8.2 SendTriggered has been renamed to SendAutomated Commented Jun 5, 2018 at 10:49
  • This came up today, but in Jacob's answer and subsequent comment, the SendAutomated (formerly known as SendTriggered) is the method exposed by the EXM Factory that you probably want to stay away from. The public API method is ECMClientService.SendStandardMessage(...) referencing this doc link: doc.sitecore.net/email_experience_manager/35/… Commented Jul 16, 2018 at 20:23
6

There are several options. One of them is to add a processor to <getXdbContactRecipient> pipeline defined at 'Include\EmailExperience\Sitecore.EmailExperience.Core.config' and add custom user data there.

In public void Process(GetXdbContactRecipientPipelineArgs args) method you can instantiate class with required custom profile properties e.g.

public class ContactInfo : Sitecore.Modules.EmailCampaign.Recipients.Property
{
    public string Token { get; set; }
    public string Title { get; set; }
    public string Salutation { get; set; }
}

and assign it to appropriate property of recipient

public void Process(GetXdbContactRecipientPipelineArgs args)
{
    _userService = IoCContainer.GetInstance<IUserService>();

    if (args.IsPropertyRequested<ContactInfo>()) // need to replace our custom token linked to ContactInfo
    {
        ContactInfo info = _userService.GetContactInfoByInformationWeHave(); // args.SourceContact can provide you with required information 
        args.TargetRecipient.GetProperties<ContactInfo>().DefaultProperty = info;
    }
}

UPD: forgot to mention that you will need to extend Sitecore.Modules.EmailCampaign.Core.Personalization.RecipientPropertyTokenMap (<recipientPropertyTokenMap in the same config)

with something like this:

    public class ExtendedRecipientPropertyTokenMap : RecipientPropertyTokenMap
{
    private static readonly Dictionary<Token, RecipientPropertyTokenBinding> TokenBindings = new[]
    {
        RecipientPropertyTokenBinding.Build(new Token("fullname"), (Expression<Func<PersonalInfo, object>>) (personalInfo => personalInfo.FullName)),
        RecipientPropertyTokenBinding.Build(new Token("name"), (Expression<Func<PersonalInfo, object>>) (personalInfo => personalInfo.FirstName)),
        RecipientPropertyTokenBinding.Build(new Token("salutation"), (Expression<Func<ContactInfo, object>>) (contact => contact.Salutation)),
    }
    .OrderBy(b => b.Token.Key)
    .ToDictionary(b => b.Token, t => t);

    public override IList<RecipientPropertyTokenBinding> GetTokenBindings()
    {
        return TokenBindings.Values.ToList();
    }

    public override RecipientPropertyTokenBinding GetTokenBinding(Token token)
    {
        Assert.ArgumentNotNull(token, "token");
        RecipientPropertyTokenBinding propertyTokenBinding;
        TokenBindings.TryGetValue(token, out propertyTokenBinding);
        return propertyTokenBinding;
    }
}
8
  • I don't think this solution is what is being asked for. This requires the data to be available on the contact, but as the author mentions that might not be the case. Commented Nov 1, 2016 at 10:20
  • I see your point, i will add more code to clarify
    – Daniil
    Commented Nov 1, 2016 at 10:23
  • Hey @Daniil, thanks for the answer. Unfortunately Jacob is right - assume the data is not available on the contact. I have clarified the question. Commented Nov 1, 2016 at 10:46
  • Yes, that is exactly what we have on our project - data for ContactInfo comes from SQL database, and we retrieve it for recipient by his email. Line ContactInfo info = _userService.GetContactInfoByInformationWeHave(); is responsible for it. If user enters email in the form you can identify current contact.
    – Daniil
    Commented Nov 1, 2016 at 10:55
  • But the data entered in the form is still not present when creating the tokens (because it is not present on the contact). Commented Nov 1, 2016 at 11:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.