Download as pdf or txt
Download as pdf or txt
You are on page 1of 46

Attraction Mailbox

Why I Love Action Mailbox ❤

Philly.rb - March 12th 2024


Cody Norman
I really ❤ email
I really ❤ email

I’m in love with the idea of email


Reason #1

Your users are probably using


their email exponentially more
than your app.
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.
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.
https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails
Reason #2

Getting started is easy


Getting Started

• Run generators to install


• ApplicationMailbox routes the email to speci c mailbox
• Routing inbound emails
• Sending inbound emails to your development environment

fi
Running the Generator
Run built in generator

$ bin/rails action_mailbox:install
$ bin/rails db:migrate
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
Getting Started
ApplicationMailbox

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

# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
routing all: :support
# routing(/support\./i => :support)
end
Getting Started
Generating your rst Mailbox

bin/rails generate mailbox support

class SupportMailbox < ApplicationMailbox


def process
# email processing logic
end
end
fi
Rails Conductor
Sending inbound emails in Development

https://localhost:3000/rails/conductor/action_mailbox/inbound_emails
Rails Conductor
Sending Inbound Emails

Two options for sending inbound email:

• By form
• Send Email by Raw Source
Reason #3

Source Code is approachable


and intuitive.
Source Code
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
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
ActionMailbox::InboundEmail
inbound_email.raw_email
=> #<ActiveStorage::Attached::One:0x0000000112e396e0
@name="raw_email",
@record=#<ActionMailbox::InboundEmail:0x0000000110523968>

inbound_email.source
=> "Date: Mon, 11 Mar 2024 11:56:41..."

inbound_email.mail.class
=> Mail::Message

inbound_email.mail
=> #<Mail::Message:41200>
ActionMailbox::InboundEmail
What Get’s Created

Ruby Mail Object

mail = Mail.new do
from 'mikel@test.lindsaar.net'
to 'you@test.lindsaar.net'
subject 'This is a test email'
body File.read('body.txt')
end

mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@…


Reason #4

Processing an email is ‘easy'


Mailboxes
Processing email messages

• Where the magic happens 🪄


• process method
• before_processing method and bounce_with
• Ruby Mail object
• Parsing Attachments
Mailboxes
Processing Emails

There are also some lifecycle_hooks around the process method


before_processing
after_processing
around_processing

bounce_with(message) or bounce_now_with(message)
Mailboxes
Processing Emails

bounce_with(message)

bounce_now_with(message)
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
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
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
Reason #5

Deploying to production is
simple
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.
Default Ingress Options

• Exim
• Mailgun
• Mandrill
• Post x
• Postmark (we’ll cover this one)
• Qmail
• SendGrid
fi
Deploying
Con gure Ingress

# config/environments/production.rb
config.action_mailbox.ingress = :postmark

# generate secure password for webhook URL


SecureRandom.alphanumeric
=> "12312ddfgdfg"
fi
Deploying
Server Inbound Webhook URL format

https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails
Deploying
Checking the Webhook URL
Deploying
Update DNS records
THANK YOU!!!

I hope you hate email less and


love Action mailbox a little more.
Cody Norman
Independent Ruby on Rails Consultant

codynorman.com
@cnorm35

You might also like