Send SMS and MMS Messages In Tcl/Tk

January 10, 2018
Written by
Paul Kamp
Twilion

tcl-tk-sms-send

Today we'll use Tcl and the Tk graphical toolkit to rapidly prototype a Twilio-powered SMS and MMS sending application.  We're going to be using the packages ttk (which will require Tcl 8.5+), tls, base64, and http.

Sign Up for (or Sign Into) a Twilio Account

You'll need to either have an existing Twilio account or create a new account to try out the Tcl code.

Don't have an account yet?  Sign up for a free Twilio trial!

Purchase or Find a SMS-Capable Number

You'll need a SMS (or optionally, MMS) capable number to use this demo application.  Purchase a new SMS-enabled number or use one that you already own.

From the Twilio Console, click the '#' hash tag to look at Phone Numbers.  Any numbers you have purchased will have the capabilities listed in the middle.  'SMS' and 'MMS' enabled numbers will appear with an icon:

Checking a Twilio Phone Number for SMS Capability

If you need to purchase a number, navigate to the 'Buy a Number' link and click the SMS checkbox (and optionally the MMS checkbox).  Hit 'Search' and you should see all of the numbers available.

Buy a SMS-Capable Twilio Phone Number

Prerequisites for This Tcl MMS and SMS Guide

Many POSIX-Compliant operating systems ship with a distribution of Tcl and a number of popular packages.  tls, however, may not be installed on your machine - you can access the tls package's site here.  Alternatively, ActiveState maintains a number of excellent distributions of Tcl which make it easy to install packages.

TCL packages needed:

  • tls
  • http
  • base64
  • Tk

We were able to run the demo with the default Tcl8.5 distribution that comes with recent versions of Mac OS X.  Here's an example session in bash on Mac OSX 10.11.6:

git clone https://github.com/TwilioDevEd/twilio-tcl-demo.git
cd twilio-tcl-demo
export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXX
export TWILIO_AUTH_TOKEN=yourauthtokengoeshere
export TWILIO_PHONE_NUMBER=+18005551212
chmod 755 twilio.tcl
/usr/bin/tclsh8.5 /twilio.tcl # Or wherever; default 8.5 worked on our Mac

Send an SMS or MMS Message with TCL/Tk

Our sample application shows how to bring up a cross-platform GUI with Tcl and Tk, then quickly send out a text message.

The GUI code is pretty boilerplate, other than the fact we're taking full advantage of ttk widgets.

#!/usr/bin/env tclsh

# twilio.tcl --
#
#   This is a short demo of how to send a SMS or a MMS message from TCL/Tk
#   with the power of the Twilio APIs (and the GUI productivity of Tk!).
#   You can use the ::twilio::send_sms{} function piecemeal for your own
#   applications.
#
#   License: MIT
#
#   Installation is platform dependent; many POSIX compliant systems have TCL
#   installed, however (try 'tcl' then <tab> completing).  On recent
#   versions of OSX, for example, you can run this with:
#
# > tclsh8.6 twilio.tcl
#
# Because we are using the ttk widets, *you'll need at least TCL 8.5.*
#
# You will need to set:
#
#   TWILIO_AUTH_TOKEN
#   TWILIO_PHONE_NUMBER
#   TWILIO_ACCOUNT_SID
#
# as environment variables.  Then, merely run and fill out the Tk form
# with a destination phone number, a message body, and an optional image URL.

package require http
package require tls
package require base64
package require Tk

# Put all our functions into the twilio namespace
namespace eval twilio {
    # Get environment variables
    set phone_number $::env(TWILIO_PHONE_NUMBER)
    set account_sid $::env(TWILIO_ACCOUNT_SID)
    set auth_token $::env(TWILIO_AUTH_TOKEN)

    # Base URL for the Twilio Messaging API
    set url \
        "https://api.twilio.com/2010-04-01/Accounts/${account_sid}/Messages"

    # twilio::build_auth_headers --
    #
    #   Use Base64 to build a Basic Authorization header.
    #
    #   Arguments:
    #       username, password which maps to ACCOUNT_SID and AUTH_TOKEN
    #   Results:
    #       A string with the Basic Authorization header

    proc build_auth_headers {username password} {

        return "Basic [base64::encode $username:$password]"
    }

    # twilio::submit_form --
    #
    #   Submit the Tk Twilio message form
    #
    #   Arguments:
    #       None (gets them from the form)
    #   Results:
    #       None (it sends an SMS or MMS)
    proc submit_form {} {
        # Get the form parameters and send a text
        set form_body [.c.body get 1.0 end]
        if {[string length form_body] <= 1} {
            set ::result "Please add a body."
            return
        }

        if { [catch { set form_to $::form_to } ] } {
            set ::result "Check phone number."
            return
        }
        if { [catch { set image_url $::form_image } ] } {
            set image_url ""
        }

        send_sms                        \
            $form_to                    \
            $::twilio::phone_number     \
            $form_body                  \
            $::twilio::account_sid      \
            $::twilio::auth_token       \
            $image_url

        # Now delete everything; we don't want the user to send twice.
        .c.body delete 1.0 end

        # Give a nice result to our user
        set ::result "You sent it!"
    }

    # twilio::send_sms --
    #
    #   Sends an SMS or MMS with Twilio.  (Get the required variables from
    #   the Twilio console.)
    #
    #   Arguments:
    #       to, from - the number to send the message to and from where
    #       body - body text to send
    #       account_sid - Twilio account SID
    #       auth_token - Twilio auth token
    #   Results:
    #       false if we failed, true if Twilio returns a 2XX.  Also dumps
    #       Twilio's response to standard out.

    proc send_sms {to from body account_sid auth_token {image_url ""}} {
        ::tls::init -tls1 1 -ssl3 0 -ssl2 0
        http::register https 443 \
            [list ::tls::socket -request 1 -require 1 -cafile ./server.pem]


        # Escape the URL characters, optionally add media
        if {[string length $image_url] == 0} {
            set html_parameters                         \
                [::http::formatQuery                    \
                    "From"  $from                       \
                    "To"    $to                         \
                    "Body"  $body                       \
                ]
        } else {
            set html_parameters                         \
                [::http::formatQuery                    \
                    "From"      $from                   \
                    "To"        $to                     \
                    "Body"      $body                   \
                    "MediaUrl"  $image_url              \
                ]
        }

        # Make a POST request to Twilio
        set tok [                                   \
            ::http::geturl $::twilio::url           \
                -query $html_parameters             \
                -headers [list                      \
                    "Authorization"                 \
                    [                               \
                        build_auth_headers          \
                        $account_sid                \
                        $auth_token                 \
                    ]                               \
                ]                                   \
        ]

        # HTTP Response: print it to command line if we failed...
        if {[string first "20" [::http::code $tok]] != -1} {
            puts [::http::code $tok]
            puts [::http::data $tok]
            return false
        } else {
            return true
        }
    }
}

# init_gui --
#
#   Initialize the GUI for the TCL/Tk Twilio demo.
#
#   Arguments:
#       None
#   Results:
#       None (it creates the GUI)

proc init_gui {} {
    # Set up the nice GUI we'll present to our end user.
    wm title . "Twilio SMS/MMS Demo with TCL/Tk!"
    grid [ttk::frame .c -padding "2 2 20 40"]                           \
        -column 0                                                       \
        -row 0                                                          \
        -sticky nwes

    grid columnconfigure . 0 -weight 1
    grid rowconfigure . 0 -weight 1

    grid [ttk::label .c.tolbl -text "To:"]                              \
        -column 1                                                       \
        -row 1                                                          \
        -sticky w

    grid [ttk::entry .c.to_number -width 20 -textvariable form_to]      \
        -column 2                                                       \
        -row 1                                                          \
        -sticky we

    grid [ttk::label .c.bodylbl -text "Body:"]                          \
        -column 1                                                       \
        -row 2                                                          \
        -sticky w

    grid [text .c.body -width 20 -height 8]                             \
        -column 2                                                       \
        -row 2                                                          \
        -sticky we

    grid [ttk::label .c.imlbl -text "Image URL (Optional):"]            \
        -column 1                                                       \
        -row 3                                                          \
        -sticky w

    grid [ttk::entry .c.image_url -width 60 -textvariable form_image]   \
        -column 2                                                       \
        -row 3                                                          \
        -sticky we

    grid [ttk::label .c.meters -textvariable result]                    \
        -column 1                                                       \
        -row 4                                                          \
        -sticky we

    grid                                                                \
        [ttk::button .c.calc                                            \
            -text "Send a Message"                                      \
            -command                                                    \
            ::twilio::submit_form                                       \
        ]                                                               \
        -column 2                                                       \
        -row 4                                                          \
        -sticky w


    # Configure all of the children of c we just set up
    foreach w [winfo children .c] {
        grid configure $w -padx 5 -pady 5
    }

    # Set the initial form function to the 'To' Number field.
    focus .c.to_number
}

# This line makes all the action happen.
init_gui

After submitting the form, we first run a function to get the current state of all the GUI elements.  Your production application should perform more exhaustive checks than we're demonstrating, but for now we'll just let Twilio reject our out-of-scope requests (like letters instead of phone numbers!).

Following that, we get to the heart of the SMS and MMS sending code.  The send_sms{} procedure should be easily portable to your own application.  It takes phone numbers, a message body, Twilio account credentials, and an optional media url as input and POSTS to Twilio's REST API.

Whether Twilio sends out an MMS or an SMS depends on the content of the Media URL and the number's home country.  Twilio currently supports MMS messages in the United States and Canada.  Other countries will have the image appended to the body as an automatically shortened URL.

Tickle Your Fancy with Twilio and... Tcl

With the power of Twilio's REST APIs you can quickly integrate communications into your application, no matter the language.  Whether that's embedding Twilio connectivity in your appliance, prototyping a desktop support application, SMSing the whole design team when the routes are too scenic or texting you when there's another Tcl flamewar on your favorite newsgroup... we've got your back.

With Twilio's API and your application, the possibilities are endless.  

We can't wait to see what you build - let us know on Twitter when you've got something to show!