Send SMS and MMS Messages with A LinkIt ONE using Programmable Wireless, AWS IoT and Lambda

March 28, 2018
Written by
Paul Kamp
Reviewed by
Kat King


Today we're going to make a contribution to the Internet of Things by sending SMS messages with a Seeed Studio LinkIt ONE.  We'll use Twilio's Programmable Wireless for connectivity and Amazon Web Services' IoT and Lambda as our backend.  To make it equally at home at... home, we'll also demonstrate how to accomplish the same task using WiFi.

Sound like a deal?  Let's shake on it!

Sign Into (Or Sign Up For) a Twilio and AWS Account

Sign in to your Twilio account. If you don't have an account yet, create one by signing up for a free Twilio trial.  You'll need values from the console to finish this guide, so please keep a tab handy.

You should also create an account with Amazon Web Services, which you'll need for the AWS IoT and Lambda steps.

Find or Purchase an SMS (and MMS) Capable Number

For this project, you need a number that is both SMS and MMS capable.

  • Enter the Twilio Console.  
  • Select the hash tag/Phone Numbers ('#') section on the left side
  • Navigate to your current active numbers.  

In 'Capabilities', you'll see the functions available with your currently held Twilio phone numbers.

Checking a Twilio Phone Number for SMS Capability

If you don't yet have a number with SMS and MMS, you'll need to buy one.  Navigate to the 'Buy a Number' link and click the SMS and MMS checkbox (countries without MMS support can comment out optional_image_path in the LinkIt code.)

Buy a SMS-Capable Twilio Phone Number

Purchase and Activate a Programmable Wireless SIM Card (Optional)

We've provided code to connect to the internet over WiFi or 2G/GPRS.  If you are using - or testing with - WiFi, you can set WIFI_USED to true.  (See more detail in the connection section below).

  • Navigate to the Programmable Wireless section of the console
  • Click the 'Getting Started' section on the left side
  • Select 'Order SIMs'
  • Wait for SIMs to arrive
  • Go back to the Programmable Wireless section of the console
  • If your SIM is associated with your account, follow the instructions on the SIMs page (Click the plus '+' button)
  • If you have a SIM that is not associated with your account, follow the instructions in 'Starter SIMs'

Once your SIM is in-hand, associated, and has a data plan, you can insert it into the LinkIt ONE.  Be careful to only punch out to the medium sized SIM (if you do accidentally punch out too much, friction should hold the SIM in the medium SIM size).  Note: the correct orientation is the reverse of most phones; the angled side should face outward.

SIM Card Insertion LinkIt

Note the orientation of the SIM card.  Also, if you haven't yet, attach the antennas as shown above.

A General Warning About 2G Development

While most of your code will be transferrable, it’s important to note: for new product development 2G isn’t a good choice.

American 2G networks are being deprecated. For new product development, we suggest researching alternatives or discussing your idea with the Twilio Programmable Wireless team.

Safety in Abstraction: Protecting Credentials where Cloud Meets Field

The LinkIt ONE with the Mediatek MT2502A SoC onboard has the power to hit the Twilio APIs directly with TLS.  However, it's safer to build a more abstract infrastructure that doesn't leave any keys on the device.  

Take note: We do not suggest putting your Twilio Account SID or API Secret directly on your final product.  As your device will be in the field, many people will have physical access; it is best to follow a similar model to the one we've built today.

Here's how sending SMS messages looks in this infrastructure:

LinkIt Architecture

By using AWS IoT as a middleman, we're able to revoke a certificate simply and quickly if a device goes missing - without invalidating the certificates on all the rest of the devices.

Add the LinkIt ONE to AWS IoT

Visit the AWS IoT console.  

  • On the left side, go into ‘Registry’ -> ‘Things
  • In the upper right-hand corner click the button to ‘Create’ a new thing.
  • Show the optional configuration and create a new type, something like ‘LinkIt_One’.  You don’t need attributes yet.
  • Name your thing ‘mtk_test_mf’.  (That name is currently hard coded in the MediaTek library.)
  • ‘Create’ your thing, and you should see a new sidebar with five options:

Add a Thing to AWS IoT
  • Go to ‘security’ and ‘Create Certificate’.  
  • DOWNLOAD EVERY CERTIFICATE THAT'S CREATED (this is your only chance to download the private key!).  You’ll have 4 downloads total:

Download All Certificates, LinkIt ONE

For the Root CA, you may need to Right-Click or Ctrl-Click and save it (name it something like root.pem).

Before leaving 'ACTIVATE' the certificate.

Activate IoT Certificate AWS IoT

Add a Policy

  • Go to the main AWS IoT Console (not the Thing’s console)
  • Select ‘Security’ -> ‘Policies’. 
  • Create a new policy with an action of ‘iot:*’ and Resource ARN of ‘*’
  • Here’s how it should look:
  "Version": "2012-10-17",
  "Statement": [
      "Effect": "Allow",
      "Action": "iot:*",
      "Resource": "*"

MQTT Security Policy, AWS IoT

After you've finished that step, go back to the main IoT console.

Attach the Policy to Our Certificate

  • Go to ‘Security’ -> ‘Certificates’ and select the certificate we made.  
  • Click ‘Policies’ in the left sidebar, then in the ‘Actions’ menu, select ‘Attach Policy’:

Attach AWS IoT Security Policy
  • Attach the policy we just created to our certificate.
  • Go back to the main console.

Open the Web MQTT Client

Now you’ll want to open a Test client to verify our Thing is working when it's finally all hooked up.  

  • Select 'Test' in the main console.
  • Subscribe to the ‘twilio’ (lowercase) topic.  

You can leave the other settings alone - that's all we need for our AWS IoT Setup!  Now, let's move over to the board.

Prepare the LinkIt ONE for Programming

Whether you will be using Programmable Wireless or WiFi, ensure your antennas are attached to the board (see the picture in "Purchase and Activate a Programmable Wireless SIM Card") - it's time to load some code!

Familiarize Yourself with the LinkIt ONE's Settings

Familiarize yourself with how the switches work on the LinkIt.  With the board oriented as it is in the following picture,

  • Left: MASS STORAGE is up, needed for firmware updates and uploading certificates.  NORMAL is down, needed for running sketches.
  • Middle: USB Power is up, use this for the demo.  BATTERY power is down.
  • Right: SPI Mode is up, you should use this because of the pins we are using.  SD Mode is down if you add an SD card.


LinkIt ONE Switch Positions

When changing modes, this is the safest order of operations:

  1. Power down the board (disconnect USB or battery)
  2. Flip the switches needed
  3. Power on the board again, or follow step specific instructions

Set Up Your Board and the Arduino IDE

For our demo today, we're going to be using the Arduino IDE.  Arduino abstracts some of the more common issues we see on the hardware front.  

JSON Board File in Arduino

(If you have many development boards put a comma between each one’s JSON path)

  • Go to ‘Boards Manager’ in the ‘Tools’ -> ‘Board’ submenu
  • Search for "LinkIt"
  • Install the Boards Manager

Boards Manager LinkIt

Next, we need to install a USB COM/Serial Port driver for your operating system.

Install the Serial Port Driver

Unfortunately, this part is platform dependent.  

Please follow the instructions for your operating system:

When you are done, restart your computer.

(Maybe) Update Your Firmware

Reopen the Arduino IDE.

Some batches of LinkIt ONEs are delivered with an older version of board firmware.  If you plug in your LinkIt in 'Normal' mode (see above) and still do not see Serial Ports in the 'Tools' menu of the Arduino, you probably need to update the firmware.

Note: It is non-destructive to update the firmware if you already have the newest firmware, this should not brick your board.

Here is our write-up for how to update the onboard firmware.

Install Arduino Libraries with the Library Manager

If you haven't yet, reopen the IDE.

  1. Select ‘Sketch’ -> ‘Include Library’ -> ‘Library Manager’ from the menu bar.  
  2. In the screen that shows up, search ‘json’ and click to install ArduinoJSON by Benoit Blanchon.

Install Arduino Libraries from a ZIP File

  1. From the AWS mbedTLS MediaTek Demo repository, choose ‘Download ZIP’ from the ‘Clone or Download’ menu.
  2. In Arduino, select ‘Sketch’ -> ‘Include Library’ -> ‘Add .ZIP Library’, and the ZIP file.

Add the AWS IoT Certificates to the LinkIt ONE

Follow along with the main MediaTek instructions at the same time as this section.

  • Unplug the LinkIt from power
  • Change your board to MASS STORAGE mode
  • Plug the LinkIt ONE into USB - you should now see a USB disk

Copy every single certificate you downloaded to the board (in the disk root, do not put anything in a directory).  Check the LinkIt ‘disk’, you should see everything inside after:

LinkIt Certificates
  • Note the full name of the certificates, and also note the extension.  
  • You need the names to match perfectly in the code.

Program The LinkIt ONE

You're now ready to start programming the LinkIt ONE.

  • Clone our repository
  • Double click the '.ino' file inside, which will open the sketch* in the Arduino IDE.

* An Arduino 'sketch' is a project, and all code is contained inside the same folder.  For more on sketches, see here.

Choose to Connect Through Twilio's Programmable Wireless or WiFi

The sample application is set up to either connect through Twilio's Programmable Wireless over GPRS or via WiFi.

To use WiFi, you should change WIFI_USED to true, as well as edit the variables WIFI_AP, WIFI_PASSWORD, and WIFI_AUTH.

Editor: we added all three files here when we migrated this tutorial to the Twilio blog. They were all in the same directory.

 * Twilio send and receive SMS/MMS messages through AWS IoT, Lambda, and API 
 * Gateway with the LinkIt ONE Mediatek development board.
 * This application demonstrates sending out an SMS or MMS from a LinkIt ONE
 * board through AWS IoT, and receiving messages via a MQTT topic
 * subscribed to by the board.
 * This code owes much thanks to MediaTek Labs, who have a skeleton up on
 * how to connect to AWS IoT here: 
 * You'll need to install it to run our code.
 * License: This code, MIT, AWS Code: Apache (
// Handle incoming messages
#include <ArduinoJson.h>

/* Scheduling and connectivity from LinkIt ONE Board */
#include <LTask.h>
#include <LWiFi.h>
#include <LWiFiClient.h>
#include <LGPRS.h>

// Local Includes
#include "TwilioLinkItHelper.hpp"

 * Start here for configuration variables.  First up, all of the AWS IoT
 * configuration you need.  You'll need to upload the private key,
 * certificate, and root key to the board.
String           AWS_IOT_MQTT_HOST =             "";
String           AWS_IOT_MQTT_CLIENT_ID =        "LinkItONE_Twilio";
String           AWS_IOT_ROOT_CA_FILENAME =      "G5.pem";
String           AWS_IOT_CERTIFICATE_FILENAME =  "SOMETHING-certificate.pem";
String           AWS_IOT_PRIVATE_KEY_FILENAME =  "SOMETHING-private.pem";
String           AWS_IOT_TOPIC_NAME = "$aws/things/mtk_test_mf/shadow/update";

// Don't edit this one unless you also edit the /aws_iot_config.h file!
// We've left the name for ease of plug and play, but might eventually 
// rework this part of the library.
String           AWS_IOT_MY_THING_NAME =         "mtk_test_mf"; 

/* Should we use WiFi or GPRS?  'true' for WiFi, 'false' for GPRS */
boolean WIFI_USED =                     true;

 *  Now, the _Twilio specific_ configuration you need to send an outgoing
 *  SMS or MMS. 
 *  In production, you may want to pass these over MQTT (or a similar 
 *  channel) to change settings on devices in the field.

String your_device_number        = "+18005551212"; // Twilio # you own
String number_to_text            = "+18005551212"; // Perhaps your cellphone?
String your_sms_message          = "Can you draw this owl?";
String optional_image_path       = "";
String linkit_image_path         = "";

/* Optional Settings.  You probably do not need to change these. */
String twilio_topic        = "twilio";
const int mqtt_tls_port = 8883;

/* Friendly WiFi Network details go here.  Auth choices:
#define WIFI_AP "Signal 2017"
#define WIFI_PASSWORD ""

 *  Twilio GPRS Settings here - you should not have to change these to 
 *  use a Twilio Programmable Wireless SIM Card.  Make sure your card 
 *  is registered, provisioned, and activated if you have issues.
#define GPRS_APN ""

/* Workaround to remove a Macro from mbedtls */
#ifdef connect
#undef connect

typedef int32_t mqtt_callback(MQTTCallbackParams);

// Global Twilio Lambda helper
void disconnect_function();
TwilioLinkitHelper helper(

struct MQTTSub {
        char* topic;
        mqtt_callback* callback; 

struct MQTTPub {

 * Our Twilio message handling callback.  This is passed as a callback function
 * when we subscribe to the Twilio topic, and will handle any incoming messages
 * on that topic.
 * You'll want to add your own application logic inside of here.  For this 
 * demo, we'll take the first 160 characters of the message body and send it 
 * back in reverse and optionally write to a serial connection.
 * No, this doesn't handle unicode - prepare for weird results if you send
 * any non-ASCII characters!
int32_t handle_incoming_message_twilio(MQTTCallbackParams params)
        MQTTMessageParams message = params.MessageParams;
        // We don't have std::unique_ptr
        //std::unique_ptr<char []> msg(new char[message.payloadlen+1]());

        char msg[message.PayloadLen+1];
        memcpy (msg,message.pPayload,message.PayloadLen);
        StaticJsonBuffer<maxMQTTpackageSize> jsonBuffer;
        JsonObject& root = jsonBuffer.parseObject(msg);
        String to_number           = root["To"];
        String from_number         = root["From"];
        String message_body        = root["Body"];
        String message_type        = root["Type"];

        // Only handle messages to the ESP's number
        if (strcmp(to_number.c_str(), your_device_number.c_str()) != 0) {
                return 0;
        // Only handle incoming messages
        if (!message_type.equals("Incoming")) {
                return 0;

        // Basic demonstration of rejecting a message based on which 'device'
        // it is sent to, if devices get one Twilio number each.
        Serial.print("\n\rNew Message from Twilio!");
        Serial.print("\r\nTo: ");
        Serial.print("\n\rFrom: ");

        // Now reverse the body and send it back.

        // std::unique_ptr<char []> return_body(new char[161]());
        char return_body[161];
        int16_t r = message_body.length()-1, i = 0;

        // Lambda will limit body size, but we should be defensive anyway.
        // uint16_t is fine because 'maxMQTTpackageSize' limits the total 
        // incoming message size.
        // 160 characters is _index_ 159.
        r = (r < 160) ? r : 159; 
        return_body[r+1] = '\0';
        while (r >= 0) {
                return_body[i++] = message_body[r--];
        // Send a message, reversing the to and from number

        return 0;

 *  Main setup function.
 *  Here we connect to either GPRS or WiFi, then AWS IoT.  We then subscribe
 *  to some channels and send out a SMS (or MMS) via MQTT and our 
 *  Lambda backend.
void setup()
        while(!Serial) {
                // Busy wait on Serial Monitor connection.

        if (WIFI_USED){
                Serial.println("Connecting to AP");
                while (!LWiFi.connect(
                                LWiFiLoginInfo(WIFI_AUTH, WIFI_PASSWORD)
                ) {
        } else {  
                Serial.println("Connecting to GPRS");
                while (!LGPRS.attachGPRS(
                ) {


        //char HostAddress[255] = AWS_IOT_MQTT_HOST;
        VMINT port = mqtt_tls_port;
        CONNECT_PORT = port;
        LTask.remoteCall(&__wifi_dns, (void*)AWS_IOT_MQTT_HOST.c_str());
        LTask.remoteCall(&__bearer_open, NULL);
        LTask.remoteCall(&__mqtt_start, NULL);

        MQTTSub sub_struct;
        sub_struct.topic = const_cast<char*>(twilio_topic.c_str());
        sub_struct.callback = handle_incoming_message_twilio;
        LTask.remoteCall(&__sub_mqtt, (void*)&sub_struct);
        //char mqtt_message[30];
        //sprintf(mqtt_message, "Hello World - Love, Twilio");
        Serial.println("publish_MQTT go");
        //linkitaws::publish_MQTT("twilio", mqtt_message);

 *   Disconnect callback function - what do you want to do when it goes down?
void disconnect_function() 

        Serial.println("Oh no, we disconnected!");
        // We could just reconnect, but in this case it's probably better to 
        // power cycle in a place with better signal.
        // helper.start_mqtt()

 *   Every time through the main loop we will spin up a new thread on the 
 *   LinkIt to perform our watchdog tasks in the background.  Insert everything
 *   you want to perform over and over again until infinity (or power 
 *   loss!) here.
boolean main_thread(void* user_data) 
        static bool sent_intro = false;

        if (!sent_intro) {
                sent_intro = true;

        // We need to handle any incoming messages from AWS.
        // Don't remove this call from the loop.

 * The standard Arduino loop.  You don't want to put anything here; just
 * spin up new threads since the MediaTek can handle these async.
 * This example just creates a new thread which calls main_thread()
 * every 3 seconds.
void loop() 
        /* We can do our main tasks in a new thread with LTask */
        LTask.remoteCall(main_thread, NULL);

 *  Trampolines/thunks are the easiest way to pass these class methods.  
 *  We don't have std::bind() or boost::bind()!
inline boolean __bearer_open(void* ctx) 
{ return helper.bearer_open(ctx); }

inline boolean __mqtt_start(void* ctx)  
{ return helper.start_mqtt(ctx); }

inline boolean __wifi_dns(void* ctx)    
{ return helper.wifiResolveDomainName(ctx); }

inline boolean __sub_mqtt(void *ctx)   
        MQTTSub* sub_struct = (MQTTSub*)ctx; char* topic = sub_struct->topic;
        mqtt_callback* callback = sub_struct->callback;
        helper.subscribe_to_topic(topic, callback);
        return true;

If you choose to use GPRS, you can leave the details in place.

Change the Certificate Names and Phone Numbers

Let us warn you now: transcription errors between the certificate names and the code cause a lot of issues in this step.  You should edit the certificates here to exactly match the names of the certificates in the root directory of the LinkIt.  

Also, the root certificate's name must be shorter than 32 characters long.  You will see a flashing error if you do not use a shorter root certificate name - we have ours named as G5.pem.

Additionally, at this point change the 'from' and 'to' phone numbers to be a Twilio number you own and a number that can receive SMSes or MMSes.  If you cannot receive MMSes, you can add an empty quote in optional_image_path.

(If you get any issues in the eventual serial output in the certificate section, this is the first place to check.)

Send SMSes and MMSes from the LinkIt ONE via MQTT

This section of code is where we prepare our outgoing message.  We assemble a to and a from number, a message, and an image (you can comment it out to send an SMS instead).

We prepare the eventual message and publish it to an MQTT topic.

Not that this code runs on a timer every 3 seconds:

LTask.remoteCall(main_thread, NULL);

The actual SMS or MMS sending only happens the first time because we have it wrapped in logic with a static boolean.  Behind the scenes, the handle_requests() method comes into play, where we perform periodic functions like watching for replies - but that's the topic of another tutorial.

You merely need to compile and run the code now.  If you still have the 'test' MQTT Client open from the AWS IoT steps, you should see a single message published on the twilio topic.

You did?  Awesome!  Now let's move to the AWS Lambda integration.

Use AWS Lambda to Talk to Twilio

So now you've got the LinkIt ONE and AWS IoT conversing over MQTT - you're most of the way to the prize.  Our second middleman is AWS Lambda, where we will set up a function which is called for certain MQTT messages and will do the actual conversing with Twilio.

Create a New Lambda Function

  • In the Lambda Console, create a new function in the same region as the location of your IoT setup.
  • Use the ‘Blank Function’ as a starting point and choose Python 2.7 for a runtime:

AWS Lambda Python Function

Create a Lambda Trigger

  • In the ‘Trigger’ window, select Amazon AWS IoT as the trigger
  • Create a nice trigger name you'll remember
  • Use the following SQL to trigger the action:
SELECT * FROM 'twilio' WHERE Type='Outgoing'
  • Your screen should match the following before continuing (but don’t put whitespaces in the rule name - just use camelCase.  This won't work):

Lambda IoT Setup


Configure the Function and Upload the ZIP File

  • First, check the runtime is still Python 2.7 (it probably has changed)
  • For ‘Code Entry Type’ pick ‘Upload a ZIP File.’
  • DOWNLOAD THIS ZIP FILE.  It contains our SMS-sending code. is where the magic happens:
from __future__ import print_function

import os
from import Client

def send_message(to_number, from_number, message_body, picture_url=""):
    """Wrap the Twilio Python library and send SMS/MMS messages."""
    auth_token = os.environ['AUTH_TOKEN']
    account_sid = os.environ['ACCOUNT_SID']
    client = Client(account_sid, auth_token)

    message_dict = {}
    message_dict['to'] = to_number
    message_dict['from_'] = from_number
    message_dict['body'] = message_body
    if picture_url != "":
        message_dict['media_url'] = picture_url

    # Send a SMS or MMS with Twilio!

def iot_handler(event, context):
    """Handle incoming messages from AWS IoT."""
    print("Received event: " + str(event))
    if 'To' not in event or 'From' not in event or 'Body' not in event \
            or 'Type' not in event or event['Type'] != 'Outgoing':
        # Guard against malformed events being sent to us

    picture_url = ""
    if 'Image' in event:
        picture_url = event['Image']

    send_message(event['To'], event['From'], event['Body'], picture_url)


We enter in the function iot_handler and do some very brief exception handling.  If we are sending an MMS, we append an image URL to the message.

From there, we create a dictionary in the send_message function.  Then we use the Twilio Python Helper Library's client to create and send the message out.

The ZIP file packages all of the dependencies so you don't have to worry about installing them on Lambda - just upload the ZIP file and you'll be 90% of the way there.  Next up, let's get you that last 10%.

Create a New Role in Lambda

  • Select ‘Create new role from template(s)’.  
  • Amazon has an IoT product, the IoT Button, which has the rules we can reuse.  Use that template:

AWS IoT Button Rules

Edit Advanced Settings

The final settings are up to you, but we were successful with these settings:

  • Memory (MB)*: 128 (This may or may not be the default)
  • Timeout: 10 seconds (Ditto)

In production, you will want to keep a careful eye on these.

  • Hit ‘Next’
  • On the next screen, select ‘Create Function’

Point AWS Lambda at Your Function

  • In 'Handler', use twilio_functions.iot_handler to tell Lambda where to enter
  • ‘Save’ it (this button is on the top of your screen)
  • Head back to the ‘Code’ Tab.

Add Your Twilio Environment Variables to Lambda

You need to set two Environmental variables for Lambda to communicate with Twilio: AUTH_TOKEN and ACCOUNT_SID.  

Find those in the Twilio Console:

Twilio Account Summary section of the console

When you find them, enter them in the ‘Environment Variables’ section in Lambda:

Lambda Environmental Variables

Save one more time, and we're ready to move on from Lambda.  There is just one more step in AWS IoT before everything is ready!

Return to AWS IoT

  1. Go back to the AWS IoT Console
  2. Click ‘Rules’ on the sidebar on the left side
  3. Click on the new rule you recently created
  4. Click ‘Edit’, then select the 2015-10-08 SQL version and 'Update'
  5. You should now see:

AWS IoT SQL Rule Version

Excellent work!  Now you've got everything in place:

  1. The LinkIt ONE can publish to the twilio topic through AWS IoT
  2. Certain messages trigger AWS IoT to passthrough to Lambda
  3. Legal messages that hit Lambda are sent to Twilio
  4. Twilio sends your messages into the world

If you're confident, it's time - power cycle your board and see if you get a text message!

Communications from a LinkIt ONE with Twilio's Programmable Wireless and AWS Lambda

Look at what you've now built: a device equally at home with 2G or with WiFi that can be deployed in the field and send SMSes at will.  If the device is compromised, you can revoke the certificate near-instantly through AWS IoT without impacting the rest of your Things.  Nifty, right?

What's next?  Since you've now built outbound messaging on the LinkIt ONE, perhaps you'd next like to visit our guide on receiving SMSes and MMSes with the LinkIt ONE, Programmable Wireless, and AWS IoT and Lambda.  We also have some awesome beginning to end Blueprints that use Programmable Wireless.

Whether you've now got what you need or you're going to keep building, keep in touch with us on Twitter!  We're very interested in your Internet of Things project, and we can't wait to see what you build.