Slide 1

Slide 1 text

Cody Norman Attraction Mailbox Why I Love Action Mailbox ❤ Boulder Ruby ❄ March 13th 2024

Slide 2

Slide 2 text

I really ❤ email

Slide 3

Slide 3 text

I’m in love with the idea of email I really ❤ email

Slide 4

Slide 4 text

Reason #1 Your users are probably using their email exponentially more than your app.

Slide 5

Slide 5 text

Introduction What we’ll be covering • What is Action Mailbox? • How to get started. • Sending inbound emails to your application. • How inbound emails are processed. • Deploying and running on production.

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

https://actionmailbox:[email protected]/rails/action_mailbox/postmark/inbound_emails

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Reason #2 Getting started is easy

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Running the Generator Run built in generator $ bin/rails action_mailbox:install $ bin/rails db:migrate

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Getting Started ApplicationMailbox # app/mailboxes/application_mailbox.rb class ApplicationMailbox < ActionMailbox::Base # routing /something/i => :somewhere end

Slide 20

Slide 20 text

Getting Started Email Routing Tips # app/mailboxes/application_mailbox.rb class ApplicationMailbox < ActionMailbox::Base routing all: :support # routing(/support\./i => :support) end

Slide 21

Slide 21 text

Getting Started Generating your fi rst Mailbox bin/rails generate mailbox support class SupportMailbox < ApplicationMailbox def process # email processing logic end end

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Rails Conductor Sending Inbound Emails Two options for sending inbound email: • By form • Send Email by Raw Source

Slide 24

Slide 24 text

Reason #3 Source Code is approachable and intuitive.

Slide 25

Slide 25 text

ActionMailbox::InboundEmail What get’s 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

Slide 26

Slide 26 text

ActionMailbox::InboundEmail What get’s created include Incineratable, MessageId, Routable

Slide 27

Slide 27 text

ActionMailbox::InboundEmail What get’s created has_one_attached :raw_email, service: ActionMailbox.storage_service

Slide 28

Slide 28 text

ActionMailbox::InboundEmail What get’s created 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

Slide 29

Slide 29 text

ActionMailbox::InboundEmail inbound_email.raw_email => # inbound_email.source => "Date: Mon, 11 Mar 2024 11:56:41..." inbound_email.mail.class => Mail::Message inbound_email.mail => #

Slide 30

Slide 30 text

ActionMailbox::InboundEmail What Get’s Created Ruby Mail Object mail = Mail.new do from '[email protected]' to '[email protected]' subject 'This is a test email' body File.read('body.txt') end mail.to_s #=> "From: [email protected]\r\nTo: you@…

Slide 31

Slide 31 text

Reason #4 Processing an email is ‘easy'

Slide 32

Slide 32 text

Mailboxes Processing email messages • Where the magic happens 🪄 • process method • before_processing method and bounce_with • Ruby Mail object • Parsing Attachments

Slide 33

Slide 33 text

Mailboxes Processing Emails There are also some lifecycle_hooks around the process method before_processing, after_processing, around_processing

Slide 34

Slide 34 text

Mailboxes Processing Emails # Enqueues the given +message+ for delivery and changes the inbound email's status to +:bounced+. def bounce_with(message) inbound_email.bounced! message.deliver_later end # Immediately sends the given +message+ and changes the inbound email's status to +:bounced+. def bounce_now_with(message) inbound_email.bounced! message.deliver_now end

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Mailboxes Process Method class LegacyDocumentMailbox < ApplicationMailbox def process create_legacy_document end def create_legacy_document legacy_document = LegacyDocument.new( name: mail.subject, email: mail.from.first, ) # imported_document is the attachment name on LegacyDocument legacy_document.imported_document.attach( io: StringIO.new(mail.attachments.first.body.decoded), filename: mail.attachments.first.filename ) legacy_document.save! end end

Slide 37

Slide 37 text

Reason #5 Deploying to production is simple

Slide 38

Slide 38 text

Deploying to Production Potential Pitfalls • Default URL isn’t going to 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 can save a lot of headaches.

Slide 39

Slide 39 text

Default Ingress Options • Exim • Mailgun • Mandrill • Post fi x • Postmark (we’ll cover this one) • Qmail • SendGrid

Slide 40

Slide 40 text

Deploying Con fi gure Ingress # config/environments/production.rb config.action_mailbox.ingress = :postmark # generate secure password for webhook URL SecureRandom.alphanumeric => "12312ddfgdfg"

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

https://actionmailbox:[email protected]/rails/action_mailbox/postmark/inbound_emails

Slide 43

Slide 43 text

Deploying Checking the Webhook URL

Slide 44

Slide 44 text

Deploying Update DNS records

Slide 45

Slide 45 text

I hope you hate email less and love Action mailbox a little more. THANK YOU!!!

Slide 46

Slide 46 text

Cody Norman Independent Ruby on Rails Consultant codynorman.com @cnorm35