14
.
11
.
2023
23
.
12
.
2014
Ruby on Rails
Backend

Text messaging with textris gem

Karol Słuszniak
Ruby Developer

Recently, I've had to hook text messaging into one of our apps. After a quick review of existing solutions, I've found nothing that would meet some of my needs, so I've decided to create a new gem for it.

Prerequisites

When I look for new gems, I try to find those which meet some basic requirements like having clear usage info in Readme, having at least a few tests and, perhaps the most important one, with code that gives good initial impression.

In addition, for this project I needed SMS text messaging solution that would have some particular features:

  • treat internatioal phone numbers in predictable manner, regardless of SMS service
  • play well with Rails conventions (i.e. work analogously to ActionMailer)
  • support sidekiq background jobs for fast server response
  • allow fast & easy change of service that is used for sending SMSes
  • somehow allow storing and inspecting messages sent both in development and in production
  • obviously, allow setting all settings mentioned above per-environment

As you can guess, textris meets all these requirements and more. You can find the gem on GitHub and RubyGems.

Example

I'm not going to repeat the feature list or usage info here, so instead I'll just show a step-by-step guide to using textris with existing Rails app.

The Case

Let's say you have an app with user authentication and accompanying password reset functionality. Users are registered with either e-mail or phone as login field and you want to send password reset SMS for users that don't have e-mail filled yet. You want to use sidekiq for password reset experience to be fast & nice. In production, you want to use Twilio as SMS gateway and store Mailinator copies of SMSes. This feature is really important for the client and she wants to investigate SMSes you app sends without your actions (like you looking into server logs).

Step 1: Switching between mailer and texter

I'm not going to describe how to do a 2-field authentication or how to hack devise (or whatever preposterous gem you use for auth) for described scenario. I'll leave this funny part up to you. Let's assume the password reset mailer gets invoked in PasswordResetsController:

class PasswordResetsController
  def create
    user = User.find_by!(:email => params[:email])

    PasswordResetMailer.reset_password(user).deliver
  end
end

Let's extend it like this:

user = User.find_by!('email = :value OR phone = :value', :value => params[:email_or_phone])

if user.email.present?
  PasswordResetMailer.reset_password(user).deliver
else
  PasswordResetTexter.reset_password(user).deliver
end

Step 2: Texter itself

Nice, but what is PasswordResetTexter in code above? Let's add it as app/texters/password_reset_texter.rb:

class PasswordResetTexter < Textris::Base
  default :from => "Our Team"

  def reset_password(user)
    @user = user

    text :to => @user.phone
  end
end

And a template at app/views/password_reset_texter/reset_password.text.erb:

Password reset for <%= @user.name %> was requested. 

In order to reset password go to <%= edit_password_reset_url(:token => @user.password_reset_token) %>

The template is kept short on purpose. You can use newlines to enhance template readibility - they'll be cut out anyway in order to form proper SMS string.

At this point, you can invoke password reset on user without e-mail in development and see SMS event in logs. You can also extend your test suite with scenario for such case using the deliveries textris attribute.

Step 3: In the background

We would be done, but the whimsical client wanted it fast and mailinated. So let's take care of the sidekiq delay first:

# before
PasswordResetTexter.reset_password(user).deliver
# after
PasswordResetTexter.delay.reset_password(user)

Note that passing user as sidekiq job argument is possible thanks to custom ActiveRecord serialization built into textris. UPDATE: Rails 4.2 update introduced ActiveJob and Global ID that provide standarized way of job delaying and ActiveRecord object passing to workers. Next release of textris is expected to have these features integrated.

Step 4: With an e-mail backup

E-mail delivery is preconfigured to send to Mailinator address based on target phone number. This won't work for us if we want to pass single e-mail to client and be done with it, so let's change the e-mail target template in production.rb:

config.textris_mail_to_template = 'dear-client-use-this-address@mailinator.com'

Now however we don't show the target phone number anywhere. Let's show it in e-mail subject:

config.textris_mail_subject_template = 'Password reset sent to %{to_phone:p}'

Remember to configure and enable Twilio as only mail delivery is on by default in production:

config.textris_delivery_method = [:twilio, :mail, :log]

Final note

That's it! Hope you liked the gem and I'd really like to see textris extended with additional delivery services - that's why there is a relevant chapter in Readme. See you on github.

Karol Słuszniak
Ruby Developer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

How to become a Ruby Certified Programmer Title image

How to become a Ruby Certified Programmer

14
.
11
.
2023
Michał Łęcicki
Ruby
Visuality
Vector Search in Ruby - Paweł Strzałkowski

Vector Search in Ruby

17
.
03
.
2024
Paweł Strzałkowski
ChatGPT
Embeddings
Postgresql
Ruby
Ruby on Rails
LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

17
.
03
.
2024
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

14
.
11
.
2023
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

02
.
10
.
2024
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

14
.
11
.
2023
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

14
.
11
.
2023
Cezary Kłos
Backend
Ruby on Rails

Smooth Concurrent Updates with Hotwire Stimulus

14
.
11
.
2023
Michał Łęcicki
Hotwire
Ruby on Rails
Software
Tutorial

Freelancers vs Software house

02
.
10
.
2024
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
Ruby on Rails

N+1 in Ruby on Rails

14
.
11
.
2023
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend

Turbo Streams and current user

29
.
11
.
2023
Mateusz Bilski
Hotwire
Ruby on Rails
Backend
Frontend

Showing progress of background jobs with Turbo

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Ruby
Hotwire
Frontend
Backend

Table partitioning in Rails, part 1 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend
Ruby on Rails

Table partitioning types - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend

Indexing partitioned table - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
SQL Views in Ruby on Rails

SQL views in Ruby on Rails

14
.
11
.
2023
Jan Grela
Backend
Ruby
Ruby on Rails
Postgresql
Design your bathroom in React

Design your bathroom in React

14
.
11
.
2023
Bartosz Bazański
Frontend
React
Lazy Attributes in Ruby - Krzysztof Wawer

Lazy attributes in Ruby

14
.
11
.
2023
Krzysztof Wawer
Ruby
Software

Exporting CSV files using COPY - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Ruby
Ruby on Rails
Michał Łęcicki - From Celluloid to Concurrent Ruby

From Celluloid to Concurrent Ruby: Practical Examples Of Multithreading Calls

14
.
11
.
2023
Michał Łęcicki
Backend
Ruby
Ruby on Rails
Software