Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Attraction Mailbox - Why I Love Action Mailbox

Cody Norman
May 09, 2024
270

Attraction Mailbox - Why I Love Action Mailbox

Email is a powerful and flexible way to extend the capabilities of your Rails application. It’s a familiar and low friction way for users to interact with your app.

As much as you may want your users to access your app, they may not need to. Email is a great example of focusing on the problem at hand instead of an over-complicated solution.

We’ll take a deeper look at Action Mailbox and how to route and process your inbound emails.

Cody Norman

May 09, 2024
Tweet

Transcript

  1. Introduction What we’ll be covering • What is Action Mailbox?

    • Practical Use Cases. • Sending inbound emails to your application. • How inbound emails are processed. • Deploying and running on production.
  2. Action Mailbox Background What is it? Action Mailbox routes incoming

    emails to controller-like mailboxes for processing in Rails. The inbound emails are turned into `InboundEmail` records using Active Record and feature life cycle tracking These inbound emails are routed asynchronously using Active Job to one or several dedicated mailboxes, which are capable of interacting directly with the rest of your domain model.
  3. Action Mailbox Examples Practical Examples • Create Active Storage attachments

    from attached fi les • Send SMS messages with inbound emails • Email a question to AI • Collecting and sorting responses
  4. Action Mailbox Examples Generate PDF from email attachment class LegacyDocumentMailbox

    < ApplicationMailbox def process legacy_document = LegacyDocument.new( name: mail.subject, email: mail.from.first, ) legacy_document.imported_document.attach( io: StringIO.new(mail.attachments.first.body.decoded), filename: mail.attachments.first.filename ) legacy_document.save! end end
  5. Action Mailbox Examples Email to SMS class SmsMailbox < ApplicationMailbox

    require 'twilio-ruby' def process @client = Twilio::REST::Client.new from_phone_number = TWILIO_FROM_NUMBER to_phone_number = mail.subject message = mail.decoded @client.messages.create( from: from_phone_number, to: to_phone_number, body: message ) end end
  6. Action Mailbox Examples Email with AI class OpenAiMailbox < ApplicationMailbox

    def process client = OpenAI::Client.new message = mail.decoded response = client.chat( parameters: { model: "gpt-3.5-turbo", messages: [{ role: "user", content: message}], temperature: 0.7, }) ai_reply = response.dig('choices', 0, 'message', 'content') to_email = mail.from.first original_question = message OpenAiMailer.with(email: to_email, response: ai_reply, original_question: original_question).response.deliver end end
  7. Action Mailbox Examples Automating Jellybean Guessing Contest class JellybeanCounterMailbox <

    ApplicationMailbox JELLYBEAN_COUNT = 123 before_processing :ensure_first_guess def process Guess.create(created_by: mail.from.first, count: mail.decoded.to_i) end def ensure_first_guess # reply with email if they've already guessed. existing_guess = Guess.find_by(email: mail.from.first) # MAKE SURE THIS SKIPS PROCESSING if existing_guess JellybeanCounterMailer.with(email: mail.from.first).existing_guess.deliver end end end
  8. Getting Started • Run generators to install • ApplicationMailbox routes

    the email to speci fi c mailbox • Routing inbound emails • Sending inbound emails to your development environment
  9. Getting Started What’s added bin/rails action_mailbox:install Copying application_mailbox.rb to app/mailboxes

    create app/mailboxes/application_mailbox.rb rails railties:install:migrations FROM=active_storage,action_mailbox Copied migration 20240123213529_create_action_mailbox_tables.action_mailbox.rb from action_mailbox
  10. Getting Started Generated Migrations # This migration comes from action_mailbox

    (originally 20180917164000) class CreateActionMailboxTables < ActiveRecord::Migration[6.0] def change create_table :action_mailbox_inbound_emails do |t| t.integer :status, default: 0, null: false t.string :message_id, null: false t.string :message_checksum, null: false t.timestamps t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true end end end
  11. Getting Started Generating your fi rst Mailbox bin/rails generate mailbox

    support class SupportMailbox < ApplicationMailbox def process # email processing logic end end
  12. ActionMailbox::InboundEmail What gets created class InboundEmail < Record self.table_name =

    "action_mailbox_inbound_emails" include Incineratable, MessageId, Routable has_one_attached :raw_email, service: ActionMailbox.storage_service enum status: %i[ pending processing delivered failed bounced ] def mail @mail ||= Mail.from_source(source) end def source @source ||= raw_email.download end def processed? delivered? || failed? || bounced? end end end
  13. Getting Started Email Routing Tips # app/mailboxes/application_mailbox.rb class ApplicationMailbox <

    ActionMailbox::Base routing all: :support # routing(/support\./i => :support) end
  14. Mailboxes Processing email messages • Where the magic happens 🪄

    • Calls the process method • Lifecycle hooks like before_processing • Handy methods like bounce_with
  15. Mailboxes Processing email messages class LegacyDocumentMailbox < ApplicationMailbox def process

    legacy_document = LegacyDocument.new( name: mail.subject, email: mail.from.first, ) legacy_document.imported_document.attach( io: StringIO.new(mail.attachments.first.body.decoded), filename: mail.attachments.first.filename ) legacy_document.save! end end
  16. class SupportMailbox < ApplicationMailbox before_processing :ensure_user ... def ensure_user @user

    = User.find_by(email: from_email) unless @user bounce_with Mailer.post_not_found(mail) end end end
  17. Deploying to Production • Exim • Mailgun • Mandrill •

    Post fi x • PostmarK • Qmail • SendGrid Default Ingress options
  18. Deploying to Production Potential Pitfalls • ActiveStorage URL options won’t

    be set by default (that happens in the Application Controller). You have to set this the same way you set it for your emails. • You probably want to set up an ActiveStorage service besides ‘disk’. That can cause some issues. • Routing all your inbound email through a subdomain like inbound.yourapp.com can save a lot of headaches.
  19. Action Mailbox “Examples” Cat Facts on Demand class CatFactsMailbox <

    ApplicationMailbox def process CatFactsMailer.with(email: mail.from.first, cat_fact: cat_fact).cat_fact.deliver end def cat_fact @cat_fact ||= HTTParty.get("https://catfact.ninja/ fact").dig("fact") end end
  20. Action Mailbox “Examples” Mail yourself a Postcard Reminder class PostcardMailbox

    < ApplicationMailbox def process postcardApi = Lob::PostcardsApi.new($lob_config) postcardCreate = Lob::PostcardEditable.new({ description: "Demo Postcard job", from: "adr_210a8d4b0b76d77b", use_type: "operational", front: "https://FAKE_BUCKET.s3.amazonaws.com/rails_conf_postcard_v2.png", back: "<html style='padding: 1in; font-size: 20;'><p>{{ message }}</p></html>", to: "adr_123123123123123123", merge_variables: { message: mail.body.decoded, }, }); begin createdPostcard = postcardApi.create(postcardCreate) rescue => err p err.message end end end
  21. Action Mailbox “Examples” Start your vehicle with an Email class

    TruckMailbox < ApplicationMailbox def process HTTParty.post(TRUCK_API_URL, body: start_request_body, headers: auth_headers) end end
  22. Cody Norman Independent Ruby on Rails Consultant codynorman.com @cnorm35 Action

    Mailbox Pro Course Special Pre-Sale O ff er Coupon Code - ATTRACTIONMAILBOX Valid until 5/18