SMS and MMS Marketing Notifications with C# and ASP.NET MVC

Ready to implement SMS and MMS marketing notifications?  

Excellent - you've come to the right place.  Today we'll explore adding them to a C# and ASP.NET MVC application.

Here's how it all works at a high level:

  1. A possible customer sends an SMS to a Twilio phone number you advertise somewhere.
  2. Your application confirms that the user wants to receive SMS and MMS notifications from your company.
  3. An administrator or marketing campaign manager uses a web form to craft a message that will go out to all subscribers via SMS/MMS message.

Building Blocks

To get this done, we'll be working with the following Twilio tools:

Let's get started! Click the arrow below to move on to the next step of the tutorial.

Subscriber

In order to send out marketing notifications to a subscriber, we need to first provide the right model:

  • PhoneNumber will store where to send the notifications.
  • Subscribed will store who is opted in to receive notifications.
Loading Code Samples...
Language
using System;

namespace MarketingNotifications.Web.Models
{
    public class Subscriber
    {
        public int Id { get; set; }
        public String PhoneNumber { get; set; }
        public bool Subscribed { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}
MarketingNotifications.Web/Models/Subscriber.cs
Subscriber model

MarketingNotifications.Web/Models/Subscriber.cs

Next, let's look at how we'll manage incoming messages.

Handling Incoming messages

This is the endpoint that will be called every time our application receives a message.

It relies on a MessageCreator abstraction that produces the message that will be returned to the sender. It also neatly hides the complexity behind creating new subscribers.

Loading Code Samples...
Language
using System.Threading.Tasks;
using System.Web.Mvc;
using MarketingNotifications.Web.Domain;
using MarketingNotifications.Web.Models.Repository;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace MarketingNotifications.Web.Controllers
{
    public class SubscribersController : TwilioController
    {
        private readonly ISubscribersRepository _repository;

        public SubscribersController() : this(new SubscribersRepository()) { }

        public SubscribersController(ISubscribersRepository repository)
        {
            _repository = repository;
        }

        // POST: Subscribers/Register
        [HttpPost]
        public async Task<TwiMLResult> Register(string from, string body)
        {
            var phoneNumber = from;
            var message = body;

            var messageCreator = new MessageCreator(_repository);
            var outputMessage = await messageCreator.Create(phoneNumber, message);

            var response = new MessagingResponse();
            response.Message(outputMessage);

            return TwiML(response);
        }
    }
}
MarketingNotifications.Web/Controllers/SubscribersController.cs
The Subscribers Controller

MarketingNotifications.Web/Controllers/SubscribersController.cs

When a new user sends us a message, we need to manage adding him or her to our database.  We'll explore that functionality next.

Creating New Subscribers

We begin by getting a new customer's phone number from the incoming Twilio request. Next, we try to find a Subscriber model with that phone number.

If there's no subscriber with this phone number, we create one, save it, and respond with a friendly message asking to reply "add" back.  We are very careful to confirm they want to receive messages from our company before flagging them as subscribed.

Loading Code Samples...
Language
using System;
using System.Threading.Tasks;
using MarketingNotifications.Web.Models;
using MarketingNotifications.Web.Models.Repository;

namespace MarketingNotifications.Web.Domain
{
    public class MessageCreator
    {
        private readonly ISubscribersRepository _repository;
        private const string Subscribe = "add";
        private const string Unsubscribe = "remove";

        public MessageCreator(ISubscribersRepository repository)
        {
            _repository = repository;
        }

        public async Task<string> Create(string phoneNumber, string message)
        {
            var subscriber = await _repository.FindByPhoneNumberAsync(phoneNumber);
            if (subscriber != null)
            {
                return await CreateOutputMessage(subscriber, message.ToLower());
            }

            subscriber = new Subscriber
            {
                PhoneNumber = phoneNumber,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            };
            await _repository.CreateAsync(subscriber);
            return "Thanks for contacting TWBC! Text 'add' if you would to receive updates via text message.";
        }

        private async Task<string> CreateOutputMessage(Subscriber subscriber, string message)
        {
            if (!IsValidCommand(message))
            {
                return "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";
            }

            var isSubscribed = message.StartsWith(Subscribe);
            subscriber.Subscribed = isSubscribed;
            subscriber.UpdatedAt = DateTime.Now;
            await _repository.UpdateAsync(subscriber);

            return isSubscribed
                ? "You are now subscribed for updates."
                : "You have unsubscribed from notifications. Test 'add' to start receiving updates again";
        }

        private static bool IsValidCommand(string command)
        {
            return command.StartsWith(Subscribe) || command.StartsWith(Unsubscribe);
        }
    }
}
MarketingNotifications.Web/Domain/MessageCreator.cs
Looking up a user and potentially adding them to the database

MarketingNotifications.Web/Domain/MessageCreator.cs

And that's all we want at this step! We've created a Subscriber model to keep track of the people that have requested our messages. We also have saved them in the database when they text us for the first time.

Next let's go further and look at how a user will modify his or her subscriber status.

Manage Subscriptions

We want to provide the user with two SMS commands to manage their subscription status: add and remove.

These commands will toggle a boolean flag for their Subscriber record in the database, and will determine whether or not they want to receive messages from our marketing campaign. Because we want to respect our users' desires, we don't opt them in automatically.  We are sure to have them confirm that they want to receive our messages.

To make this happen, we will need to update the controller logic which handles the incoming text message to do a couple things:

  • If the user is a person already in the database, parse the message they sent to see if it's a command we recognize.
  • If it is a add or remove command, update her or his subscription status in the database.
  • If it is a command we don't recognize, send her or him a message explaining available commands.
Loading Code Samples...
Language
using System;
using System.Threading.Tasks;
using MarketingNotifications.Web.Models;
using MarketingNotifications.Web.Models.Repository;

namespace MarketingNotifications.Web.Domain
{
    public class MessageCreator
    {
        private readonly ISubscribersRepository _repository;
        private const string Subscribe = "add";
        private const string Unsubscribe = "remove";

        public MessageCreator(ISubscribersRepository repository)
        {
            _repository = repository;
        }

        public async Task<string> Create(string phoneNumber, string message)
        {
            var subscriber = await _repository.FindByPhoneNumberAsync(phoneNumber);
            if (subscriber != null)
            {
                return await CreateOutputMessage(subscriber, message.ToLower());
            }

            subscriber = new Subscriber
            {
                PhoneNumber = phoneNumber,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            };
            await _repository.CreateAsync(subscriber);
            return "Thanks for contacting TWBC! Text 'add' if you would to receive updates via text message.";
        }

        private async Task<string> CreateOutputMessage(Subscriber subscriber, string message)
        {
            if (!IsValidCommand(message))
            {
                return "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";
            }

            var isSubscribed = message.StartsWith(Subscribe);
            subscriber.Subscribed = isSubscribed;
            subscriber.UpdatedAt = DateTime.Now;
            await _repository.UpdateAsync(subscriber);

            return isSubscribed
                ? "You are now subscribed for updates."
                : "You have unsubscribed from notifications. Test 'add' to start receiving updates again";
        }

        private static bool IsValidCommand(string command)
        {
            return command.StartsWith(Subscribe) || command.StartsWith(Unsubscribe);
        }
    }
}
MarketingNotifications.Web/Domain/MessageCreator.cs
Dealing with incoming user commands

MarketingNotifications.Web/Domain/MessageCreator.cs

With the logic behind subscribing and unsubscribing implemented, let's move on to sending messages out.

Sending Notifications

On the server, we grab an incoming form's text and (optional) image URL, then loop through all Subscribers to call the method Send on our MessageSender domain object.

When the messages are on their way, we redirect the form submitter back to the same form with a ViewBag Property message containing feedback about the messaging attempt.

Loading Code Samples...
Language
using System.Threading.Tasks;
using System.Web.Mvc;
using System;
using MarketingNotifications.Web.Domain;
using MarketingNotifications.Web.Models.Repository;
using MarketingNotifications.Web.ViewModels;
using System.Collections.Generic;

namespace MarketingNotifications.Web.Controllers
{
    public class NotificationsController : Controller
    {
        private readonly ISubscribersRepository _repository;
        private readonly INotificationService _notificationService;

        public NotificationsController() : this(new SubscribersRepository(), new NotificationService())
        {
        }

        public NotificationsController(ISubscribersRepository repository, INotificationService notificationService)
        {
            _notificationService = notificationService;
            _repository = repository;
        }

        // GET: Notifications/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Notifications/Create
        [HttpPost]
        public async Task<ActionResult> Create(NotificationViewModel model)
        {
            if (ModelState.IsValid)
            {
                var mediaUrl = new List<Uri> { new Uri(model.ImageUrl) };
                var subscribers = await _repository.FindActiveSubscribersAsync();
                foreach (var subscriber in subscribers)
                {
                    await _notificationService.SendMessageAsync(
                        subscriber.PhoneNumber,
                        model.Message,
                        mediaUrl);
                }

                ModelState.Clear();
                ViewBag.FlashMessage = "Messages on their way!";
                return View();
            }

            return View(model);
        }
    }
}
MarketingNotifications.Web/Controllers/NotificationsController.cs
The Notifications controller

MarketingNotifications.Web/Controllers/NotificationsController.cs

And how do we actually send the MMS or SMS messages?  Glad you asked - let's go there next.

Send SMS or MMS Notifications

Now that we have a list of subscribers for our awesome SMS and MMS content, we need to provide our marketing team some kind of interface to send out their perfectly crafted messages.

When the model object is loaded, it initializes a Twilio REST API client that it can be used to send SMS and MMS messages. The client requires your Twilio account credentials (an account SID and auth token), which can be found in the console:

console credentials

 

Next all we need to do is call MessageResource.CreateAsync in order to send our message. The Twilio Message API call requires a From and To parameter, and either Body or a MediaUrl (or both).

Loading Code Samples...
Language
using MarketingNotifications.Web.Domain.Twilio;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
using Twilio;

namespace MarketingNotifications.Web.Domain
{
    public interface INotificationService
    {
        Task<MessageResource> SendMessageAsync(string to, string body, List<Uri> mediaUrl);
    }

    public class NotificationService : INotificationService
    {
        public NotificationService()
        {
            if (Configuration.Credentials.AccountSID != null && Configuration.Credentials.AuthToken != null)
            {
                TwilioClient.Init(Configuration.Credentials.AccountSID, Configuration.Credentials.AuthToken);
            }
        }

        public async Task<MessageResource> SendMessageAsync(string to, string body, List<Uri> mediaUrl)
        {
            return await MessageResource.CreateAsync(
                from: new PhoneNumber(Configuration.PhoneNumbers.Twilio),
                to: new PhoneNumber(to),
                body: body,
                mediaUrl: mediaUrl);
        }
    }
}
MarketingNotifications.Web/Domain/NotificationService.cs
A Notifications service

MarketingNotifications.Web/Domain/NotificationService.cs

And that's a wrap!  You should now be able to easily integrate this awesome feature into your own application.  On the next pane, we'll look at some other easy-to-implement features.

Where to Next?

That's it! We've just implemented a an opt-in process and an administrative interface to run an SMS and MMS marketing campaign all through C# and ASP.NET MVC. Now all you need is killer content to share with your users via text or MMS... and you're on your own for that.

Twilio and .NET go together very well - perhaps you'd enjoy these other tutorials?

IVR: Phone Tree

Build an automated phone tree with C# and Twilio's powerful features.

Automated Survey

Don't let valuable feedback slip away - conduct instant surveys with voice or SMS.

Did this help?

Thanks for checking this tutorial out!

We'd love to hear from you - Tweet to us @twilio with what you're building!

Paul Kamp
Hector Ortega
Andrew Baker
Agustin Camino

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
using System;

namespace MarketingNotifications.Web.Models
{
    public class Subscriber
    {
        public int Id { get; set; }
        public String PhoneNumber { get; set; }
        public bool Subscribed { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}
using System.Threading.Tasks;
using System.Web.Mvc;
using MarketingNotifications.Web.Domain;
using MarketingNotifications.Web.Models.Repository;
using Twilio.AspNet.Mvc;
using Twilio.TwiML;

namespace MarketingNotifications.Web.Controllers
{
    public class SubscribersController : TwilioController
    {
        private readonly ISubscribersRepository _repository;

        public SubscribersController() : this(new SubscribersRepository()) { }

        public SubscribersController(ISubscribersRepository repository)
        {
            _repository = repository;
        }

        // POST: Subscribers/Register
        [HttpPost]
        public async Task<TwiMLResult> Register(string from, string body)
        {
            var phoneNumber = from;
            var message = body;

            var messageCreator = new MessageCreator(_repository);
            var outputMessage = await messageCreator.Create(phoneNumber, message);

            var response = new MessagingResponse();
            response.Message(outputMessage);

            return TwiML(response);
        }
    }
}
using System;
using System.Threading.Tasks;
using MarketingNotifications.Web.Models;
using MarketingNotifications.Web.Models.Repository;

namespace MarketingNotifications.Web.Domain
{
    public class MessageCreator
    {
        private readonly ISubscribersRepository _repository;
        private const string Subscribe = "add";
        private const string Unsubscribe = "remove";

        public MessageCreator(ISubscribersRepository repository)
        {
            _repository = repository;
        }

        public async Task<string> Create(string phoneNumber, string message)
        {
            var subscriber = await _repository.FindByPhoneNumberAsync(phoneNumber);
            if (subscriber != null)
            {
                return await CreateOutputMessage(subscriber, message.ToLower());
            }

            subscriber = new Subscriber
            {
                PhoneNumber = phoneNumber,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            };
            await _repository.CreateAsync(subscriber);
            return "Thanks for contacting TWBC! Text 'add' if you would to receive updates via text message.";
        }

        private async Task<string> CreateOutputMessage(Subscriber subscriber, string message)
        {
            if (!IsValidCommand(message))
            {
                return "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";
            }

            var isSubscribed = message.StartsWith(Subscribe);
            subscriber.Subscribed = isSubscribed;
            subscriber.UpdatedAt = DateTime.Now;
            await _repository.UpdateAsync(subscriber);

            return isSubscribed
                ? "You are now subscribed for updates."
                : "You have unsubscribed from notifications. Test 'add' to start receiving updates again";
        }

        private static bool IsValidCommand(string command)
        {
            return command.StartsWith(Subscribe) || command.StartsWith(Unsubscribe);
        }
    }
}
using System;
using System.Threading.Tasks;
using MarketingNotifications.Web.Models;
using MarketingNotifications.Web.Models.Repository;

namespace MarketingNotifications.Web.Domain
{
    public class MessageCreator
    {
        private readonly ISubscribersRepository _repository;
        private const string Subscribe = "add";
        private const string Unsubscribe = "remove";

        public MessageCreator(ISubscribersRepository repository)
        {
            _repository = repository;
        }

        public async Task<string> Create(string phoneNumber, string message)
        {
            var subscriber = await _repository.FindByPhoneNumberAsync(phoneNumber);
            if (subscriber != null)
            {
                return await CreateOutputMessage(subscriber, message.ToLower());
            }

            subscriber = new Subscriber
            {
                PhoneNumber = phoneNumber,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            };
            await _repository.CreateAsync(subscriber);
            return "Thanks for contacting TWBC! Text 'add' if you would to receive updates via text message.";
        }

        private async Task<string> CreateOutputMessage(Subscriber subscriber, string message)
        {
            if (!IsValidCommand(message))
            {
                return "Sorry, we don't recognize that command. Available commands are: 'add' or 'remove'.";
            }

            var isSubscribed = message.StartsWith(Subscribe);
            subscriber.Subscribed = isSubscribed;
            subscriber.UpdatedAt = DateTime.Now;
            await _repository.UpdateAsync(subscriber);

            return isSubscribed
                ? "You are now subscribed for updates."
                : "You have unsubscribed from notifications. Test 'add' to start receiving updates again";
        }

        private static bool IsValidCommand(string command)
        {
            return command.StartsWith(Subscribe) || command.StartsWith(Unsubscribe);
        }
    }
}
using System.Threading.Tasks;
using System.Web.Mvc;
using System;
using MarketingNotifications.Web.Domain;
using MarketingNotifications.Web.Models.Repository;
using MarketingNotifications.Web.ViewModels;
using System.Collections.Generic;

namespace MarketingNotifications.Web.Controllers
{
    public class NotificationsController : Controller
    {
        private readonly ISubscribersRepository _repository;
        private readonly INotificationService _notificationService;

        public NotificationsController() : this(new SubscribersRepository(), new NotificationService())
        {
        }

        public NotificationsController(ISubscribersRepository repository, INotificationService notificationService)
        {
            _notificationService = notificationService;
            _repository = repository;
        }

        // GET: Notifications/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Notifications/Create
        [HttpPost]
        public async Task<ActionResult> Create(NotificationViewModel model)
        {
            if (ModelState.IsValid)
            {
                var mediaUrl = new List<Uri> { new Uri(model.ImageUrl) };
                var subscribers = await _repository.FindActiveSubscribersAsync();
                foreach (var subscriber in subscribers)
                {
                    await _notificationService.SendMessageAsync(
                        subscriber.PhoneNumber,
                        model.Message,
                        mediaUrl);
                }

                ModelState.Clear();
                ViewBag.FlashMessage = "Messages on their way!";
                return View();
            }

            return View(model);
        }
    }
}
using MarketingNotifications.Web.Domain.Twilio;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
using Twilio;

namespace MarketingNotifications.Web.Domain
{
    public interface INotificationService
    {
        Task<MessageResource> SendMessageAsync(string to, string body, List<Uri> mediaUrl);
    }

    public class NotificationService : INotificationService
    {
        public NotificationService()
        {
            if (Configuration.Credentials.AccountSID != null && Configuration.Credentials.AuthToken != null)
            {
                TwilioClient.Init(Configuration.Credentials.AccountSID, Configuration.Credentials.AuthToken);
            }
        }

        public async Task<MessageResource> SendMessageAsync(string to, string body, List<Uri> mediaUrl)
        {
            return await MessageResource.CreateAsync(
                from: new PhoneNumber(Configuration.PhoneNumbers.Twilio),
                to: new PhoneNumber(to),
                body: body,
                mediaUrl: mediaUrl);
        }
    }
}