I'm trying to create a marketing automation EXM campaign with custom-made tokens in the e-mails. To this end, we're importing contacts from a CSV into the xDB and reading them with a custom dispatcher and token map.
I followed the steps outlined in the documentation and I've succeeded to get my tokens to show up in the EXM editor and tokens that have standard facets as a datasource are filled correctly. I've also validated imported contacts to have correctly filled facet values. However my tokens that should be filled with those values from my custom facets remain empty.
I strongly suspect this has something to do with my implementation of either the Dispatcher or TokenMap, but both the documentation and my personal experience with this piece of Sitecore are simply not sufficient to come to a conclusion I'm afraid. Can anyone help me out?
For context, below are excerpts from my Facet, DispatchTask and TokenMapper respecively:
[FacetKey(FacetKeys.PolicyHolder)]
public class PolicyHolder : Facet
{
public const string DefaultFacetKey = FacetKeys.PolicyHolder;
public string PolicyHolderId { get; set; }
public string InsertionCapital { get; set; }
public string InsertionLower { get; set; }
}
public static class MaturityMailFacetExtensions
{
public static PolicyHolder GetPolicyHolder(this Contact contact)
{
return contact.GetFacet<PolicyHolder>();
}
public static PersonalInformation GetPolicyHolderPersonalInformation(this Contact contact)
{
return contact.GetFacet<PersonalInformation>();
}
}
public class MarketingAutomationDispatchTask : DispatchTask
{
private IContactService _contactService;
public MarketingAutomationDispatchTask(ShortRunningTaskPool taskPool, IRecipientValidator recipientValidator, IContactService contactService, EcmDataProvider dataProvider, ItemUtilExt itemUtil, IEventDataService eventDataService, IDispatchManager dispatchManager, EmailAddressHistoryManager emailAddressHistoryManager, IRecipientManagerFactory recipientManagerFactory, SentMessageManager sentMessageManager)
: base(taskPool, recipientValidator, contactService, dataProvider, itemUtil, eventDataService, dispatchManager, emailAddressHistoryManager, recipientManagerFactory, sentMessageManager)
{
_contactService = contactService;
}
protected override IReadOnlyCollection<IEntityLookupResult<Contact>> GetContacts(List<DispatchQueueItem> dispatchQueueItems)
{
return _contactService.GetContacts(dispatchQueueItems.Select(x => x.ContactIdentifier),
FacetKeys.MaturityMailModelFacets);
}
}
public class MarketingAutomationTokenMap : DefaultRecipientPropertyTokenMap
{
protected static readonly MethodInfo GetPolicyHolderPersonalInformation = typeof(MaturityMailFacetExtensions).GetMethod(nameof(MaturityMailFacetExtensions.GetPolicyHolderPersonalInformation), new[] { typeof(Contact) });
protected static readonly MethodInfo GetPolicyHolder = typeof(MaturityMailFacetExtensions).GetMethod(nameof(MaturityMailFacetExtensions.GetPolicyHolder), new[] { typeof(Contact) });
static MarketingAutomationTokenMap()
{
if (TokenBindings == null)
{
TokenBindings = new Dictionary<Token, RecipientPropertyTokenBinding>();
}
// These work
var customTokenBinding = RecipientPropertyTokenBinding.Build<PersonalInformation>(new Token(MarketingAutomationTokens.InitialsPolicyHolder), p => p.FirstName, GetPolicyHolderPersonalInformation);
TokenBindings.Add(customTokenBinding.Token, customTokenBinding);
customTokenBinding = RecipientPropertyTokenBinding.Build<PersonalInformation>(new Token(MarketingAutomationTokens.LastNamePolicyHolder), p => p.LastName, GetPolicyHolderPersonalInformation);
TokenBindings.Add(customTokenBinding.Token, customTokenBinding);
customTokenBinding = RecipientPropertyTokenBinding.Build<PersonalInformation>(new Token(MarketingAutomationTokens.GenderPolicyHolder), p => p.Gender, GetPolicyHolderPersonalInformation);
TokenBindings.Add(customTokenBinding.Token, customTokenBinding);
// These don't
customTokenBinding = RecipientPropertyTokenBinding.Build<PolicyHolder>(new Token(MarketingAutomationTokens.LastNamePrefixCapitalCasePolicyHolder), p => p.InsertionCapital, GetPolicyHolder);
TokenBindings.Add(customTokenBinding.Token, customTokenBinding);
customTokenBinding = RecipientPropertyTokenBinding.Build<PolicyHolder>(new Token(MarketingAutomationTokens.LastNamePrefixLowerCasePolicyHolder), p => p.InsertionLower, GetPolicyHolder);
TokenBindings.Add(customTokenBinding.Token, customTokenBinding);
}
}
Lastly, the config that puts it all together:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<exm>
<dispatchTask type="Feature.ExmTokenReplacer.MarketingAutomationDispatchTask, Feature.ExmTokenReplacer" patch:instead="*[@type='Sitecore.EmailCampaign.Cm.Dispatch.DispatchTask, Sitecore.EmailCampaign.Cm']">
<param ref="exm/dispatchFailedTaskPool"/>
<param desc="recipientValidator" ref="exm/recipientValidator" />
<param desc="contactService" ref="exm/contactService" />
<param desc="dataProvider" ref="exm/dataProvider" />
<param desc="itemUtil" ref="exm/itemUtil" />
<param desc="eventDataService" ref="exm/eventDataService" />
<param desc="dispatchManager" ref="exm/dispatchManager" />
<param desc="emailAddressHistoryManager" ref="exm/emailAddressHistoryManager" />
<param desc="recipientManagerFactory" ref="exm/recipientManagerFactory" />
<param desc="sentHistoryManager" ref="exm/sentHistoryManager" />
</dispatchTask>
</exm>
<recipients>
<recipientPropertyTokenMap type="Feature.ExmTokenReplacer.MarketingAutomationTokenMap, Feature.ExmTokenReplacer" singleInstance="true" patch:instead="*[@type='Sitecore.Modules.EmailCampaign.Core.Personalization.DefaultRecipientPropertyTokenMap, Sitecore.EmailCampaign']" />
</recipients>
</sitecore>
</configuration>
EDIT: As requested, here's the PolicyHolder JSON that's deployed in the IndexWorker and XConnect app_data/model folders:
{
"Name": "MaturityMailModel",
"Version": "1.0",
"References": [
{
"Name": "XConnect",
"Version": "1.0"
},
{
"Name": "Sitecore.XConnect.Collection.Model",
"Version": "9.0"
}
],
"Types": {
"Foundation.MarketingAutomation.Models.Facets.PolicyHolder": {
"Type": "Facet",
"BaseType": "Sitecore.XConnect.Facet",
"ClrType": "Foundation.MarketingAutomation.Models.Facets.PolicyHolder, Foundation.MarketingAutomation.Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"Properties": {
"PolicyHolderId": {
"Type": "String"
},
"InsertionCapital": {
"Type": "String"
},
"InsertionLower": {
"Type": "String"
}
}
},
(etc...)
},
"Facets": [
{
"Target": "Contact",
"Name": "PolicyHolder",
"Type": "Foundation.MarketingAutomation.Models.Facets.PolicyHolder"
},
(etc...)
]
}