dotMobimobiThinkingmobiForgemobiReadyDeviceAtlasgoMobi
background image

Receiving SMS Messages via Shortcodes & Keywords

Message in a bottle, on sand
Posted by mokil - 01 Nov 2010
Twitter share icon Facebook share icon Google Plus share icon

Back around 1998, more and more billboards started to display some odd messages, along the lines of "Visit us on the web at www.companyname.com". This was a major shift in advertising and gave marketers another avenue to explore. It was, of course, the beginning of the Internet's move from academia to the mainstream, and the proliferation of Web sites seemed to happen almost overnight.

Here in 2010, we're now back in 1998 territory. This time it's the mobile Web that has everybody excited. The thought of potential customers being able to access services from anywhere - and not just in front of a PC - has marketers drooling. SMS plays an important role. On billboards ads, one is very likely to see something like "text companyname to 81707 for more information". This is a shortcode/keyword combination. By texting the specified keyword (companyname in the example above) to the specified shortcode (81707 in the example), a mobile system can perform some type of action, defined by the company offering the keyword/shortcode. This may be as simple as sending a "Thanks for contacting us, we'll be in touch" message back to the user, or beginning some kind of workflow process. Or, as in our example above, the user may receive a text message back containing a URL to a mobile Web site.

But just how do developers go about setting up keyword/shortcodes? And how do they process incoming messages to those keyword/shortcodes? Let's take a look at how to do this. We'll build upon the system we've built in previous articles that allows us to send SMS messages and to process delivery receipts. We'll build on the basic message processing system we built in the delivery receipts article. Remember we're using SQL Server and Visual Studio (C#) to build our system.

Obtaining a Keyword/Shortcode

In the Sending SMS Messages article, we talked about ways of sending SMS messages, and demonstrated how to do this using an aggregator called iTagg. We also need to use an aggregator to set up keyword/shortcodes. You have two options here:

  • Purchase a dedicated shortcode This is the expensive option. Expect to pay several thousand pounds for a dedicated shortcode. ITV, for example, has the dedicated shortcode 63330. This means ITV can set up any keyword they like on this shortcode; no other company has access to it. It is purely for ITV's exclusive use.
  • Purchase a keyword on a shared shortcode This is what most companies use. An aggregator provides a shared shortcode (so called because several companies use keywords on the same shortcode), and the aggregator's clients purchase keywords to use on it. iTagg own the shared shortcode 60300. So client 1 may purchase the keyword mobile. If client 2 comes along and also wants the keyword mobile, they cannot have it as it has already been assigned. Client 2 would have to select an alternative keyword.

The costs of purchasing a keyword/shortcode vary from aggregator to aggregator. You can expect to pay a monthly or annual fee for the use of the platform and a monthly rental fee for the keyword/shortcode. Some aggregators allow you to pay a fee and have a certain number of keywords - contact the aggregators to determine what price point works best for you.

One last important point - make sure you choose your aggregator carefully. Once you've chosen an aggregator any advertising of your keyword/shortcode will be showing the shared shortcode used by the aggregator - effectively locking you in. I strongly recommend you run some tests with the aggregators you're interested in before making a final decision.

Configuring a Keyword/Shortcode

Once you have a keyword/shortcode, how do you set it up? It's pretty easy. You give the aggregator a Web address, and the aggregator forwards any inbound messages they receive on your keyword/shortcode to the specified Web address. Depending upon the aggregator, there are two ways you can provide the Web address:

  • Provide the Web address to the aggregator's support team, who will then configure it for you (this is the only method of configuration for certain aggregators)
  • Log on to the aggregator's control panel and change the URL yourself

The second option is much preferred, as it gives you the ability to quickly change the URL yourself. The first option puts a reliance on the aggregator's support team.

Here's the iTagg control panel, listing available keywords:



Figure 1 iTagg Control Panel

Clicking on the keyword allows you to configure details for the keyword/shortcode. There are a number of options - you can choose to forward messages on to a URL, and/or to an e-mail address. Clicking on the E-mail/URL Forwarder option displays a screen allowing you to configure the relevant pieces of information:



Figure 2 Configuring Keyword URL

Once you've configured these pieces of information, you're ready to receive inbound SMS messages - as long as you have a system present to process them, of course. Let's build that system after we've looked at how iTagg send inbound messages to us.

iTagg Inbound Messages

iTagg send all inbound SMS message requests over HTTP POST. Here are the parameters iTagg send to our Web listener page:

  • Source This is the mobile number the message was sent from. So if you sent the message from your mobile number, the value for this field would be your mobile number
  • Dest The shortcode the message was delivered to, e.g. 60300
  • Dtime The delivery time of the message from the mobile network - in the format YYYYMMDDHHMM.
  • Message The message body received
  • Network The network the source mobile number uses. This is passed as a code:
    • 10 - O2
    • 15 - Vodafone
    • 20 - 3
    • 30 - T-Mobile/Virgin
    • 33 - Orange
    • 99 - Other

    Different aggregators use different codes

  • Message_id The unique identifier assigned to the message by the aggregator
  • Message_after_match Message_after_match contains the body of the message sent after the keyword has been matched
  • Matched_keyword This is the keyword that was matched. This can be used to quickly match up keywords in multi-keyword systems
  • Matched_subkeyword This is usually blank. iTagg allows keywords to be set up as sub-keywords (e.g. "example" could be extended with sub-keywords 1, 2 - so the user would send "example1" and "example2"

Note that these parameters are specific to iTagg - they will differ for each individual aggregator. For example, Tanla pass the following parameters:

  • ServiceID (Tanla service number)
  • MOID (aggregator ID)
  • Operator ID (denotes which network the sender uses)
  • Date (date/time the message was received)
  • From (the mobile number the message was sent from)
  • To (the shortcode the message was sent to)
  • Message (the message body)

It's important to look at the API documents for the aggregator you're planning to use (iTagg's API documents can be found at their Web site). Using iTagg, let's enhance our existing system to process inbound messages.

Building the Database

Here's the SQL Server database script from the delivery receipts article, with one minor alteration:

USE master
 
-- Delete the database if it already exists
IF EXISTS(SELECT [name] FROM sys.DATABASES
	    WHERE [name] = 'MobiforgeMessageDemo')
 BEGIN
	DROP DATABASE MobiforgeMessageDemo
 END
 
GO
 
-- Create the database
CREATE DATABASE MobiforgeMessageDemo
 
GO
 
USE MobiforgeMessageDemo
 
-- Add the Messages table
CREATE TABLE [Messages]
(
 AggregatorMessageID		VARCHAR(40),
 InboundMessage			BIT,
 Originator				VARCHAR(12),
 Recipient				VARCHAR(12),
 MessageDate			DATETIME	DEFAULT GETDATE(),
 MessageBody			VARCHAR(160),
 MessageDelivered			BIT		DEFAULT 0
)
 
GO
 
-- Stored procedure to insert a message
-- Amended to accept an optional Message Date
CREATE PROCEDURE usp_InsertMessage
@AggregatorMessageID		VARCHAR(40),
@InboundMessage			BIT,
@Originator				VARCHAR(12),
@Recipient				VARCHAR(12),
@MessageBody			VARCHAR(160),
@MessageDate			DATETIME = NULL
AS
BEGIN
 
SET NOCOUNT ON
 
IF (@MessageDate IS NULL)
 BEGIN
	INSERT INTO [Messages]
	(AggregatorMessageID, InboundMessage, Originator, 
	 Recipient, MessageBody)
	VALUES 
	(@AggregatorMessageID, @InboundMessage, @Originator,
	 @Recipient, @MessageBody)
 END
ELSE
 BEGIN
	INSERT INTO [Messages]
	(AggregatorMessageID, InboundMessage, Originator, 
	 Recipient, MessageBody, MessageDate)
	VALUES 
	(@AggregatorMessageID, @InboundMessage, @Originator,
	 @Recipient, @MessageBody, @MessageDate)
 END
 
SET NOCOUNT OFF
 
END
 
GO
 
-- Stored procedure to update a message's delivery status
CREATE PROCEDURE usp_MarkMessageAsDelivered
@AggregatorMessageID		VARCHAR(40)
AS
BEGIN
 
SET NOCOUNT ON
 
-- iTagg pass message IDs with a -3 when sending messages,
-- but do not pass the -3 in delivery receipts. So we check
-- if we can find the message first, and if we can't we append
-- a -3 to the end of the ID
IF NOT EXISTS (SELECT AggregatorMessageID 
		   FROM [Messages]
		   WHERE AggregatorMessageID = @AggregatorMessageID)
BEGIN
 
	SET @AggregatorMessageID += '-3'
 
END	   
 
UPDATE [Messages]
	SET MessageDelivered = 1
WHERE AggregatorMessageID = @AggregatorMessageID
 
END

We've made one change to this script - in the usp_InsertMessage stored procedure. We've added an optional @MessageDate parameter. If this is passed we insert the date into the table; otherwise we use the current date/time.

Amending the Listener Web Page

When we added the ability to process delivery receipts to our simple messaging system, we introduced a listener Web page. We configured this in the iTagg control panel. We'll now amend the listener to detect whether a delivery receipt or inbound message has been received, and we'll process it accordingly.

Here's the original code:

protected void Page_Load(object sender, EventArgs e)
{
 try
 {
  // Check if some POST data was passed
  if (Request.InputStream != null)
  {
   StreamReader sr = new StreamReader(Request.InputStream);
 
   // Convert the POST stream to a string
   // We remove the xml= tag at the front, otherwise the XML will not parse
   string inputStream = sr.ReadToEnd().Replace("xml=", string.Empty);
 
   // Create a delivery receipt
   iTaggDeliveryRecipt dr = new iTaggDeliveryRecipt(inputStream);
 
   // Write out the delivery receipt details to the trace
   Trace.Write("iTagg DR data: " + inputStream);
 
   Trace.Write("DR submission ref: " + dr.SubmissionRef);
   Trace.Write("DR status: " + dr.Status);
 
   // Only process if the status is "delivered"
   if (dr.Status == "Delivered")
   {
    // Try and update the database
    SqlConnection conn = new SqlConnection(
     "Data Source=DEVSQL;Initial Catalog=MobiforgeMessageDemo;Integrated 
Security=true");
 
    SqlCommand cmd = new SqlCommand("usp_MarkMessageAsDelivered", conn);
                    cmd.CommandType = CommandType.StoredProcedure;
 
    // Make sure we set the aggregator message ID
    cmd.Parameters.Add(new SqlParameter("@AggregatorMessageID", 
dr.SubmissionRef));
 
    conn.Open();
    cmd.ExecuteNonQuery();
    conn.Close();
 
    // Show we updated the database in the trace
    Trace.Write("Successfully updated database.");
   }
 
   // A different status has been received, so we output a message to show    
   // this
   else        
   {
   Response.Write("Message was not delivered. Status: " + dr.Status + 
"<BR>");
   }
 
   // Write out a confirmation message at the end
   Response.Write("OK");
  }
 }
 catch (Exception ex)        // Output the error details
 {
  Response.Write("Error occurred whilst processing: " + ex.Message);
  Trace.Write("Error occurred whilst processing: " + ex.Message);
 }
}

The changes we'll make are:

  • Add a class to represent an inbound SMS message
  • Add a method to inspect the POST data and determine whether a delivery receipt or inbound SMS message has been received
  • Move the delivery receipt processing into a separate method
  • Add a method to process an inbound SMS message
  • Change the Page_Load event to call the appropriate methods and process inbound requests

The Inbound SMS Message Class

The inbound SMS message class does nothing more than parse the inbound data from iTagg and convert it to properties on the class. This makes it easier for us to insert the message into the database.

using System;
using System.Collections.Generic;
using System.Web;
 
/// <summary>
/// Summary description for InboundSmsMessage
/// </summary>
public class InboundSmsMessage
{
    private string _aggregatorMessageID = string.Empty;     //The aggregator message ID
    private string _originator = string.Empty;              // The message originator
    private string _recipient = string.Empty;               // The message recipient
    private DateTime _messageDate = DateTime.MinValue;      // Date/time the message was received
    private string _messageBody = string.Empty;             // The message body
 
    // Public accessors
    public string AggregatorMessageID
    {
        get { return _aggregatorMessageID; }
        set { _aggregatorMessageID = value; }
    }
 
    public string Originator
    {
        get { return _originator; }
        set { _originator = value; }
    }
 
    public string Recipient
    {
        get { return _recipient; }
        set { _recipient = value; }
    }
 
    public DateTime MessageDate
    {
        get { return _messageDate; }
        set { _messageDate = value; }
    }
 
    public string MessageBody
    {
        get { return _messageBody; }
        set { _messageBody = value; }
    }
 
    /// <summary>
    /// Accepts Input Stream parameter as a string and populates class properties
    /// </summary>
    /// <param name="InputStream"></param>
	public InboundSmsMessage(string InputStream)
	{
        // Obtain the parameter values by splitting on the ampersand
        string[] parameters = InputStream.Split(new char[] { '&' });
 
        // And now we can loop around the parameters and process each value
        // Note that we don't process every value iTagg send to us as we don't need them all
        for (int i = 0; i < parameters.Length; i++)
        {
            // Separate parameter name and value
            string[] parameterValues = parameters[i].Split(new char[] { '=' });
 
            // Should have two values
            if (parameterValues.Length == 2)
            {
                // Assign the value to the appropriate class property
                switch (parameterValues[0])
                {
                    case "source":
                        _originator = parameterValues[1];
                        break;
 
                    case "dest":
                        _recipient = parameterValues[1];
                        break;
 
                    case "dtime":
                        // Length should be 12
                        // 4 digits for YYYY
                        // 2 digits for MM
                        // 2 digits for DD
                        // 2 digits for HH
                        // 2 digits for MM
                        if (parameterValues[1].Length == 12)
                        {
                            int year = Convert.ToInt32(parameterValues[1].Substring(0, 4));
                            int month = Convert.ToInt32(parameterValues[1].Substring(4, 2));
                            int day = Convert.ToInt32(parameterValues[1].Substring(6, 2));
                            int hour = Convert.ToInt32(parameterValues[1].Substring(8, 2));
                            int minute = Convert.ToInt32(parameterValues[1].Substring(10, 2));
 
                            DateTime dateToProcess = new DateTime(year, month, day, hour, minute, 0);
                            _messageDate = dateToProcess;
                        }
                        break;
 
                    case "message":
                        _messageBody = parameterValues[1];
                        break;
 
                    case "message_id":
                        _aggregatorMessageID = parameterValues[1];
                        break;
                }
            }
        }
	}
}

There's not much to say about this code; it's self-explanatory. In the real world we'd add try/catch blocks, constants for literal values, and better processing around dates.

POST Data Processing Method

The method to check if the POST data returns a Boolean - true if we're processing a delivery receipt, false if we're processing an inbound SMS message.

    /// <summary>
    /// Determines whether we should process a delivery receipt or an inbound SMS message
    /// </summary>
    /// <param name="inputStream">The data to check</param>
    /// <returns></returns>
    private bool deliveryReceiptReceived(string inputStream)
    {
        // Assume we're processing an inbound SMS message
        bool deliveryReceiptReceived = false;
 
        // If the input stream contains a delivery receipt XML element, 
        // return delivery receipt
        if (inputStream.Contains("itagg_delivery_receipt"))
        {
            deliveryReceiptReceived = true;
        }
 
        return deliveryReceiptReceived;
    }

We perform a simple check for a delivery receipt value, and if we find that value we know we're processing a delivery receipt.

Move Delivery Receipt Processing Into a Separate Method

We now move the coding to process a delivery receipt from the Page_Load event into a separate method called, originally enough, processDeliveryReceipt.

/// <summary>
    /// Process the inbound delivery receipt and add it to the database
    /// </summary>
    /// <param name="inputStream"></param>
    private void processDeliveryReceipt(string inputStream)
    {
        // Create a delivery receipt
        iTaggDeliveryRecipt dr =
            new iTaggDeliveryRecipt(inputStream);
 
        // Write out the delivery receipt details to the trace
        Trace.Write("iTagg DR data: " + inputStream);
 
        Trace.Write("DR submission ref: " + dr.SubmissionRef);
        Trace.Write("DR status: " + dr.Status);
 
        // Only process if the status is "delivered"
        if (dr.Status == "Delivered")
        {
            // Try and update the database
            SqlConnection conn = new SqlConnection(
                "Data Source=DEVSQL;Initial Catalog=MobiforgeMessageDemo;Integrated Security=true");
 
            SqlCommand cmd = new SqlCommand("usp_MarkMessageAsDelivered", conn);
            cmd.CommandType = CommandType.StoredProcedure;
 
            // Make sure we set the aggregator message ID
            cmd.Parameters.Add(new SqlParameter("@AggregatorMessageID", dr.SubmissionRef));
 
            conn.Open();
            cmd.ExecuteNonQuery();
            conn.Close();
 
            // Show we updated the database in the trace
            Trace.Write("Successfully updated database.");
        }
        else        // A different status has been received, so we output a message to show this
        {
            Response.Write("Message was not delivered. Status: " + dr.Status + "<BR>");
        }
 
        // Write out a confirmation message at the end
        Response.Write("OK");
    }

This method creates a delivery receipt object and inserts it into the database.

Add a Method to Process an Inbound SMS Message

Our penultimate step sees us write a similar method to that above, but to process an inbound SMS message.

/// <summary>
    /// Processes an inbound SMS message and adds it to the database
    /// </summary>
    /// <param name="inputStream"></param>
    private void processInboundSmsMessage(string inputStream)
    {
        // Create an inbound SMS message
        InboundSmsMessage message = new InboundSmsMessage(inputStream);
 
        // Write out the SMS details to the trace
        Trace.Write("iTagg SMS data: " + inputStream);
 
        // Only process if we have valid data
        if (message.AggregatorMessageID != string.Empty)
        {
            SqlConnection conn = new SqlConnection(
                "Data Source=DEVSQL;Initial Catalog=MobiforgeMessageDemo;Integrated Security=true");
 
            SqlCommand cmd = new SqlCommand("usp_InsertMessage", conn);
            cmd.CommandType = CommandType.StoredProcedure;
 
            // Set the parameters
            cmd.Parameters.Add(new SqlParameter("@AggregatorMessageID", message.AggregatorMessageID));
            cmd.Parameters.Add(new SqlParameter("@InboundMessage", true));
            cmd.Parameters.Add(new SqlParameter("@Originator", message.Originator));
            cmd.Parameters.Add(new SqlParameter("@Recipient", message.Recipient));
            cmd.Parameters.Add(new SqlParameter("@MessageBody", message.MessageBody));
            cmd.Parameters.Add(new SqlParameter("@MessageDate", message.MessageDate));
 
            conn.Open();
            cmd.ExecuteNonQuery();
            conn.Close();
 
            // Show we updated the database in the trace
            Trace.Write("Successfully updated database.");
        }
 
        // Write out a confirmation message at the end
        Response.Write("OK");
    }

The InboundSmsMessage class takes care of parsing the input stream; once we have these values we write it to the database.

If we were following strict OOP guidelines we'd have put the database code in a method on the InboundSmsMessage class called Update(). We'd also have added a similar method to the Delivery Receipt class.

The Page_Load Event

All that's left to do now is rewrite the Page_Load event to call the methods we've written.

protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            // Check if some POST data was passed
            if (Request.InputStream != null)
            {
                StreamReader sr = new StreamReader(Request.InputStream);
 
                // Convert the POST stream to a string
                // We remove the xml= tag at the front, otherwise the XML will not parse
                // NOTE - "xml" is only passed if we receive a delivery receipt
                string inputStream = sr.ReadToEnd().Replace("xml=", string.Empty);
 
                if (deliveryReceiptReceived(inputStream))
                {
                    processDeliveryReceipt(inputStream);
                }
                else
                {
                    processInboundSmsMessage(inputStream);
                }
            }
        }
        catch (Exception ex)        // Output the error details
        {
            Response.Write("Error occurred whilst processing: " +
                            ex.Message);
            Trace.Write("Error occurred whilst processing: " +
                            ex.Message);
        }        
    }

This method has been greatly simplified. The first part remains the same - we convert the input stream to a string. After that, we check if we've received a delivery receipt and process accordingly.

We're now ready to try it out!

Putting It All Together

Publish the listener to IIS and make sure you can access it through your Web browser. Once you're happy visit iTagg (or your selected aggregator) and set up the URL as the inbound message path by either configuring it in the aggregator control panel, or contacting aggregator support.

Then try sending in a message. A typical message may look like:



Figure 3 About to send a message

Send this message, and after a few seconds it should appear in the Messages database table.



Figure 4 The updated Messages table

Summary

It's not difficult to process inbound SMS messages - in fact, it's quite similar to sending an inbound message. Once you have a simple database configured, it's pretty easy to process the messages and insert them into the database. The main thing to consider is your choice of aggregator - as you'll be tied in to their shortcode it's vital you make the correct choice.

Our example system has some limitations. Firstly, we don't actually do anything with the inbound message. A further article will show how to do this. Secondly, what we've developed can only support one keyword/shortcode. Say we have keyword1 on 60300 and keyword2 on 60300, and we wanted different messages to be sent back depending upon which keyword/shortcode was used. We'd need to add a Keywords table to the database and link this to the Messages table. We could then amend our stored procedures accordingly and change our class library and listener Web page over to process messages from different keywords. We'll also look at how to do this in a future article.


Posted by mokil - 01 Nov 2010

mokil's picture

Mike McQuillan began developing software professionally after graduating from university with a 1st class Honours degree in Computer Studies in 1999. Since then he has worked for small companies (five employees) and large PLCs, such as Experian. He has developed just about every type of software there is – Windows client/server, Web systems, services, Unix systems, and now mobile systems. He is the CTO at Mdev Technology, a mobile software development company, and he also runs his own consultancy firm, McQTech.
He's been working on mobile since 2006 and has built up a comprehensive knowledge of what works and what doesn’t, especially on the mobile Web. He mainly concentrates on using .NET and SQL Server but has developed systems for Apple's mobile devices, Android, and Windows Phone 7 too.

Posted by mamunms 3 years ago

do you have any PHP example? as I am a PHP MySql developer plz someone share in PHP