Friday, March 13, 2015

Case Study: Web Form to Store Phone Numbers and Send Texts

SUMMARY:
  • Store a list phone numbers without managing a database.
  • Run web app from anywhere with internet access.
  • Send text messages in bulk with Twilio API.


BACKGROUND:
Kinsey is a member of the PTA (Parent Teacher Association) at her children's school and in charge of reminding everyone about meetings.

PROBLEM:
Sending text messages to all the people on the PTA is a pain. She wanted a better way to send the messages and better way to manage the list.

ANSWER:
Using hookscript's built in data store, Kinsey created a simple html form to add/remove people and send them texts.

STACK:
Written in Perl. Sends text via Twilio. Data storage with hookscript's built in 'State'.

Kinsey wants to use a web form to add people, remove people, and send message. The form is built into the script. GET displays the form. POST changes the data stored in State.

If the HTTP method is "POST" look at the action value for the form and take that action. Either adding or removing data or sending a text message.

# If method is POST do something
if($req->method eq 'POST') {
    given($req->param('action')) {
        when(undef) {
            # die if no action chosen
            die "Need 'action' parameter";
        }
        when('add') {
            # add a new phone number if you choose that action
            push @$state, $req->param('phone'); 
        }
        when('del') {
            # delete the phone number if you choose that action
            my $phone = $req->param('phone');
            $state = [ grep { $_ ne $phone } @$state ];
        }
        when('sms') {
            # send a text message if you choose that action
            my $message = $req->param('message');
            for my $recipient (@$state) {
                send_text_message($recipient, $message);  
            }
        }
    }
}
Display a web form so you can see all phone numbers in the list and take the action you want.
# HTML to display the webform
# web form displays when runhook.com url is visited
$res->headers->content_type('text/html');
print <<'HTML';
<html>
    <head>
        <title>PTA Contact List</title>
    </head>

    <body>
        <h1>PTA Contact List</h1>
        <table>
            <tr>
                <th>Phone number</th>
                <th></th>
            </tr>
HTML

# display a list of all phone numbers.
# clicking delete removes it from list
for my $phone_number (@$state) {
    my $form = qq{
        <form method=POST>
            <input type=hidden name=action value=del />
            <input type=hidden name=phone value="$phone_number" />
            <input type=submit value="Delete" />
        </form>
    };
    # one row for each phone number
    print "<tr><td>$phone_number</td><td>$form</td></tr>";
}

print <<'HTML';
        </table>

        <!-- add a new user to the list -->
        <h2>Add a new number</h2>
        <form method="POST">
            <input type=hidden name=action value=add />
            <input type=tel name=phone autofocus />
            <input type=submit value="Add a phone number" />
        </form>

        <!-- write a text message and send it to everyone on list -->
        <h2>Send a message</h2>
        <form method=POST">
            <input type=hidden name=action value=sms />
            <textarea name=message cols=40 rows=3></textarea>
            <input type=submit value="Send SMS" />
        </form>
    </body>
</html>
HTML
The 'send_text_message' function uses Twilio's REST API to send text messages to everyone on the list.
# the function to send a text message via twilio
# receives one argument: phone number
sub send_text_message {
    my ($to_phone_number, $message) = @_;
    my $from_phone_number = "your_phone_number";
    
    # Stuff for verifying your account with Twilio.
    # Your account info would replace these placeholders
    my $url = "api.twilio.com/2010-04-01/Accounts";
    my $accountsid = "your_account_id_from_twilio";
    my $authtoken = "your_auth_token_from_twilio";
    
    # Build the full API url with account ID & authentication
    $url = "https://" .
           $accountsid .
           ":" .
           $authtoken .
           "\@" .
           $url .
           "/" .
           $accountsid .
           "/Messages";
    
    # Send the POST details to Twilio.  API must include From, To, and Body
    my $res = HTTP::Tiny->new->post_form($url, [
        From => $from_phone_number,
        To => $to_phone_number,
        Body => $message ]);
    
    # If everything went as planned, confirm it
    if ( $res->{status} == 200 || $res->{status} == 201 ) {
        print "SMS sent: $to_phone_number\n";
    }
    # Oops, something went wrong, confirm that too
    else {
        print "FAILED: $to_phone_number => " . $res->{status};
    }
}
Using hookscript, Kinsey was able to:
  • Store a list phone numbers without managing a database.
  • Send text messages in bulk.
  • Send the text messages from anywhere with internet access.
Complete code:
use Hookscript;
use HTTP::Tiny;
use experimental qw( switch );

# initialize state if it's empty
$state = [] if not defined $state;

# If method is POST do something
if($req->method eq 'POST') {
    given($req->param('action')) {
        when(undef) {
            # die if no action chosen
            die "Need 'action' parameter";
        }
        when('add') {
            # add a new phone number if you choose that action
            push @$state, $req->param('phone'); 
        }
        when('del') {
            # delete the phone number if you choose that action
            my $phone = $req->param('phone');
            $state = [ grep { $_ ne $phone } @$state ];
        }
        when('sms') {
            # send a text message if you choose that action
            my $message = $req->param('message');
            for my $recipient (@$state) {
                send_text_message($recipient, $message);  
            }
        }
    }
}

# HTML to display the webform
# web form displays when runhook.com url is visited
$res->headers->content_type('text/html');
print <<'HTML';
<html>
    <head>
        <title>PTA Contact List</title>
    </head>

    <body>
        <h1>PTA Contact List</h1>
        <table>
            <tr>
                <th>Phone number</th>
                <th></th>
            </tr>
HTML

# display a list of all phone numbers.
# clicking delete removes it from list
for my $phone_number (@$state) {
    my $form = qq{
        <form method=POST>
            <input type=hidden name=action value=del />
            <input type=hidden name=phone value="$phone_number" />
            <input type=submit value="Delete" />
        </form>
    };
    # one row for each phone number
    print "<tr><td>$phone_number</td><td>$form</td></tr>";
}

print <<'HTML';
        </table>

        <!-- add a new user to the list -->
        <h2>Add a new number</h2>
        <form method="POST">
            <input type=hidden name=action value=add />
            <input type=tel name=phone autofocus />
            <input type=submit value="Add a phone number" />
        </form>

        <!-- write a text message and send it to everyone on list -->
        <h2>Send a message</h2>
        <form method=POST">
            <input type=hidden name=action value=sms />
            <textarea name=message cols=40 rows=3></textarea>
            <input type=submit value="Send SMS" />
        </form>
    </body>
</html>
HTML

# the function to send a text message via twilio
# receives one argument: phone number
sub send_text_message {
    my ($to_phone_number, $message) = @_;
    my $from_phone_number = "your_phone_number";
    
    # Stuff for verifying your account with Twilio.
    # Your account info would replace these placeholders
    my $url = "api.twilio.com/2010-04-01/Accounts";
    my $accountsid = "your_account_id_from_twilio";
    my $authtoken = "your_auth_token_from_twilio";
    
    # Build the full API url with account ID & authentication
    $url = "https://" .
           $accountsid .
           ":" .
           $authtoken .
           "\@" .
           $url .
           "/" .
           $accountsid .
           "/Messages";
    
    # Send the POST details to Twilio.  API must include From, To, and Body
    my $res = HTTP::Tiny->new->post_form($url, [
        From => $from_phone_number,
        To => $to_phone_number,
        Body => $message ]);
    
    # If everything went as planned, confirm it
    if ( $res->{status} == 200 || $res->{status} == 201 ) {
        print "SMS sent: $to_phone_number\n";
    }
    # Oops, something went wrong, confirm that too
    else {
        print "FAILED: $to_phone_number => " . $res->{status};
    }
}