EXM Unsubscribe Form

Sitecore EXM is a great feature for sending newsletters and capturing details about users’ behaviors on the newsletter. One of the features offered by EXM is to include an unsubscribe link which can be included in the newsletter, which is a cool feature to remove users from the list when the user prefers not to receive the newsletters. But, the current functionality doesn’t allow users to specify why they no longer wish to receive the newsletters. This information can be vital for businesses if they can know my the users find this information in the newsletter no longer useful.

To overcome this, I have made some changes to the Sitecore EXM unsubscribe feature to include the Sitecore Form to capture the reason to unsubscribe before removing the user from the mailing list and I would like to share the details of my implementation in this post.

Code changes

I have created a new class to capture the user details when they click on unsubscribe from the newsletter and update the Inherits of /sitecore modules/Web/EXM/Unsubscribe.aspx file used by Sitecore with the custom class below.

Create a Helper class for Contacts with the name ContactHelper as we will be sharing this later for custom Sitecore form action.

ContactHelper.cs

using Sitecore.Modules.EmailCampaign.Core;
using Sitecore.XConnect;
using System;
using Sitecore.EmailCampaign.Cd;
using Sitecore.XConnect.Collection.Model;
using System.Collections.Generic;
using Sitecore.XConnect.Client.Configuration;
using Sitecore.XConnect.Client;

namespace Sc.Foundation.Forms.Helper
{
    public static class ContactHelper
    {

        /// <summary>
        /// Get Contact by Identifier
        /// </summary>
        /// <param name="source">source to get contact from</param>
        /// <param name="indentifier">contact identifier</param>
        /// <returns>The Contact based on source and Identifier</returns>
        public static Contact GetContactByIdentifier(string source, string identifier)
        {
            var facets = new List<string>
            {
                EmailAddressList.DefaultFacetKey
            };

            var contactReference = new IdentifiedContactReference(source, identifier);
            IXdbContext client = SitecoreXConnectClientConfiguration.GetClient();
            return client.Get<Contact>(contactReference, new ContactExecutionOptions(new ContactExpandOptions(facets.ToArray()) { }));
        }
    }
}

Unsubscribe.cs

using Sitecore.Modules.EmailCampaign.Core;
using Sitecore.XConnect;
using System;
using Sitecore.EmailCampaign.Cd;
using Sitecore.XConnect.Collection.Model;
using System.Collections.Generic;
using Sc.Foundation.Forms.Helper;

namespace Sc.Feature.Email
{
    public class Unsubscribe : UnsubscribeMessageEventPage
    {
        /// <summary>
        ///This override will append additional parameters to the redirected URL
        /// </summary>
        /// <param name="contactIdentifier">Contact Identifier of the user who clicked on unsubscribe</param>
        /// <param name="messageID">Newsletter message id from which newsletter the unsubscibe is clicked</param>
        /// <returns>return url - the url of the unsubscribe form with additional query parameters to embed to form</returns>
        protected override string UnsubscribeContact(ContactIdentifier contactIdentifier, Guid messageID)
        {
            var redirectUrl = ExmContext.Message.ManagerRoot.GetAlreadyUnsubscribedPage();
            var email = string.Empty;
            try
            {
                var contact = ContactHelper.GetContactByIdentifier(contactIdentifier.Source, contactIdentifier.Identifier);
                if (contact != null)
                {
                    email = contact?.Emails()?.PreferredEmail.SmtpAddress;
                    redirectUrl = ExmContext.Message.ManagerRoot.GetSubscriptionPage();
                    return redirectUrl + "?id=" + contactIdentifier.Identifier + "&mId=" + messageID + "&src=" + contactIdentifier.Source + "&email=" + email;
                }
            }
            catch (Exception ex)
            {
                Sitecore.Diagnostics.Log.Error("Failed to update Url " + ex.Message, ex, this);
            }
            return redirectUrl;
        }
    }
}

Update the /sitecore modules/Web/EXM/Unsubscribe.aspx Inherrits with the Unsubcribe class above. the updated /sitecore modules/Web/EXM/Unsubscribe.aspx will look like

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Unsubscribe.aspx.cs" Inherits="Sc.Feature.Email.Unsubscribe" %>
<%@ Register TagPrefix="sc" Namespace="Sitecore.Web.UI.WebControls" Assembly="Sitecore.Analytics" %>
<%@ OutputCache Location="None" VaryByParam="none" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>
<html lang="en" xml:lang="en" xmlns=http://www.w3.org/1999/xhtml>
<head runat="server">
  <title>Unsubscribe</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <meta name="CODE_LANGUAGE" content="C#" />
  <meta name="vs_defaultClientScript" content="JavaScript" />
  <meta name="vs_targetSchema" content=http://schemas.microsoft.com/intellisense/ie5 />
  <sc:VisitorIdentification runat="server" />
</head>
<body>
</body>
</html>

Create custom Sitecore form action to Unsubscribe the user and remove the user from the list

Create a Model class called UnsubscribeModel

UnsubscribeModel.cs

namespace Sc.Foundation.Forms.Models
{
    public class UnsubscribeModel
    {
        public string Identifier { get; set; }
        public string MessageId { get; set; }
        public string Source { get; set; }
    }
}

Create an helper class to get string values from form objects with name ReplaceToken

ReplaceToken.cs

using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Mvc.Models.Fields;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Sc.Foundation.Forms.Helper
{
    public static class TokenHelper
    {
        public static string ReplaceTokens(string template, IList<IViewModel> formFields)
        {
            if (string.IsNullOrWhiteSpace(template))
                return string.Empty;

            var tokenList = Regex.Matches(template, @"\[(.+?)\]");

            if (tokenList.Count <= 0)
                return template;

            var usedTokens = new Dictionary<string, bool>();
            foreach (Match token in tokenList)
            {
                if (usedTokens.ContainsKey(token.Value))
                    continue;

                var tokenName = token.Value.TrimStart('[').TrimEnd(']');
                var matchingField = formFields.FirstOrDefault(f => f.Name == tokenName);

                if (matchingField != null)
                    template = template.Replace(token.Value, GetFieldStringValue(matchingField));

                usedTokens.Add(token.Value, true);
            }
            return template;
        }

        private static string GetFieldStringValue(object field)
        {
            if (field != null && field is ListViewModel)
            {
                var listField = (ListViewModel)field;

                if (listField.Value == null || !listField.Value.Any())
                    return string.Empty;

                return string.Join(", ", listField.Value);
            }

            return field?.GetType().GetProperty("Value").GetValue(field, null)?.ToString() ?? string.Empty;
        }
    }
}

Create a custom form action class to process unsubscription of contact from newsletter list with name UnsubscribeFromNewsletter

UnsubscribeFromNewsletter.cs

using Sc.Foundation.Forms.Helper;
using Sc.Foundation.Forms.Models;
using Sitecore.DependencyInjection;
using Sitecore.EmailCampaign.Cd.Services;
using Sitecore.EmailCampaign.Model.Messaging;
using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Processing;
using Sitecore.ExperienceForms.Processing.Actions;
using System;
using System.Linq;

namespace Sc.Foundation.Forms.Actions
{
    public class UnsubscribeFromNewsletter : SubmitActionBase<UnsubscribeModel>
    {

        /// <summary>
        /// EXM client service.
        /// </summary>
        private readonly IClientApiService clientApiService;

        /// <summary>
        /// Initializes a new instance of the <see cref="UnsubscribeModel"/> class.
        /// </summary>
        /// <param name="submitActionData">The submit action data.</param>
        public UnsubscribeFromNewsletter(ISubmitActionData submitActionData) : base(submitActionData)
        {
            clientApiService = ServiceLocator.ServiceProvider.GetService(typeof(IClientApiService)) as IClientApiService;
        }

        protected override bool Execute(UnsubscribeModel data, FormSubmitContext formSubmitContext)
        {
            try
            {
                var identifier = TokenHelper.ReplaceTokens(data.Identifier, formSubmitContext.Fields);
                var messageId = TokenHelper.ReplaceTokens(data.MessageId, formSubmitContext.Fields);
                var source = TokenHelper.ReplaceTokens(data.Source, formSubmitContext.Fields);
                var contact = ContactHelper.GetContactByIdentifier(source, identifier);

                if (contact == null)
                {
                    Logger.Info("Contact not found for identifier");
                }
                else
                {
                    var subscriptionMessage = new UpdateListSubscriptionMessage() { ListSubscribeOperation = ListSubscribeOperation.Unsubscribe, ContactIdentifier = contact.Identifiers.FirstOrDefault(), MessageId = new Guid(messageId) };
                    clientApiService.UpdateListSubscription(subscriptionMessage);
                    return true;
                }
            }
            catch (Exception ex)
            {
                Logger.Info("failed to unsubscribe");
                Logger.LogError(ex.Message, ex + ex.StackTrace);
            }
            return false;
        }
    }
}

Create a Custom FieldValueProvider class

This class is to read the URL query parameters and update the form fields

using Sitecore.ExperienceForms.ValueProviders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace Sc.Foundation.Forms.FieldValueProviders
{
    public class UriFieldValueProvider : IFieldValueProvider
    {
        public FieldValueProviderContext ValueProviderContext { get; set; }

        public object GetValue(string parameters)
        {
            try
            {
                Uri uri = new Uri(HttpContext.Current.Request.Url.AbsoluteUri);
                return HttpUtility.ParseQueryString(uri.Query).Get(parameters);
            }
            catch (Exception ex)
            {
                Sitecore.Diagnostics.Log.Error("Failed to get UriFieldValue " + ex.Message, this);
            }
            return string.Empty;
        }
    }
}

All the code required to implement the custom unsubscribe and link it with the form is now done.

Sitecore Changes

It’s time to integrate all the code changes in Sitecore and make it work. Let’s start with creating the custom form action for the Sitecore form to process unsubscribe the user on form submit.

Create custom Sitecore Form Action

Go back to the desktop and switch to Master database

Go to Content Editor

Navigate to /sitecore/system/Settings/Forms/Submit Actions

Right-click and Create Submit Action with the name “Unsubscribe from Newsletter”

Select the suitable icon for the item

Fill in the following fields

ModelType = “Sc.Foundation.Forms.Actions.UnsubscribeFromNewsletter”

Error Message = “Failed to unsubscribe user!”

Editor = “Unsubscribe newsletter” pick it from dropdown

Save the item and publish it

Create Field Value Parameters

Go to the Content Editor

Navigate to /sitecore/system/Settings/Forms/Value Providers

Add a new Item with the name “Uri Value Provider” using template “/System/Forms/Value Provider”

Update Model Type field with Sc.Foundation.Forms.FieldValueProviders.UriFieldValueProvider

Publish the item

Create unsubscribe form

This form will be used to capture the reason from users when they click on unsubscribe from the newsletter

Add Single line text field for Email

Set CSS class for label and field with d-none (Bootstrap class for hiding)

Go to Advanced Settings and Set Value provider with Url Value Provider and Value Provider Parameters with email

Repeat the same for Identifier, MessageId, and Source (Parameter values will change from email to id, mid, src respectively)

Add any additional sections or information you prefer to capture in the form

Add Submit button and label is “Unsubscribe”

Add Save action to the Unsubscribe submit button

Add Unsubscribe from Newsletter submit action

Create an Unsubscribe Page

Create a new page in your Sitecore instance and add the unsubscribe form to it and publish it.

Configure EXM to use the Unsubscribe page

Update EXM redirect settings to use the Unsubscribe page

Navigate to /sitecore/content/Email

Set Subscription Page – The page displayed if an unidentified visitor wants to change their subscription with Unsubscribe Page.

Publish all the changes.

This is all the changes you need. Once this is configured and a user clicks on the Unsubscribe link in the newsletter, the user will be redirected to the unsubscribe page with the form displayed. on submission of the form, the user will be unsubscribed from the newsletter. the unsubscribed reason captured in the form will be stored in the forms database and can be downloaded easily.

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *