Handling newsletter subscriptions on MVC sites

You can use the email marketing features of Kentico with websites that are presented by a separate MVC application.

In this scenario, you manage and send newsletters from the administration interface of the Kentico application. All settings related to email marketing that you configure in Kentico apply (both global settings and the configuration of individual newsletters). On the side of the MVC application, you need to handle the actions that visitors perform on the pages of the website – newsletter subscription and unsubscription.

To allow visitors on your MVC website to manage subscriptions for your newsletters:

  1. Initialize subscription-related services.

  2. Implement the required functionality on your MVC site:

  3. Enable resource sharing with the Kentico administration application.

Initializing subscription services

The API for managing newsletter subscriptions is provided by services from the CMS.Newsletters namespace (part of the API provided by the Kentico.Libraries NuGet package).

  • ISubscriptionService – handles subscription and unsubscription for email feeds.
  • IUnsubscriptionProvider – allows verification and management of email feed unsubscriptions and opt-out lists.
  • IContactProvider – creates or gets contact objects, which are used to represent newsletter recipients (subscribers).
  • IEmailHashValidator – handles validation of security hashes for email addresses (for unsubscription and subscription confirmation links).
  • ISubscriptionApprovalService – handles approval of subscriptions for newsletters that use double opt-in.

You need to initialize instances of these services and make them available in the related controllers.

We recommend using a dependency injection container to initialize service instances.




using CMS.Newsletters;
using CMS.Core;






        private readonly ISubscriptionService subscriptionService;
        private readonly IUnsubscriptionProvider unsubscriptionProvider;
        private readonly IContactProvider subscriptionContactProvider;
        private readonly IEmailHashValidator emailHashValidatorService;        
        private readonly ISubscriptionApprovalService approvalService;

        public NewsletterSubscriptionController()
        {
            // Initializes instances of services required to manage subscriptions and unsubscriptions for all types of email feeds
            // For real-world projects, we recommend using a dependency injection container to initialize service instances
            subscriptionService = Service.Resolve<ISubscriptionService>();
            unsubscriptionProvider = Service.Resolve<IUnsubscriptionProvider>();
            subscriptionContactProvider = Service.Resolve<IContactProvider>();
            emailHashValidatorService = Service.Resolve<IEmailHashValidator>();
            approvalService = Service.Resolve<ISubscriptionApprovalService>();
        }



You can now call the methods of the subscription services within your application’s code to perform actions related to newsletter subscription and unsubscription.

Setting up subscription

Use the following approach to develop actions that allow visitors to subscribe to newsletters on your website:

Tip: To view the full code of a functional example, you can inspect and download the LearningKit project on GitHub. You can also run the LearningKit website by connecting the project to a Kentico database.

  1. Create a newsletter for your MVC site in Kentico (or use an existing one). See Creating newsletters for more information.

  2. Create a new controller class in your MVC project or edit an existing one.

  3. Implement two subscription actions – one basic GET action to display a subscription form and a second POST action to handle the creation of new recipients when the form is submitted.

  4. Perform the following steps within the POST action:

    1. Get a ContactInfo object representing the new recipient based on the email address posted from the subscription form – call the GetContactForSubscribing method of the IContactProvider service instance.

    2. Use the NewsletterInfoProvider class to get a NewsletterInfo object representing the newsletter for which you are implementing subscription.

    3. Call the Subscribe method of the ISubscriptionService instance with the prepared ContactInfo and NewsletterInfo objects as parameters.

      Notes:

      • The GetContactForSubscribing method automatically creates the contact object in Kentico based on the specified email address (if necessary).
      • The Subscribe method adds the contact as a recipient of the newsletter.
      • If you wish to check whether a contact with the given email address is already a recipient of a newsletter, call the IsSubscribed method of the ISubscriptionService instance.
    
    
    
     using System;
     using System.Web.Mvc;
    
     using CMS.Core;
     using CMS.ContactManagement;
     using CMS.Newsletters;
     using CMS.SiteProvider;
     using CMS.Helpers;
    
    
    
     
    Subscription actions example
    
    
    
             /// <summary>
             /// Basic action that displays the newsletter subscription form.
             /// </summary>
             public ActionResult Subscribe()
             {
                 return View();
             }
    
             /// <summary>
             /// Handles creation of new marketing email recipients when the subscription form is submitted.
             /// Accepts an email address parameter posted from the subscription form.
             /// </summary>
             [HttpPost]
             [ValidateAntiForgeryToken]
             public ActionResult Subscribe(NewsletterSubscribeViewModel model)
             {
                 if (!ModelState.IsValid)
                 {
                     // If the subscription data is not valid, displays the subscription form with error messages
                     return View(model);
                 }
    
                 // Either gets an existing contact by email or creates a new contact object with the given email
                 ContactInfo contact = subscriptionContactProvider.GetContactForSubscribing(model.Email);
    
                 // Gets a newsletter
                 // Fill in the code name of your newsletter object in Kentico
                 NewsletterInfo newsletter = NewsletterInfoProvider.GetNewsletterInfo("SampleNewsletter", SiteContext.CurrentSiteID);
    
                 // Prepares settings that configure the subscription behavior
                 var subscriptionSettings = new SubscribeSettings()
                 {
                     RemoveAlsoUnsubscriptionFromAllNewsletters = true, // Subscription removes the given email address from the global opt-out list for all marketing emails (if present)
                     SendConfirmationEmail = true, // Allows sending of confirmation emails for the subscription
                     AllowOptIn = true // Allows handling of double opt-in subscription for newsletters that have it enabled
                 };
    
                 // Subscribes the contact with the specified email address to the given newsletter
                 subscriptionService.Subscribe(contact, newsletter, subscriptionSettings);
    
                 // Passes information to the view, indicating whether the newsletter requires double opt-in for subscription
                 model.RequireDoubleOptIn = newsletter.NewsletterEnableOptIn;
    
                 // Displays a view to inform the user that the subscription was successful
                 return View("SubscribeSuccess", model);
             }
    
    
    
     
  5. We recommend creating a view model for your subscription action (NewsletterSubscribeViewModel in the example above). The view model allows you to:

    • Pass parameters from the subscription form (email address and, optionally, the recipient’s name).

    • Use data annotations to define validation and formatting rules for the subscription data. See System.ComponentModel.DataAnnotations on MSDN for more information about the available data annotation attributes.

    • Pass information back to the subscription form, for example, a flag indicating that the newsletter requires a double opt-in subscription.

      Subscription view model example
      
      
      
        using System.ComponentModel;
        using System.ComponentModel.DataAnnotations;
      
        public class NewsletterSubscribeViewModel
        {
            [DataType(DataType.EmailAddress)]
            [Required(ErrorMessage = "The Email address cannot be empty.")]
            [DisplayName("Email address")]
            [EmailAddress(ErrorMessage = "Invalid email address.")]
            [MaxLength(254, ErrorMessage = "The Email address cannot be longer than 254 characters.")]
            public string Email
            {
                get;
                set;
            }
      
            /// <summary>
            /// Indicates whether the newsletter requires double-opt in for subscription.
            /// Allows the view to display appropriate information to newly subscribed users.
            /// </summary>
            [Bindable(false)]
            public bool RequireDoubleOptIn
            {
                get;
                set;
            }
        }
      
      
        
  6. Design the user interface required for newsletter subscription on your website:

    • Create a view for the Subscribe action and display an appropriate subscription form. We recommend using a strongly typed view based on your subscription view model.
    • Create any other views required by the Subscribe action, for example, to inform users about successful subscriptions.

Your website’s visitors can now subscribe to newsletters. The system automatically performs all standard Kentico subscription features, such as sending of confirmation emails based on the newsletter’s Subscription confirmation template.

Continue by setting up:

Setting up unsubscription

Users can unsubscribe from email campaigns or newsletters by clicking links placed into the content of your emails.

When setting up unsubscription functionality, you first need to ensure that the Kentico application generates correct unsubscription links:

  1. Make sure that your site in Kentico is configured to use extensionless URLs by default – set Settings -> URLs and SEO -> Friendly URLs extension to an empty value.

  2. Add unsubscription links into the content of your marketing emails or email templates (in the Email marketing application).

    Use the {% EmailFeed.UnsubscribeFromEmailFeedUrl %} and {% EmailFeed.UnsubscribeFromAllEmailFeedsUrl %} macros to generate the URLs of the links.

    See Preparing email templates to learn more.

  3. In the Email marketing application, edit your email feeds, open their Configuration tab and set the following properties:

    • Base URL – must match the Presentation URL of your MVC site. For example: http://www.SiteDomain.com

    • Unsubscription page URL – must match the route of the action that handles unsubscription requests in your MVC application. For example: /NewsletterSubscription/Unsubscribe

      To use a shared Unsubscription page URL for multiple email feeds, leave the property empty for individual email feeds and set the value in the Settings -> On-line marketing -> Email marketing -> Unsubscription page URL setting.

Continue by implementing a controller action within your MVC application for handling of unsubscription requests:

  1. Create a new model class in your MVC project. The model will represent the parameters that Kentico generates in newsletter and email campaign unsubscription links.

    Unsubscription model
    
    
    
     using System;
     using System.ComponentModel.DataAnnotations;
    
     public class MarketingEmailUnsubscribeModel
     {
         /// <summary>
         /// The email address of the recipient who is requesting unsubscription.
         /// </summary>
         [Required]
         public string Email
         {
             get;
             set;
         }
    
         /// <summary>
         /// The GUID (identifier) of the Kentico email feed related to the unsubscription request.
         /// </summary>
         [Required]
         public Guid NewsletterGuid
         {
             get;
             set;
         }
    
         /// <summary>
         /// The GUID (identifier) of the Kentico marketing email related to the unsubscription request.
         /// </summary>
         [Required]
         public Guid IssueGuid
         {
             get;
             set;
         }
    
         /// <summary>
         /// Hash for protection against forged unsubscription requests.
         /// </summary>
         [Required]
         public string Hash
         {
             get;
             set;
         }
    
         /// <summary>
         /// Indicates whether the unsubscription request is for all marketing emails or only a specific email feed.
         /// </summary>
         public bool UnsubscribeFromAll
         {
             get;
             set;
         }
     }
    
    
     
  2. Add a GET action to your newsletter subscription controller and perform the following steps:

    1. Use the unsubscription model to pass parameters from the unsubscription request.
    2. Verify that the unsubscription hash is valid for the given email address – call the ValidateEmailHash method of the IEmailHashValidator service instance, with the email address and hash from the unsubscription request as parameters.
    3. Verify that the email address is not already unsubscribed by calling the IsUnsubscribedFromSingleNewsletter and IsUnsubscribedFromAllNewsletters methods of the IUnsubscriptionProvider instance. Branch the logic based on the UnsubscribeFromAll parameter from the unsubscription request.
    4. To remove subscriptions, call the UnsubscribeFromSingleNewsletter or UnsubscribeFromAllNewsletters method of the ISubscriptionService instance (with parameters taken from the unsubscription request).



using System.Web.Mvc;

using CMS.Newsletters;
using CMS.SiteProvider;


Unsubscription action example



        /// <summary>
        /// Handles marketing email unsubscription requests.
        /// </summary>
        public ActionResult Unsubscribe(MarketingEmailUnsubscribeModel model)
        {
            // Verifies that the unsubscription request contains all required parameters
            if (ModelState.IsValid)
            {
                // Confirms whether the hash in the unsubscription request is valid for the given email address
                // Provides protection against forged unsubscription requests
                if (emailHashValidatorService.ValidateEmailHash(model.Hash, model.Email))
                {
                    // Gets the marketing email (issue) from which the unsubscription request was sent
                    IssueInfo issue = IssueInfoProvider.GetIssueInfo(model.IssueGuid, SiteContext.CurrentSiteID);

                    if (model.UnsubscribeFromAll)
                    {
                        // Checks that the email address is not already unsubscribed from all marketing emails
                        if (!unsubscriptionProvider.IsUnsubscribedFromAllNewsletters(model.Email))
                        {
                            // Unsubscribes the specified email address from all marketing emails (adds it to the opt-out list)
                            subscriptionService.UnsubscribeFromAllNewsletters(model.Email, issue?.IssueID);
                        }
                    }
                    else
                    {
                        // Gets the email feed for which the unsubscription was requested
                        NewsletterInfo newsletter = NewsletterInfoProvider.GetNewsletterInfo(model.NewsletterGuid, SiteContext.CurrentSiteID);

                        if (newsletter != null)
                        {
                            // Checks that the email address is not already unsubscribed from the specified email feed
                            if (!unsubscriptionProvider.IsUnsubscribedFromSingleNewsletter(model.Email, newsletter.NewsletterID))
                            {
                                // Unsubscribes the specified email address from the email feed
                                subscriptionService.UnsubscribeFromSingleNewsletter(model.Email, newsletter.NewsletterID, issue?.IssueID);
                            }
                        }
                    }

                    // Displays a view to inform the user that they were unsubscribed
                    return View("UnsubscribeSuccess");
                }
            }

            // If the unsubscription was not successful, displays a view to inform the user
            // Failure can occur if the request does not provide all required parameters or if the hash is invalid
            return View("UnsubscribeFailure");
        }



  1. Create views for informing recipients about the result of their unsubscription requests.

When a user clicks on an unsubscription link in a newsletter, the URL leads to the unsubscribe action on your MVC site. The action validates the parameters and either unsubscribes the recipient or displays information if the request is invalid.

The content of the unsubscription page depends on your implementation of the returned views.

Handling confirmations for double opt-in subscription

If your newsletters are configured to use double opt-in, you need to set up additional logic on your MVC site to allow users to confirm their newsletter subscriptions.

When a visitor subscribes to a newsletter with double opt-in enabled, the system automatically adds the recipient with the “Waiting for confirmation” subscription status and the Kentico application sends a confirmation email to the given email address. You need to ensure that the double opt-in email contains a correct confirmation link:

  1. Make sure that your site in Kentico is configured to use extensionless URLs by default – set Settings -> URLs and SEO -> Friendly URLs extension to an empty value.

  2. Add the confirmation link into the content of the Double opt-in template assigned to your newsletter (in the Email marketing application).

    Use the {% EmailFeed.SubscriptionConfirmationUrl %} macro to generate the URL of the subscription confirmation link. See Preparing email templates to learn more.

  3. In the Email marketing application, edit your newsletters, open their Configuration tab and set the following properties:

    • Base URL – must match the Presentation URL of your MVC site. For example: http://www.SiteDomain.com
    • Approval page URL – must match the route of the action that handles subscription confirmation requests in your MVC application. For example: /NewsletterSubscription/ConfirmSubscription

To use a shared Approval page URL for multiple newsletters, leave the property empty for individual newsletters and set the value in the Settings -> On-line marketing -> Email marketing -> Double opt-in approval page URL setting.

Continue by implementing a controller action within your MVC application to handle the subscription confirmation requests:

  1. Create a new model class in your MVC project. The model will represent the parameters that Kentico generates in subscription confirmation links.

    Subscription confirmation view model
    
    
    
     using System.ComponentModel.DataAnnotations;
    
     public class NewsletterConfirmSubscriptionViewModel
     {
         /// <summary>
         /// Hash used to identify the subscription and for protection against forged confirmation requests.
         /// </summary>
         [Required]
         public string SubscriptionHash
         {
             get;
             set;
         }
    
         /// <summary>
         /// The date and time of the original subscription. Used to detect expired confirmation requests.
         /// </summary>
         public string DateTime
         {
             get;
             set;
         }
     }
    
    
     
  2. Add a GET action to your newsletter subscription controller and perform the following steps:

    1. Use the subscription confirmation model to pass parameters from the confirmation request.
    2. Parse the date and time parameter from the confirmation request. The value uses a specific date and time format required by the Kentico API (see the code example below for details).
    3. To confirm subscriptions, call the ApproveSubscription method of the ISubscriptionApprovalService instance (with parameters taken from the hash and DateTime value in the confirmation request).
    4. Handle the result of the subscription confirmation based on the ApprovalResult value returned by the ApproveSubscription method (see the code example below for information about the possible values).



using System;
using System.Web.Mvc;

using CMS.Core;
using CMS.ContactManagement;
using CMS.Newsletters;
using CMS.SiteProvider;
using CMS.Helpers;



Subscription confirmation action example



        /// <summary>
        /// Handles confirmation requests for newsletter subscriptions (when using double opt-in).
        /// </summary>
        public ActionResult ConfirmSubscription(NewsletterConfirmSubscriptionViewModel model)
        {
            // Verifies that the confirmation request contains the required hash parameter
            if (!ModelState.IsValid)
            {
                // If the hash is missing, returns a view informing the user that the subscription confirmation was not successful
                ModelState.AddModelError(String.Empty, "The confirmation link is invalid.");
                return View(model);
            }

            // Attempts to parse the date and time parameter from the request query string
            // Uses the date and time formats required by the Kentico API
            DateTime parsedDateTime = DateTimeHelper.ZERO_TIME;
            if (!string.IsNullOrEmpty(model.DateTime) && !DateTimeUrlFormatter.TryParse(model.DateTime, out parsedDateTime))
            {
                // Returns a view informing the user that the subscription confirmation was not successful
                ModelState.AddModelError(String.Empty, "The confirmation link is invalid.");
                return View(model);
            }

            // Attempts to confirm the subscription specified by the request's parameters
            ApprovalResult result = approvalService.ApproveSubscription(model.SubscriptionHash, false, SiteContext.CurrentSiteName, parsedDateTime);

            switch (result)
            {
                // The confirmation was successful or the recipient was already approved
                // Displays a view informing the user that the subscription is active
                case ApprovalResult.Success:
                case ApprovalResult.AlreadyApproved:
                    return View(model);

                // The confirmation link has expired
                // Expiration occurs after a certain number of hours from the time of the original subscription
                // You can set the expiration interval in Kentico (Settings -> On‑line marketing -> Email marketing -> Double opt-in interval)
                case ApprovalResult.TimeExceeded:
                    ModelState.AddModelError(String.Empty, "Your confirmation link has expired. Please subscribe to the newsletter again.");
                    break;

                // The subscription specified in the request's parameters does not exist
                case ApprovalResult.NotFound:
                    ModelState.AddModelError(String.Empty, "The subscription that you are attempting to confirm does not exist.");
                    break;

                // The confirmation failed
                default:
                    ModelState.AddModelError(String.Empty, "The confirmation of your newsletter subscription did not succeed.");
                    break;
            }           

            // If the subscription confirmation was not successful, displays a view informing the user
            return View(model);
        }



  1. Create a view for informing users about the result of their subscription confirmation requests.

When a new recipient clicks the link in the confirmation email received after subscribing to a newsletter with double opt-in enabled, the confirmation action on your MVC site attempts to confirm the subscription. If the link is valid (based on the expiration interval configured in the Kentico application in Settings -> On‑line marketing -> Email marketing -> Double opt-in interval), the system updates the recipient to the “Subscribed” status and the newsletter subscription becomes active.

The content of the subscription approval page depends on your implementation of the returned views.

Enabling resource sharing

When running an MVC site, the Email marketing application in the Kentico administration interface automatically checks the accessibility of the URLs specified for each email feed. This includes the following:

If any of the given URLs are not accessible, the system displays a warning on the Emails tab of the email feed and the Send tab of individual emails.

To allow the system to verify the URLs correctly in scenarios where your MVC site and Kentico administration application run on different domains, you need to have cross-origin resource sharing enabled.

If you have hotfix 12.0.30 or newer installed, the resource sharing feature is enabled automatically. When processing requests sent from the domain of the connected Kentico administration application, your MVC application sends a response with the Access-Control-Allow-Origin header, which enables cross origin resource sharing.

You only need to enable the resource sharing feature manually in the initial Kentico 12 Service Pack release (hotfix 12.0.29) and older versions of Kentico.

To enable the resource sharing feature for your MVC application on older hotfix versions – call the UseResourceSharingWithAdministration() method of the ApplicationBuilder instance at the start of your application’s life cycle, for example in the Application_Start method of your project’s Global.asax file.

MVC projects created by the installer contain the ApplicationConfig class, whose RegisterFeatures method is called in the Application_Start method by default. You can use this class to encapsulate all of your ApplicationBuilder code (enabling and configuring of Kentico MVC features).

Example



using Kentico.Web.Mvc;

...

protected void Application_Start()
{
    ...

    // Gets the ApplicationBuilder instance
    // Allows you to enable and configure selected Kentico MVC integration features
    ApplicationBuilder builder = ApplicationBuilder.Current;

    // Enables cross-origin resource sharing with the connected Kentico administration application
    builder.UseResourceSharingWithAdministration();

    ...
}