Skip to content
This repository has been archived by the owner on Mar 17, 2021. It is now read-only.

codeforamerica/la_message

Repository files navigation

LA'MESSAGE

A barebones Ruby on Rails application for sending and tracking SMS messages in Louisiana via Twilio. This was used in the second half of 2019 to support the LA'MESSAGE pilot by the Integrated Benefits initiative. Specifically, this was used to send text messages to Medicaid clients.

Usage

This application is intended to be used via the Rails console bin/rails c and does not have a web-based UI.

There are two database-backed models:

  • Contact: represents a name, phone number, and additional metadata such as whether they have opted-in.
  • Message: represents an inbound or outbound SMS, including receiving and sending phone numbers and message body.

There is one set of objects called Campaign Messages that define outbound messages, and can optionally handle responses. These all inherit from the base class CampaignMessage.

Development Setup

This Ruby on Rails application tries to be as boring as possible.

Run $ bin/setup.

More details

You will need, assuming a standard Mac setup:

  1. Homebrew for MacOS.
  2. A ruby version manager, like rbenv.
  3. Locally installed Ruby of the version defined in .ruby-version. e.g. $ rbenv install 2.6.3
  4. Postgres, like Postgres.app.
    • If setting up Postgres.app, you will also need to add the binary to your path. e.g. Add to your ~/.bashrc: export PATH="$PATH:/Applications/Postgres.app/Contents/Versions/latest/bin"
  5. Install system dependencies defined in Brewfile with $ brew bundle

Example scripts

Some examples of scripts for running within Rails console ($ bin/rails c):

Importing contacts

require 'csv'
CSV_PATH = '/path/to/file.csv'
RENEWAL_DATE = 'August 31, 2019'.to_date
csv = CSV.open(CSV_PATH, headers: true)

csv.each.with_index do |row, index|
  next if row['CELLPH_NUM'] == 'NULL'

  contact = Contact.find_or_initialize_by(phone_number: PhoneNumberFormatter.format(row['CELLPH_NUM'])) do |c|
    c.first_name = row['FIRST_NAME']
    c.last_name = row['LAST_NAME']
  end

  # Filter out anyone whose name doesn't match an existing record
  next if contact.first_name != row['FIRST_NAME'] || contact.last_name != row['LAST_NAME']

  contact.renewal_date = RENEWAL_DATE
  contact.opted_in = true if row['NOTIFICATION_TYPE'] == 'TEXT'
  contact.save!

  puts "\n\n\n\n==== ROW #{index} ====\n\n\n\n" if index.multiple_of?(10)
end

Validating mobile phone numbers

twilio_client = Twilio::REST::Client.new

Contact.where(carrier_type: nil).find_each do |contact|
  response = twilio_client.lookups.phone_numbers(contact.phone_number).fetch(type: ['carrier']).carrier
  
  if response['type'].present?
    contact.update(carrier_type: response['type'])
  elsif response['error_code'].present?
    contact.update(carrier_type: 'error')
  end     
rescue Twilio::REST::RestError
  contact.update(carrier_type: 'error')
rescue
  next  
end

puts Contact.group(:carrier_type).count

Deployment / Architecture

Briefly:

  1. Docker is used to create an image.
  2. Docker images are stored in an ECR repository.
  3. EKS is used to manage a Kubernetes Cluster.
  4. The application is deployed to the cluster with an ELB-backed Ingress Service.
  5. The application uses an RDS database whose credentials are stored in a Kubernetes Secret.
  6. Some common workflows are captured in the Makefile:
    • $ make deploy: creates a new docker image, pushes it to ECR, creates a migration job, waits and cleans it up, and issues a rollout.
    • $ make kube-bash: Creates an interactive bash shell on one of the web pods.

EKS

To authorize:

# Sign in with the cluster-authorized IAM user
aws configure

# Set up ~/.kube/config to connect to the cluster
aws eks --region us-east-1 update-kubeconfig --name ibi-production

# Check that you can connect to the cluster
kubectl get svc

# For convenience, create a context to set the namespace for this application
kubectl config set-context la-message --cluster=ibi-production.us-east-1.eksctl.io --user=kube-production@ibi-production.us-east-1.eksctl.io --namespace la-message \
  && kubectl config use-context la-message

Copying a CSV file to the first pod:

kubectl cp tmp.csv la-message/$(kubectl get pods --selector=app=web --output=jsonpath='{.items[0].metadata.name}'):tmp.csv

Creating a new cluster

The Kubernetes cluster is created with eksctl which shouldn't be needed once the initial cluster is set up:

eksctl create cluster \
  --name ibi-production \
  --nodegroup-name standard-workers \
  --node-type m5.large \
  --nodes 2 \
  --nodes-min 1 \
  --nodes-max 4 \
  --node-ami auto \
  --region us-east-1 \
  --zones "us-east-1a,us-east-1b"

About

SMS messaging application for sending and tracking text messages in Louisiana.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published