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

Rocky_Mountian_Ruby_2024.pdf

Cody Norman
October 08, 2024
41

 Rocky_Mountian_Ruby_2024.pdf

Cody Norman

October 08, 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. • Tips for deploying and running in 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 • Information and Promotions for Events
  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 Special Event Promotions 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, Rocky Mountain Ruby 2024 (add code? Or at least the screenshots of the easter egg Maybe break that up into multiple slides/
  8. Getting Started • Run generators to install • Routing inbound

    emails • Implementing the parsing logic
  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 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 /food@/ => :food_options routing /coffee@/ => :coffee_options routing /winner-winner-lunch-or-dinner@/ => :gift_card_easter_egg routing /death-before-decaf@/ => :gift_card_easter_egg routing all: :promotions end
  14. Mailboxes Processing email messages • Where the magic happens 🪄

    • Calls the process method • Provides lifecycle hooks like before_processing • Handy methods like bounce_with
  15. 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
  16. Deploying to Production • Exim • Mailgun • Mandrill •

    Post fi x • PostmarK • Qmail • SendGrid Default Ingress options
  17. 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. 
 
 ([email protected])
  18. 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
  19. Action Mailbox “Examples” Age Guesser class GuessMyAgeMailbox < ApplicationMailbox def

    process response = HTTParty.get(agify_url, { query: { name: name } }) age = response["age"] GuessMyAgeMailer.with(guess: age, sender_email: mail.from.first, name: name).response.deliver_now end def name mail[:from].display_names.first.presence || mail.from.first.split("@").first 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/boston_ruby_postcard.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