One of the more abstract concepts you'll handle when building your business is what the workflow will look like.
At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.
To illustrate a very real-world example, today we'll build a C# and ASP.NET MVC webapp for finding and booking vacation properties — tentatively called Airtng.
Here's how it'll work:
We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:
AirTNG.Web/Domain/Reservations/Notifier.cs
_61using System;_61using System.Linq;_61using System.Text;_61using System.Threading.Tasks;_61using AirTNG.Web.Domain.Twilio;_61using AirTNG.Web.Models;_61using AirTNG.Web.Models.Repository;_61_61namespace AirTNG.Web.Domain.Reservations_61{_61 public interface INotifier_61 {_61 Task SendNotificationAsync(Reservation reservation);_61 }_61_61 public class Notifier : INotifier_61 {_61 private readonly ITwilioMessageSender _client;_61 private readonly IReservationsRepository _repository;_61_61 public Notifier() : this(_61 new TwilioMessageSender(),_61 new ReservationsRepository()) { }_61_61 public Notifier(ITwilioMessageSender client, IReservationsRepository repository)_61 {_61 _client = client;_61 _repository = repository;_61 }_61_61 public async Task SendNotificationAsync(Reservation reservation)_61 {_61 var pendingReservations = await _repository.FindPendingReservationsAsync();_61 if (pendingReservations.Count() < 2)_61 {_61 var notification = BuildNotification(reservation);_61 await _client.SendMessageAsync(notification.To, notification.From, notification.Messsage);_61 }_61 }_61_61 private static Notification BuildNotification(Reservation reservation)_61 {_61 var message = new StringBuilder();_61 message.AppendFormat("You have a new reservation request from {0} for {1}:{2}",_61 reservation.Name,_61 reservation.VacationProperty.Description,_61 Environment.NewLine);_61 message.AppendFormat("{0}{1}",_61 reservation.Message,_61 Environment.NewLine);_61 message.Append("Reply [accept] or [reject]");_61_61 return new Notification_61 {_61 From = PhoneNumbers.Twilio,_61 To = reservation.PhoneNumber,_61 Messsage = message.ToString()_61 };_61 }_61 }_61}
Ready to go? Boldly click the button right after this sentence.
For this use case to work we have to handle authentication. We will rely on ASP.NET Identity for this purpose.
Each User
will need to have a phone_number
that we will use later to send SMS notifications.
AirTNG.Web/Models/IdentityModels.cs
_41using System.Collections.Generic;_41using System.Data.Entity;_41using System.Security.Claims;_41using System.Threading.Tasks;_41using Microsoft.AspNet.Identity;_41using Microsoft.AspNet.Identity.EntityFramework;_41_41namespace AirTNG.Web.Models_41{_41 // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more._41 public class ApplicationUser : IdentityUser_41 {_41 public string Name { get; set; }_41_41 public virtual IList<VacationProperty> VacationProperties { get; set; }_41_41 public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)_41 {_41 // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType_41 var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);_41 // Add custom user claims here_41 return userIdentity;_41 }_41 }_41_41 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>_41 {_41 public ApplicationDbContext()_41 : base("AirTNGConnection", false)_41 {_41 }_41_41 public static ApplicationDbContext Create()_41 {_41 return new ApplicationDbContext();_41 }_41_41 public DbSet<VacationProperty> VacationProperties { get; set; }_41 public DbSet<Reservation> Reservations { get; set; }_41 }_41}
Next let's take a look at the Vacation Property model.
Our rental application will obviously require listing properties.
The VacationProperty
belongs to the User
who created it (we'll call this user the host from this point on) and contains only two properties: a Description
and an ImageUrl
.
A VacationProperty
can have many Reservations.
AirTNG.Web/Models/VacationProperty.cs
_16using System;_16using System.Collections.Generic;_16_16namespace AirTNG.Web.Models_16{_16 public class VacationProperty_16 {_16 public int Id { get; set; }_16 public string UserId { get; set; }_16 public virtual ApplicationUser User { get; set; }_16 public string Description { get; set; }_16 public string ImageUrl { get; set; }_16 public DateTime CreatedAt { get; set; }_16 public virtual IList<Reservation> Reservations { get; set; }_16 }_16}
Next, let's see what our Reservation model looks like.
The Reservation
model is at the center of the workflow for this application.
It is responsible for keeping track of:
VacationProperty
it is associated with.
User
who owns that vacation property (the host). Through this property the user will have access to the
host
phone number indirectly.
Status
of the reservation
AirTNG.Web/Models/Reservation.cs
_18using System;_18using System.ComponentModel.DataAnnotations.Schema;_18_18namespace AirTNG.Web.Models_18{_18 public class Reservation_18 {_18 public int Id { get; set; }_18 public string Name { get; set; }_18 public string PhoneNumber { get; set; }_18 public ReservationStatus Status { get; set; }_18 public string Message { get; set; }_18 public DateTime CreatedAt { get; set; }_18 public int VactionPropertyId { get; set; }_18 [ForeignKey("VactionPropertyId")]_18 public virtual VacationProperty VacationProperty { get; set; }_18 }_18}
Now that our models are ready, let's have a look at the controller that will create reservations.
The reservation creation form holds only a single field: the message that will be sent to the host when one of her properties is reserved. The rest of the information needed to create a reservation is taken from the VacationProperty itself.
A reservation is created with a default status ReservationStatus.Pending
. That way when the host replies with an accept
or reject
response the application knows which reservation to update.
AirTNG.Web/Controllers/ReservationsController.cs
_121using System;_121using System.Threading.Tasks;_121using System.Web.Mvc;_121using AirTNG.Web.Domain.Reservations;_121using AirTNG.Web.Models;_121using AirTNG.Web.Models.Repository;_121using AirTNG.Web.ViewModels;_121using Twilio.AspNet.Mvc;_121using Twilio.TwiML;_121_121namespace AirTNG.Web.Controllers_121{_121 [Authorize]_121 public class ReservationsController : TwilioController_121 {_121 private readonly IVacationPropertiesRepository _vacationPropertiesRepository;_121 private readonly IReservationsRepository _reservationsRepository;_121 private readonly IUsersRepository _usersRepository;_121 private readonly INotifier _notifier;_121_121 public ReservationsController() : this(_121 new VacationPropertiesRepository(),_121 new ReservationsRepository(),_121 new UsersRepository(),_121 new Notifier()) { }_121_121 public ReservationsController(_121 IVacationPropertiesRepository vacationPropertiesRepository,_121 IReservationsRepository reservationsRepository,_121 IUsersRepository usersRepository,_121 INotifier notifier)_121 {_121 _vacationPropertiesRepository = vacationPropertiesRepository;_121 _reservationsRepository = reservationsRepository;_121 _usersRepository = usersRepository;_121 _notifier = notifier;_121 }_121_121 // GET: Reservations/Create_121 public async Task<ActionResult> Create(int id)_121 {_121 var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);_121 var reservation = new ReservationViewModel_121 {_121 ImageUrl = vacationProperty.ImageUrl,_121 Description = vacationProperty.Description,_121 VacationPropertyId = vacationProperty.Id,_121 VacationPropertyDescription = vacationProperty.Description,_121 UserName = vacationProperty.User.Name,_121 UserPhoneNumber = vacationProperty.User.PhoneNumber,_121 };_121_121 return View(reservation);_121 }_121_121 // POST: Reservations/Create_121 [HttpPost]_121 public async Task<ActionResult> Create(ReservationViewModel model)_121 {_121 if (ModelState.IsValid)_121 {_121 var reservation = new Reservation_121 {_121 Message = model.Message,_121 PhoneNumber = model.UserPhoneNumber,_121 Name = model.UserName,_121 VactionPropertyId = model.VacationPropertyId,_121 Status = ReservationStatus.Pending,_121 CreatedAt = DateTime.Now_121 };_121_121 await _reservationsRepository.CreateAsync(reservation);_121 reservation.VacationProperty = new VacationProperty {Description = model.VacationPropertyDescription};_121 await _notifier.SendNotificationAsync(reservation);_121_121 return RedirectToAction("Index", "VacationProperties");_121 }_121_121 return View(model);_121 }_121_121 // POST Reservations/Handle_121 [HttpPost]_121 [AllowAnonymous]_121 public async Task<TwiMLResult> Handle(string from, string body)_121 {_121 string smsResponse;_121_121 try_121 {_121 var host = await _usersRepository.FindByPhoneNumberAsync(from);_121 var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);_121_121 var smsRequest = body;_121 reservation.Status =_121 smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||_121 smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)_121 ? ReservationStatus.Confirmed_121 : ReservationStatus.Rejected;_121_121 await _reservationsRepository.UpdateAsync(reservation);_121 smsResponse =_121 string.Format("You have successfully {0} the reservation", reservation.Status);_121 }_121 catch (Exception)_121 {_121 smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";_121 }_121 _121 return TwiML(Respond(smsResponse));_121 }_121_121 private static MessagingResponse Respond(string message)_121 {_121 var response = new MessagingResponse();_121 response.Message(message);_121_121 return response;_121 }_121 }_121}
Next, let's see how we will send SMS notifications to the vacation rental host.
When a reservation is created we want to notify the owner of the property that someone is interested.
This is where we use Twilio C# Helper Library to send a SMS message to the host, using our Twilio phone number. As you can see, sending SMS messages using Twilio takes just a few lines of code.
Next we just have to wait for the host to send an SMS response accepting or rejecting the reservation. Then we can notify the guest and host that the reservation information has been updated.
AirTNG.Web/Domain/Reservations/Notifier.cs
_61using System;_61using System.Linq;_61using System.Text;_61using System.Threading.Tasks;_61using AirTNG.Web.Domain.Twilio;_61using AirTNG.Web.Models;_61using AirTNG.Web.Models.Repository;_61_61namespace AirTNG.Web.Domain.Reservations_61{_61 public interface INotifier_61 {_61 Task SendNotificationAsync(Reservation reservation);_61 }_61_61 public class Notifier : INotifier_61 {_61 private readonly ITwilioMessageSender _client;_61 private readonly IReservationsRepository _repository;_61_61 public Notifier() : this(_61 new TwilioMessageSender(),_61 new ReservationsRepository()) { }_61_61 public Notifier(ITwilioMessageSender client, IReservationsRepository repository)_61 {_61 _client = client;_61 _repository = repository;_61 }_61_61 public async Task SendNotificationAsync(Reservation reservation)_61 {_61 var pendingReservations = await _repository.FindPendingReservationsAsync();_61 if (pendingReservations.Count() < 2)_61 {_61 var notification = BuildNotification(reservation);_61 await _client.SendMessageAsync(notification.To, notification.From, notification.Messsage);_61 }_61 }_61_61 private static Notification BuildNotification(Reservation reservation)_61 {_61 var message = new StringBuilder();_61 message.AppendFormat("You have a new reservation request from {0} for {1}:{2}",_61 reservation.Name,_61 reservation.VacationProperty.Description,_61 Environment.NewLine);_61 message.AppendFormat("{0}{1}",_61 reservation.Message,_61 Environment.NewLine);_61 message.Append("Reply [accept] or [reject]");_61_61 return new Notification_61 {_61 From = PhoneNumbers.Twilio,_61 To = reservation.PhoneNumber,_61 Messsage = message.ToString()_61 };_61 }_61 }_61}
Now's let's peek at how we're handling the host's responses.
The Reservations/Handle
controller controller handles our incoming Twilio request and does four things:
AirTNG.Web/Controllers/ReservationsController.cs
_121using System;_121using System.Threading.Tasks;_121using System.Web.Mvc;_121using AirTNG.Web.Domain.Reservations;_121using AirTNG.Web.Models;_121using AirTNG.Web.Models.Repository;_121using AirTNG.Web.ViewModels;_121using Twilio.AspNet.Mvc;_121using Twilio.TwiML;_121_121namespace AirTNG.Web.Controllers_121{_121 [Authorize]_121 public class ReservationsController : TwilioController_121 {_121 private readonly IVacationPropertiesRepository _vacationPropertiesRepository;_121 private readonly IReservationsRepository _reservationsRepository;_121 private readonly IUsersRepository _usersRepository;_121 private readonly INotifier _notifier;_121_121 public ReservationsController() : this(_121 new VacationPropertiesRepository(),_121 new ReservationsRepository(),_121 new UsersRepository(),_121 new Notifier()) { }_121_121 public ReservationsController(_121 IVacationPropertiesRepository vacationPropertiesRepository,_121 IReservationsRepository reservationsRepository,_121 IUsersRepository usersRepository,_121 INotifier notifier)_121 {_121 _vacationPropertiesRepository = vacationPropertiesRepository;_121 _reservationsRepository = reservationsRepository;_121 _usersRepository = usersRepository;_121 _notifier = notifier;_121 }_121_121 // GET: Reservations/Create_121 public async Task<ActionResult> Create(int id)_121 {_121 var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);_121 var reservation = new ReservationViewModel_121 {_121 ImageUrl = vacationProperty.ImageUrl,_121 Description = vacationProperty.Description,_121 VacationPropertyId = vacationProperty.Id,_121 VacationPropertyDescription = vacationProperty.Description,_121 UserName = vacationProperty.User.Name,_121 UserPhoneNumber = vacationProperty.User.PhoneNumber,_121 };_121_121 return View(reservation);_121 }_121_121 // POST: Reservations/Create_121 [HttpPost]_121 public async Task<ActionResult> Create(ReservationViewModel model)_121 {_121 if (ModelState.IsValid)_121 {_121 var reservation = new Reservation_121 {_121 Message = model.Message,_121 PhoneNumber = model.UserPhoneNumber,_121 Name = model.UserName,_121 VactionPropertyId = model.VacationPropertyId,_121 Status = ReservationStatus.Pending,_121 CreatedAt = DateTime.Now_121 };_121_121 await _reservationsRepository.CreateAsync(reservation);_121 reservation.VacationProperty = new VacationProperty {Description = model.VacationPropertyDescription};_121 await _notifier.SendNotificationAsync(reservation);_121_121 return RedirectToAction("Index", "VacationProperties");_121 }_121_121 return View(model);_121 }_121_121 // POST Reservations/Handle_121 [HttpPost]_121 [AllowAnonymous]_121 public async Task<TwiMLResult> Handle(string from, string body)_121 {_121 string smsResponse;_121_121 try_121 {_121 var host = await _usersRepository.FindByPhoneNumberAsync(from);_121 var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);_121_121 var smsRequest = body;_121 reservation.Status =_121 smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||_121 smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)_121 ? ReservationStatus.Confirmed_121 : ReservationStatus.Rejected;_121_121 await _reservationsRepository.UpdateAsync(reservation);_121 smsResponse =_121 string.Format("You have successfully {0} the reservation", reservation.Status);_121 }_121 catch (Exception)_121 {_121 smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";_121 }_121 _121 return TwiML(Respond(smsResponse));_121 }_121_121 private static MessagingResponse Respond(string message)_121 {_121 var response = new MessagingResponse();_121 response.Message(message);_121_121 return response;_121 }_121 }_121}
Let's have closer look at how Twilio webhooks are configured to enable incoming requests to our application.
In the Twilio console, you must setup the sms web hook to call your application's end point in the route Reservations/Handle
.
One way to expose your development machine to the outside world is using ngrok. The url for the sms web hook on your number would look like this:
_10http://<subdomain>.ngrok.io/Reservations/Handle
An incoming request from Twilio comes with some helpful parameters, such as a from
phone number and the message body
.
We'll use the from
parameter to look for the host and check if he/she has any pending reservations. If he/she does, we'll use the message body to check for 'accept' and 'reject'.
In the last step, we'll use Twilio's TwiML as a response to Twilio to send an SMS message to the guest.
AirTNG.Web/Controllers/ReservationsController.cs
_121using System;_121using System.Threading.Tasks;_121using System.Web.Mvc;_121using AirTNG.Web.Domain.Reservations;_121using AirTNG.Web.Models;_121using AirTNG.Web.Models.Repository;_121using AirTNG.Web.ViewModels;_121using Twilio.AspNet.Mvc;_121using Twilio.TwiML;_121_121namespace AirTNG.Web.Controllers_121{_121 [Authorize]_121 public class ReservationsController : TwilioController_121 {_121 private readonly IVacationPropertiesRepository _vacationPropertiesRepository;_121 private readonly IReservationsRepository _reservationsRepository;_121 private readonly IUsersRepository _usersRepository;_121 private readonly INotifier _notifier;_121_121 public ReservationsController() : this(_121 new VacationPropertiesRepository(),_121 new ReservationsRepository(),_121 new UsersRepository(),_121 new Notifier()) { }_121_121 public ReservationsController(_121 IVacationPropertiesRepository vacationPropertiesRepository,_121 IReservationsRepository reservationsRepository,_121 IUsersRepository usersRepository,_121 INotifier notifier)_121 {_121 _vacationPropertiesRepository = vacationPropertiesRepository;_121 _reservationsRepository = reservationsRepository;_121 _usersRepository = usersRepository;_121 _notifier = notifier;_121 }_121_121 // GET: Reservations/Create_121 public async Task<ActionResult> Create(int id)_121 {_121 var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);_121 var reservation = new ReservationViewModel_121 {_121 ImageUrl = vacationProperty.ImageUrl,_121 Description = vacationProperty.Description,_121 VacationPropertyId = vacationProperty.Id,_121 VacationPropertyDescription = vacationProperty.Description,_121 UserName = vacationProperty.User.Name,_121 UserPhoneNumber = vacationProperty.User.PhoneNumber,_121 };_121_121 return View(reservation);_121 }_121_121 // POST: Reservations/Create_121 [HttpPost]_121 public async Task<ActionResult> Create(ReservationViewModel model)_121 {_121 if (ModelState.IsValid)_121 {_121 var reservation = new Reservation_121 {_121 Message = model.Message,_121 PhoneNumber = model.UserPhoneNumber,_121 Name = model.UserName,_121 VactionPropertyId = model.VacationPropertyId,_121 Status = ReservationStatus.Pending,_121 CreatedAt = DateTime.Now_121 };_121_121 await _reservationsRepository.CreateAsync(reservation);_121 reservation.VacationProperty = new VacationProperty {Description = model.VacationPropertyDescription};_121 await _notifier.SendNotificationAsync(reservation);_121_121 return RedirectToAction("Index", "VacationProperties");_121 }_121_121 return View(model);_121 }_121_121 // POST Reservations/Handle_121 [HttpPost]_121 [AllowAnonymous]_121 public async Task<TwiMLResult> Handle(string from, string body)_121 {_121 string smsResponse;_121_121 try_121 {_121 var host = await _usersRepository.FindByPhoneNumberAsync(from);_121 var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);_121_121 var smsRequest = body;_121 reservation.Status =_121 smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||_121 smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)_121 ? ReservationStatus.Confirmed_121 : ReservationStatus.Rejected;_121_121 await _reservationsRepository.UpdateAsync(reservation);_121 smsResponse =_121 string.Format("You have successfully {0} the reservation", reservation.Status);_121 }_121 catch (Exception)_121 {_121 smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";_121 }_121 _121 return TwiML(Respond(smsResponse));_121 }_121_121 private static MessagingResponse Respond(string message)_121 {_121 var response = new MessagingResponse();_121 response.Message(message);_121_121 return response;_121 }_121 }_121}
Now that we know how to expose a webhook to handle Twilio requests, let's see how we generate the TwiML needed.
After updating the reservation status, we must notify the host that he/she has successfully confirmed or rejected the reservation. We also have to return a friendly error message if there are no pending reservations.
If the reservation is confirmed or rejected we send an additional SMS to the guest to deliver the news.
We use the verb Message from TwiML to instruct Twilio's server that it should send the SMS messages.
AirTNG.Web/Controllers/ReservationsController.cs
_121using System;_121using System.Threading.Tasks;_121using System.Web.Mvc;_121using AirTNG.Web.Domain.Reservations;_121using AirTNG.Web.Models;_121using AirTNG.Web.Models.Repository;_121using AirTNG.Web.ViewModels;_121using Twilio.AspNet.Mvc;_121using Twilio.TwiML;_121_121namespace AirTNG.Web.Controllers_121{_121 [Authorize]_121 public class ReservationsController : TwilioController_121 {_121 private readonly IVacationPropertiesRepository _vacationPropertiesRepository;_121 private readonly IReservationsRepository _reservationsRepository;_121 private readonly IUsersRepository _usersRepository;_121 private readonly INotifier _notifier;_121_121 public ReservationsController() : this(_121 new VacationPropertiesRepository(),_121 new ReservationsRepository(),_121 new UsersRepository(),_121 new Notifier()) { }_121_121 public ReservationsController(_121 IVacationPropertiesRepository vacationPropertiesRepository,_121 IReservationsRepository reservationsRepository,_121 IUsersRepository usersRepository,_121 INotifier notifier)_121 {_121 _vacationPropertiesRepository = vacationPropertiesRepository;_121 _reservationsRepository = reservationsRepository;_121 _usersRepository = usersRepository;_121 _notifier = notifier;_121 }_121_121 // GET: Reservations/Create_121 public async Task<ActionResult> Create(int id)_121 {_121 var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);_121 var reservation = new ReservationViewModel_121 {_121 ImageUrl = vacationProperty.ImageUrl,_121 Description = vacationProperty.Description,_121 VacationPropertyId = vacationProperty.Id,_121 VacationPropertyDescription = vacationProperty.Description,_121 UserName = vacationProperty.User.Name,_121 UserPhoneNumber = vacationProperty.User.PhoneNumber,_121 };_121_121 return View(reservation);_121 }_121_121 // POST: Reservations/Create_121 [HttpPost]_121 public async Task<ActionResult> Create(ReservationViewModel model)_121 {_121 if (ModelState.IsValid)_121 {_121 var reservation = new Reservation_121 {_121 Message = model.Message,_121 PhoneNumber = model.UserPhoneNumber,_121 Name = model.UserName,_121 VactionPropertyId = model.VacationPropertyId,_121 Status = ReservationStatus.Pending,_121 CreatedAt = DateTime.Now_121 };_121_121 await _reservationsRepository.CreateAsync(reservation);_121 reservation.VacationProperty = new VacationProperty {Description = model.VacationPropertyDescription};_121 await _notifier.SendNotificationAsync(reservation);_121_121 return RedirectToAction("Index", "VacationProperties");_121 }_121_121 return View(model);_121 }_121_121 // POST Reservations/Handle_121 [HttpPost]_121 [AllowAnonymous]_121 public async Task<TwiMLResult> Handle(string from, string body)_121 {_121 string smsResponse;_121_121 try_121 {_121 var host = await _usersRepository.FindByPhoneNumberAsync(from);_121 var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);_121_121 var smsRequest = body;_121 reservation.Status =_121 smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||_121 smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)_121 ? ReservationStatus.Confirmed_121 : ReservationStatus.Rejected;_121_121 await _reservationsRepository.UpdateAsync(reservation);_121 smsResponse =_121 string.Format("You have successfully {0} the reservation", reservation.Status);_121 }_121 catch (Exception)_121 {_121 smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";_121 }_121 _121 return TwiML(Respond(smsResponse));_121 }_121_121 private static MessagingResponse Respond(string message)_121 {_121 var response = new MessagingResponse();_121 response.Message(message);_121_121 return response;_121 }_121 }_121}
Congratulations! You've just learned how to automate your workflow with Twilio SMS.
Next, lets see what else we can do with the Twilio C# SDK.
If you're a .NET developer working with Twilio you know we've got a lot of great content here on the Docs site. Here are just a couple ideas for your next tutorial:
Easily route callers to the right people and information with an IVR (interactive voice response) system.
Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.
Thanks for checking out this tutorial! Tweet to us @twilio with what you're building!