<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><generator uri="https://bridgetownrb.com/" version="0.19.1">Bridgetown</generator><link href="https://codynorman.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://codynorman.com/" rel="alternate" type="text/html"/><updated>2025-02-12T09:32:03-05:00</updated><id>https://codynorman.com/feed.xml</id><title type="html">Cody Norman Personal Website</title><subtitle>Personal website for Cody Norman - Independent Ruby on Rails Consultant.</subtitle><author><name>cody</name></author><entry><title type="html">Create PDF attachments from email with Action Mailbox</title><link href="https://codynorman.com/ruby/pdf_attachments_action_mailbox/" rel="alternate" type="text/html" title="Create PDF attachments from email with Action Mailbox"/><published>2024-07-14T17:54:46-04:00</published><updated>2024-07-14T17:54:46-04:00</updated><id>https://codynorman.com/ruby/pdf_attachments_action_mailbox</id><content type="html" xml:base="https://codynorman.com/ruby/pdf_attachments_action_mailbox/">&lt;p&gt;&lt;a href=&quot;https://github.com/cnorm35/PdfMailboxExampleApp&quot; target=&quot;_blank&quot;&gt;Source Code&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Today, I’m going to show you how to save a PDF attachment from an inbound email using Action Mailbox a Ruby on Rails application. I think this is a great example of a common feature that we can accomplish with email. This is going to allow your users to perform tasks without even having to open your app.&lt;/p&gt; &lt;p&gt;I’ll cover how to handle edge cases like checking for a valid user and handling missing or incorrect attachments.&lt;/p&gt; &lt;p&gt;This post will cover creating a new Rails app and adding Devise for a User model to check against the sender of the email. Since this will be a new app, I’ll also be installing Action Mailbox. Everything after that initial setup should apply to an existing app you’re trying to add this feature to.&lt;/p&gt; &lt;p&gt;If you’ll be adding this feature to an existing app, you can skip over most of that setup.&lt;/p&gt; &lt;p&gt;There’s also a video version of this post with some more detail.&lt;/p&gt; &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/dDrNOpNvKEI?si=sxcyXjSzv_zlk6VS&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt; &lt;h3 id=&quot;create-your-rails-app-with-devise&quot;&gt;Create your Rails app with Devise&lt;/h3&gt; &lt;p&gt;Let’s start by creating a new Rails app and installing Devise for the User model.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails new pdf_attachments_action_mailbox &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Once the new app is created, move into the directory that was created and open the project in your code editor to add the Devise gem.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Gemfile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;devise&apos;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Once Devise is added to the &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; you can &lt;code class=&quot;highlighter-rouge&quot;&gt;bundle install&lt;/code&gt; and run the Devise install generators.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails generate devise:install &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;After running the Devise install generator, you’ll see some output that mentions some in your code editor changes to the views and routes. To keep things simple, I’ll be skipping the User views and routes to keep things focused on Action Mailbox.&lt;/p&gt; &lt;p&gt;Since we’ll be sending &lt;em&gt;outbound&lt;/em&gt; emails to the sender to update them on the status on the import, there is one change we need to add from Devise.&lt;/p&gt; &lt;p&gt;Open the &lt;code class=&quot;highlighter-rouge&quot;&gt;config/environments/development.rb&lt;/code&gt; file and add the following line to the configuration:&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action_mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_url_options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;host: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;localhost&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;port: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3000&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;With Devise installed, we can now generate the User model.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails generate devise User &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails db:migrate &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Since this app will be BYOV (Bring Your Own Views - I realize spelling it out doesn’t really save time but I think it’s neat), we can create a &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; in our Rails console to test with.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;user@actionmailbox.pro&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;password: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;youvegotmail&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Next, we’ll create a simple model to store the PDF attachments using Active Storage.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails g model ImportDocument user:references name:string &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This creates a new model &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; that belongs to a &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; and has a &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; value. &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; will store the subject of the email to make it easier to reference the import.&lt;/p&gt; &lt;h3 id=&quot;installing-action-mailbox&quot;&gt;Installing Action Mailbox&lt;/h3&gt; &lt;p&gt;If you haven’t already done so, you’ll also need to install Action Mailbox.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails action_mailbox:install &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;One final change to make before running the migrations is to add the &lt;code class=&quot;highlighter-rouge&quot;&gt;has_one_attached&lt;/code&gt; method to the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; model.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/import_document.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ImportDocument&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_one_attached&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pdf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now, running the migrations will create the tables for the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; model and the tables needed for Action Mailbox, which includes Active Storage.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails db:migrate &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This is all the setup we need to do to get started with the Action Mailbox side of things.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Sidenote:&lt;/strong&gt; I’ve found that keeping your mailboxes small and focused on a single task helps keep things organized. This mailbox will be responsible for saving the PDF attachment from the email. If you decide to add different attachment types to the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; model, you could create a new mailbox for each type.&lt;/p&gt; &lt;p&gt;To get started, we can create a new Mailbox to handle the inbound emails and save the PDF.&lt;/p&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails generate mailbox Pdf &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;routing-the-email&quot;&gt;Routing the email&lt;/h3&gt; &lt;p&gt;Before processing the inbound email, we first need to route the email to the correct mailbox.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/application_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# routing /pdf/i =&amp;gt; :pdf&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;routing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;all: :pdf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;I’ve included a commented-out line that gives a better idea of how you may want to route this email outside of your development environment. This example would route emails that contain &lt;code class=&quot;highlighter-rouge&quot;&gt;pdf&lt;/code&gt; in the to address of the inbound email. In other words, sending an email to &lt;code class=&quot;highlighter-rouge&quot;&gt;pdf@inbound.yousite.com&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;pdf-import@inbound,yoursite.com&lt;/code&gt; would route the email to the pdf mailbox.&lt;/p&gt; &lt;p&gt;When working on a new mailbox in development, I typically use the &lt;code class=&quot;highlighter-rouge&quot;&gt;all:&lt;/code&gt; option to route &lt;em&gt;any&lt;/em&gt; inbound email to the specified mailbox. This rules out any potential routing issues and lets you focus on the processing in the mailbox.&lt;/p&gt; &lt;p&gt;To confirm everything is working, we can send an email through the Rails Conductor and confirm it’s been delivered.&lt;/p&gt; &lt;p&gt;With your Rails server running, open the Rails Conductor by visiting&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;http://localhost:3000/rails/conductor/action_mailbox/inbound_emails&lt;/code&gt;&lt;/p&gt; &lt;p&gt;and click the ‘New inbound email by form’ option or head there directly by going to&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;http://localhost:3000/rails/conductor/action_mailbox/inbound_emails/new&lt;/code&gt;&lt;/p&gt; &lt;p&gt;Since we’re using the &lt;code class=&quot;highlighter-rouge&quot;&gt;all:&lt;/code&gt; routing option, we don’t have to worry about the correct address to send the email to. Just fill in the ‘to’ and ‘from’ fields and submit the form.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Action Mailbox Rails Conductor&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PdfRailsConductor.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;After submitting the form, you’ll land on the detail page for the &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; record that was created from the form. Refresh the page and you should see the status change from &lt;code class=&quot;highlighter-rouge&quot;&gt;processing&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;delivered&lt;/code&gt;. This means the email was successfully processed by the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailbox&lt;/code&gt; and everything is working as expected.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Action Mailbox Rails Conductor Detail&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/DeliveredRailsConductor.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;h3 id=&quot;re-routing-and-debugging&quot;&gt;Re-routing and Debugging&lt;/h3&gt; &lt;p&gt;If something went wrong and the status didn’t update to &lt;code class=&quot;highlighter-rouge&quot;&gt;delivered&lt;/code&gt;, the Rails conductor provides an easy button to re-route and deliver the email again.&lt;/p&gt; &lt;p&gt;I’m also going to add a &lt;code class=&quot;highlighter-rouge&quot;&gt;debugger&lt;/code&gt; statement using &lt;a href=&quot;https://github.com/ruby/debug&quot; target=&quot;_blank&quot;&gt;ruby debug&lt;/a&gt; to the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method of the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailbox&lt;/code&gt; to show how you can inspect the inbound email and attachments or investigate any issues.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PdfMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;debugger&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;After adding the &lt;code class=&quot;highlighter-rouge&quot;&gt;debugger&lt;/code&gt; statement, restart your Rails server and click the ‘Route again’ button on the Inbound Email detail page.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since this app is only running with &lt;code class=&quot;highlighter-rouge&quot;&gt;rails server&lt;/code&gt; and not using a Procfile, you’ll see the debugger open in the terminal where the server is running. If you’re using a Procfile and with something like &lt;a href=&quot;https://github.com/DarthSim/overmind&quot; target=&quot;_blank&quot;&gt;Overmind&lt;/a&gt;, you can connect to the debugger with &lt;code class=&quot;highlighter-rouge&quot;&gt;overmind connect web&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;overmind connect worker&lt;/code&gt; depending on your specific setup.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Action Mailbox debugger in mailbox&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/MailboxDebugger.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;In the above example, I’m interacting with the &lt;a href=&quot;https://github.com/mikel/mail&quot; target=&quot;_blank&quot;&gt;Ruby Mail&lt;/a&gt; gem to parse information from the inbound email.&lt;/p&gt; &lt;p&gt;Since we’re fully embracing the BYOV philosophy, you can also use the Rails console to accomplish the same task.&lt;/p&gt; &lt;p&gt;Inside the Rails console, you can get the last &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; record, update the status to &lt;code class=&quot;highlighter-rouge&quot;&gt;pending&lt;/code&gt;, and then route the email to the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailbox&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# In the Rails Console&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inbound_email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailbox&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;InboundEmail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inbound_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pending!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inbound_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;route&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If your &lt;code class=&quot;highlighter-rouge&quot;&gt;debugger&lt;/code&gt; statement is still in the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method of the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailbox&lt;/code&gt;, you’ll see the debugger open in the terminal where the Rails console is running.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Action Mailbox debugger in Rails console&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/ConsoleMailboxDebugger.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;h3 id=&quot;the-happy-path&quot;&gt;The Happy Path&lt;/h3&gt; &lt;p&gt;To get things working initially, I’ll be assuming the mailbox will receive an inbound email with all the requirements. For now, it’s just going to be attached to the first User and focus on creating the Active Storage attachment.&lt;/p&gt; &lt;p&gt;In the later steps, we’ll add some error handling to handle edge cases and reply with emails alerting the sender of the import status.&lt;/p&gt; &lt;p&gt;Before getting started, if you’ve not already done so, be sure to remove the &lt;code class=&quot;highlighter-rouge&quot;&gt;debugger&lt;/code&gt; statement from earlier.&lt;/p&gt; &lt;p&gt;Inside the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method of the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailbox&lt;/code&gt;, we start by initializing a new &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; record and setting the &lt;code class=&quot;highlighter-rouge&quot;&gt;user_id&lt;/code&gt; to the id of the first User record.&lt;/p&gt; &lt;p&gt;For this to work, you’ll need to have a User record in your database (snippet for that above). After setting the &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt;, we’ll set the &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; to the subject of the email.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImportDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;After initializing the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt;, we’ll attach the PDF file from the inbound email to the &lt;code class=&quot;highlighter-rouge&quot;&gt;pdf&lt;/code&gt; Active Storage attachment.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImportDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This code is also assuming your inbound email has a PDF attachment. We’ll worry about the file type in a later step.&lt;/p&gt; &lt;p&gt;The final step in the happy path is to save the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; record.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImportDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you submit an email through the Rails Conductor with all the required fields, including the PDF attachment, that should create a new &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; record with the PDF attachment from the inbound email.&lt;/p&gt; &lt;p&gt;To confirm the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; was created with the correct attachment, you can check for the attachment in the Rails console.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImportDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attached?&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If the email is showing as delivered and &lt;code class=&quot;highlighter-rouge&quot;&gt;import_document.pdf.attached?&lt;/code&gt; returns &lt;code class=&quot;highlighter-rouge&quot;&gt;true&lt;/code&gt;, everything should have been saved correctly.&lt;/p&gt; &lt;h3 id=&quot;checking-for-an-existing-user&quot;&gt;Checking for an existing User&lt;/h3&gt; &lt;p&gt;Anything connected to the internet is only going to last a few days before it starts receiving inbound spam. It’s probably a safe assumption that you may not want to try to create a new Import Document from some spammers email signature.&lt;/p&gt; &lt;p&gt;To prevent this, we can check if a User record exists for the sender of the email. If the User is found, we move on to processing the email. If the User is not found, we’ll mark the email as &lt;code class=&quot;highlighter-rouge&quot;&gt;bounced&lt;/code&gt; and send a reply to the sender.&lt;/p&gt; &lt;p&gt;Action Mailbox provides a simple way to handle this. We’ll use the &lt;code class=&quot;highlighter-rouge&quot;&gt;before_processing&lt;/code&gt; callback to check for the User and the &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; method to mark the email as bounced. The &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; method takes a mailer as an argument and uses that as the email to alert the sender of the bounce.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PdfMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before_processing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ensure_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImportDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_user&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# bounce_with &lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Inside the &lt;code class=&quot;highlighter-rouge&quot;&gt;ensure_user&lt;/code&gt; method, we’ll check if a User record exists for the email address of the sender. If a User is not found, we’ll use the &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; method to mark the email as bounced and send a reply.&lt;/p&gt; &lt;p&gt;Since the &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; method requires a mailer, you’ll need to generate a new mailer to respond with. There will be more mailers used in the later steps so we can create them all at once.&lt;/p&gt; &lt;h3 id=&quot;generate-mailers&quot;&gt;Generate Mailers&lt;/h3&gt; &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails generate mailer Pdf user_not_found missing_attachment bad_attachment_format import_complete &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This will generate a new mailer with the methods &lt;code class=&quot;highlighter-rouge&quot;&gt;user_not_found&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;missing_attachment&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;bad_attachment_format&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;import_complete&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;I won’t be updating any of the mailer views, but there are some changes to make to each one of the methods in the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailer&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailers/pdf_mailer.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;user_not_found&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@greeting&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;User Not Found&quot;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@to&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@to&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Updating &lt;code class=&quot;highlighter-rouge&quot;&gt;@greeting&lt;/code&gt; is optional, but you &lt;em&gt;will&lt;/em&gt; need to update the value passed to &lt;code class=&quot;highlighter-rouge&quot;&gt;mail to:&lt;/code&gt; to use the &lt;code class=&quot;highlighter-rouge&quot;&gt;to&lt;/code&gt; value from the &lt;code class=&quot;highlighter-rouge&quot;&gt;params&lt;/code&gt; hash. This is how we’re going to pass the email address of the sender to the mailer.&lt;/p&gt; &lt;p&gt;Bonus Step: Install &lt;code class=&quot;highlighter-rouge&quot;&gt;letter_opener_web&lt;/code&gt; to view the &lt;em&gt;outbound&lt;/em&gt; emails easily in your browser. There’s more detailed information on how to set that up in the included video.&lt;/p&gt; &lt;!-- #### Wrapping up the User Not Found check --&gt; &lt;p&gt;With our mailers updated, we can now finishing implementing the &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; method.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_user&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bounce_with&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PdfMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user_not_found&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;In this example, &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailer&lt;/code&gt; is the mailer we generated earlier. The &lt;code class=&quot;highlighter-rouge&quot;&gt;with&lt;/code&gt; method is used to pass params to the mailer.&lt;/p&gt; &lt;p&gt;We’re using &lt;code class=&quot;highlighter-rouge&quot;&gt;mail.from.first&lt;/code&gt; to get the first email address (the same one we used to find the User record) and passing that as the &lt;code class=&quot;highlighter-rouge&quot;&gt;to&lt;/code&gt; value to the mailer.&lt;/p&gt; &lt;p&gt;The last part, &lt;code class=&quot;highlighter-rouge&quot;&gt;user_not_found&lt;/code&gt;, is the mailer method we’re going to be sending.&lt;/p&gt; &lt;p&gt;If you re-start your Rails server and send an email from an &lt;em&gt;email address that does not match a User in your local database&lt;/em&gt;, you should see the email marked as bounced and a reply sent to the sender.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Bounced Email in Rails Conductor&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/RailsConductorBounced.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;Now, your Action Mailbox app is a bit more secure from the scary internet and won’t be trying to process documents and attachments all willy-nilly.&lt;/p&gt; &lt;p&gt;Another one of the assumptions from the previous step is that the inbound email has at least one attachment.&lt;/p&gt; &lt;p&gt;In the next step, I show how you can add a check for an attachment and reply to the sender if one is missing.&lt;/p&gt; &lt;h3 id=&quot;checking-for-an-attachment&quot;&gt;Checking for an attachment&lt;/h3&gt; &lt;p&gt;To check for an attachment before attempting to process the email, we’ll be using another &lt;code class=&quot;highlighter-rouge&quot;&gt;before_processing&lt;/code&gt; callback. This time, we’ll be checking if the email has at least one attachment.&lt;/p&gt; &lt;p&gt;This portion won’t be checking that it’s a PDF file, just that there is an attachment. Letting the sender know they may have forgotten to attach the file provides a better experience than getting a more generic error.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before_processing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ensure_attachment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_attachment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# repy to sender&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now, we need to send a reply to the sender &lt;em&gt;and&lt;/em&gt; make sure we don’t attempt to process the email. The main difference between this and the &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; method is that the &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; updates the &lt;code class=&quot;highlighter-rouge&quot;&gt;status&lt;/code&gt; of the &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; record to &lt;code class=&quot;highlighter-rouge&quot;&gt;bounced&lt;/code&gt; instead of something like &lt;code class=&quot;highlighter-rouge&quot;&gt;failed&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;delivered&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;In this scenario, the sender &lt;em&gt;was a valid User&lt;/em&gt; sending to the &lt;em&gt;correct email address&lt;/em&gt; so I don’t think &lt;code class=&quot;highlighter-rouge&quot;&gt;bounced&lt;/code&gt; is a good representation of the inbound email’s status.&lt;/p&gt; &lt;p&gt;If you’ve followed the steps above for generating the mailers, you should have a &lt;code class=&quot;highlighter-rouge&quot;&gt;missing_attachment&lt;/code&gt; method in the &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailer&lt;/code&gt; that we can use to reply to the sender.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_attachment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PdfMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;missing_attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_later&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This mailer is setup and called the same way as the one passed to &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; except for adding &lt;code class=&quot;highlighter-rouge&quot;&gt;deliver_later&lt;/code&gt; to send the email asynchronously.&lt;/p&gt; &lt;p&gt;Our final step is to call &lt;code class=&quot;highlighter-rouge&quot;&gt;:abort&lt;/code&gt; to stop the email from being processed.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_attachment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PdfMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;missing_attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_later&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:abort&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This ensures that we exit and don’t attempt to process the email if there are no attachments.&lt;/p&gt; &lt;p&gt;To test this out, you can send a test email through the Rails Conductor and confirm the reply email was sent and the status was updated to &lt;code class=&quot;highlighter-rouge&quot;&gt;failed&lt;/code&gt;.&lt;/p&gt; &lt;h3 id=&quot;checking-the-attachment-type&quot;&gt;Checking the attachment type&lt;/h3&gt; &lt;p&gt;Now we’ve confirmed the inbound email has at least one attachment, another additional check we can add is to confirm the attachment is a PDF file.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before_processing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ensure_attachment_format&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_attachment_format&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/pdf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# send email and exit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;For this approach, we’ll be using another &lt;code class=&quot;highlighter-rouge&quot;&gt;before_processing&lt;/code&gt; callback to check the content type of the attachment.&lt;/p&gt; &lt;p&gt;Note: Outside of the scope of a tutorial, you may want to combine or refactor some of the callbacks to suit your needs. I wanted to keep them separate to show easy ways we can add incremental improvements and refinements to the mailbox.&lt;/p&gt; &lt;p&gt;Inside the &lt;code class=&quot;highlighter-rouge&quot;&gt;ensure_attachment_format&lt;/code&gt; method, we’ll check if the content type of the first attachment starts with &lt;code class=&quot;highlighter-rouge&quot;&gt;application/pdf&lt;/code&gt;. If it doesn’t, we’ll send a reply and exit before processing.&lt;/p&gt; &lt;!-- Now you know what to check for, we can re-use the same approach we used for the `ensure_attachment` callback to send a reply to the sender and exit the processing of the email. --&gt; &lt;p&gt;Since you’re an old pro at these types of checks and callbacks by now, we can re-use the same approach used in the &lt;code class=&quot;highlighter-rouge&quot;&gt;ensure_attachment&lt;/code&gt; callback to send a reply to the sender and exit.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_attachment_format&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/pdf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PdfMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bad_attachment_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_later&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:abort&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;reply-on-success&quot;&gt;Reply on success&lt;/h3&gt; &lt;p&gt;We have a couple of different emails alerting the sender of issues, but what about when things go right?&lt;/p&gt; &lt;p&gt;It’d be nice to let the sender know when the import has been completed.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/pdf_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImportDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PdfMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;import_complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_later&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now, if the &lt;code class=&quot;highlighter-rouge&quot;&gt;ImportDocument&lt;/code&gt; is saved successfully, we’ll send an email to the sender with the &lt;code class=&quot;highlighter-rouge&quot;&gt;import_complete&lt;/code&gt; mailer method.&lt;/p&gt; &lt;p&gt;Like the previous step, you can confirm this is working by sending some test emails in the Rails Conductor and confirm the new record was created and the correct email was sent.&lt;/p&gt; &lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt; &lt;p&gt;Inside our &lt;code class=&quot;highlighter-rouge&quot;&gt;PdfMailbox&lt;/code&gt;, we’ve moved the task of uploading and processing files to email. We’ve also added some error handling and UX improvements to let the sender know if something went wrong or when their import is complete.&lt;/p&gt; &lt;p&gt;As an additional perk, creating and processing the attachments via email with Action Mailbox will run in the background whenever the &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; is processed. This is a great way to handle large files without tying up your web server.&lt;/p&gt; &lt;p&gt;The included video is a free preview from my upcoming course on Action Mailbox covering more of these advanced topics and edge cases. If you’d like to learn how to create more features like this one, the course is available for pre-sale - &lt;a href=&quot;https://store.codynorman.com/action-mailbox-pro&quot;&gt;Action Mailbox Pro&lt;/a&gt;.&lt;/p&gt; &lt;script async=&quot;&quot; data-uid=&quot;ec40bc76b7&quot; src=&quot;https://codynorman.ck.page/ec40bc76b7/index.js&quot;&gt;&lt;/script&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">Your users likely spend more time on their email than on your app. This is a huge opportunity to create tools and features that integrate with what they&apos;re already familiar with. In this post, I&apos;ll show you how to parse inbound email attachments and save them in your application using Action Mailbox and Active Storage.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Create a QR Code with a Logo</title><link href="https://codynorman.com/ruby/create_qr_code_with_logo/" rel="alternate" type="text/html" title="Create a QR Code with a Logo"/><published>2024-06-09T17:54:46-04:00</published><updated>2024-06-09T17:54:46-04:00</updated><id>https://codynorman.com/ruby/create_qr_code_with_logo</id><content type="html" xml:base="https://codynorman.com/ruby/create_qr_code_with_logo/">&lt;p&gt;QR codes are a great way to share links or information easily with your users, unless you’re a restaurant, in which case you should really consider a paper menu please.&lt;/p&gt; &lt;p&gt;In all seriousness, it’s a great way to easily let people scan and access specific information.&lt;/p&gt; &lt;p&gt;To say QR codes are a dime a dozen would be an understatement. Creating a composite QR code with a logo is a great way to help your code stand out and add some additional branding and feel to your app.&lt;/p&gt; &lt;p&gt;Being able to generate a custom QR code overlaid with a logo, while still being scannable is pretty easy with Rails.&lt;/p&gt; &lt;p&gt;This post will walk through setting up a simple standalone QR with logo app.&lt;/p&gt; &lt;p&gt;We’ll be using MiniMagick and RQRCode to create a PNG of the qr code, then create a composite image with the logo.&lt;/p&gt; &lt;p&gt;If you’d like to see everything in action, I have a video that goes through things in a little more detail&lt;/p&gt; &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/i9EcMnrXuq8?si=UX6mhPoxk8GhV7wQ&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt; &lt;h3 id=&quot;getting-started&quot;&gt;Getting started&lt;/h3&gt; &lt;p&gt;The app is going to have a single model &lt;code class=&quot;highlighter-rouge&quot;&gt;QRCode&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;It will have a string field for the URL and 3 attached active storage images.&lt;/p&gt; &lt;p&gt;3 might be a bit overkill but this will store the logo uploaded by a user, the QR code without the logo and finally the combined QR code image with the logo.&lt;/p&gt; &lt;p&gt;Create a new Rails app with Tailwind by running the following command in your terminal.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rails new qr_code_generator &lt;span class=&quot;nt&quot;&gt;--css&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tailwind &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We’ll keep things simple by scaffolding the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; resources&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails generate scaffold QrCode url:string &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Since this is a simple app, we’ll set the root route to the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; index page.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;c1&quot;&gt;# config/routes.rb&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;qr_codes#index&quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h4 id=&quot;active-storage-and-image-processing&quot;&gt;Active Storage and Image Processing&lt;/h4&gt; &lt;p&gt;With a simple model for the QR code, the next step is to add the image processing gem and install active storage for the images that will be attached.&lt;/p&gt; &lt;p&gt;If you look in the &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;, you should already see the &lt;code class=&quot;highlighter-rouge&quot;&gt;image_processing&lt;/code&gt; gem in your &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# gem &quot;image_processing&quot;, &quot;~&amp;gt; 1.2&quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can uncomment the gem and run &lt;code class=&quot;highlighter-rouge&quot;&gt;bundle install&lt;/code&gt;&lt;/p&gt; &lt;p&gt;With the image processing gem installed and ready to go, the next step is to install Active Storage and run the corresponding migrations.&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails active_storage:install bin/rails db:migrate &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;With Active Storage installed, we can add our attachments we’ll be using.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/models/qr_code.rb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QrCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_one_attached&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:logo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_one_attached&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:original_image&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_one_attached&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:combined_image&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We’ll be generating the &lt;code class=&quot;highlighter-rouge&quot;&gt;combined_image&lt;/code&gt; and the &lt;code class=&quot;highlighter-rouge&quot;&gt;original_image&lt;/code&gt; based on inputs from the user, including a &lt;code class=&quot;highlighter-rouge&quot;&gt;logo&lt;/code&gt; file.&lt;/p&gt; &lt;p&gt;Updating the form and adding the file attributes to the params in the controller will allow us to start storing the uploaded logo and URL attached to the QR code.&lt;/p&gt; &lt;p&gt;Add the file input for the logo to the form.&lt;/p&gt; &lt;p&gt;I’m also limiting the file type to PNG for simplicity. Or QR codes are going to be PNGs, so it makes sense to limit the logo to the same format. Accepting PNGs with transparent backgrounds also makes it easier to overlay the logo on the QR code after some processing.&lt;/p&gt; &lt;p&gt;This is by no means a foolproof way to ensure the file is a PNG, but it’s a good start. If you find yourself needing more, you can look into validating the file type in the model before saving.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/views/qr_codes/_form.html.erb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-5&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= form.label :logo %&amp;gt; &amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;file_field&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:logo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;accept: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&amp;gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Add &lt;code class=&quot;highlighter-rouge&quot;&gt;logo&lt;/code&gt; to the strong params in the controller.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/controllers/qr_codes_controller.rb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;qr_code_params&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;permit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:logo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Adding the logo to the show page will give us an easy way to reference the image we uploaded.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/views/qr_codes/_qr_code.html.erb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;%= dom_id qr_code %&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-5&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;block font-medium mb-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= qr_code.url %&amp;gt; &amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image_tag&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combined_image&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt; &amp;lt;/p&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&amp;gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you haven’t started the server yet, now would be a good time to start everything up and make sure it’s working.&lt;/p&gt; &lt;p&gt;If things are good to go, you should land directly on the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; index page.&lt;/p&gt; &lt;p&gt;Clicking the New QR Code button, filling out the URL, and uploading a logo should create a new QR code record with the URL and logo attached.&lt;/p&gt; &lt;h3 id=&quot;qr-codes&quot;&gt;QR codes.&lt;/h3&gt; &lt;p&gt;We’ll be using the &lt;a href=&quot;https://github.com/whomwah/rqrcode&quot; target=&quot;_blank&quot;&gt;rqrcode&lt;/a&gt; gem to generate QR codes as a PNG and saving as an Active Storage attachment.&lt;/p&gt; &lt;p&gt;Add it to the Gemfile with::&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rqrcode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 2.0&quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;and install the gem with &lt;code class=&quot;highlighter-rouge&quot;&gt;bundle install&lt;/code&gt;&lt;/p&gt; &lt;p&gt;To keep a lot of the image processing logic out of the controller, we’ll create a method on the model to generate the QR code and save it as an attachment.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/models/qr_code.rb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_qr_code!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RQRCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;QRCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_qr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;as_png&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;size: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;bit_depth: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;border_modules: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;color_mode: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ChunkyPNG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;COLOR_GRAYSCALE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;color: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;black&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;file: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fill: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;original_qr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;original_qr.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;save!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This method will generate a QR code based on the URL and save it as an attachment.&lt;/p&gt; &lt;p&gt;Another advantage of having the QR code generation on the model is that we can test and diagnose issues in our Rails console without needing to go through the controller.&lt;/p&gt; &lt;p&gt;Running &lt;code class=&quot;highlighter-rouge&quot;&gt;qr_code.generate_qr_code!&lt;/code&gt; in the console (or anywhere) should generate a QR code from the URL and save it as an attachment.&lt;/p&gt; &lt;p&gt;Calling this method after we’ve successfully saved the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; record will create the QR code and save it as an attachment.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/controllers/qr_codes_controller.rb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;QrCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qr_code_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;respond_to&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generate_qr_code!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qr code was successfully created.&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :created&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;location: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;So we can make sure the image was created an attached, we can display it on the details page.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;%= image_tag qr_code.original_image %&amp;gt;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/views/qr_codes/_qr_code.html.erb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;%= dom_id qr_code %&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-5&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;block font-medium mb-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= qr_code.url %&amp;gt; &amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image_tag&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logo&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt; &amp;lt;%= image_tag qr_code.original_image %&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/p&amp;gt; &amp;lt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If everything is working as expected, you should see the QR code image on the show page for the QR code record. To check to make sure everything is working as expected, you can call &lt;code class=&quot;highlighter-rouge&quot;&gt;generate_qr_code!&lt;/code&gt; on an existing record in the Rails console or create a new record through the form. (Screenshot of the QR code on the show page - Use real URLs)&lt;/p&gt; &lt;h3 id=&quot;going-further&quot;&gt;Going Further&lt;/h3&gt; &lt;p&gt;Adding a bit of flair to things, we’re ready to start combining the uploaded logo, this will be the &lt;code class=&quot;highlighter-rouge&quot;&gt;combined_image&lt;/code&gt; attachment.&lt;/p&gt; &lt;p&gt;At this point, there are 2 attached image to the QR code model, &lt;code class=&quot;highlighter-rouge&quot;&gt;original_image&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;logo&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;We’re now going to use &lt;a href=&quot;https://github.com/minimagick/minimagick&quot; target=&quot;_blank&quot;&gt;MiniMagick&lt;/a&gt; to do a bit of formatting and create a composite image.&lt;/p&gt; &lt;p&gt;The combined logo will be using the same approach as the original QR code, meaning this code will be added to a method in the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; model.&lt;/p&gt; &lt;p&gt;To start, we create a &lt;code class=&quot;highlighter-rouge&quot;&gt;MiniMagick::Image&lt;/code&gt; for the &lt;code class=&quot;highlighter-rouge&quot;&gt;logo&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;original_image&lt;/code&gt; qr code.&lt;/p&gt; &lt;p&gt;Before attempting to combine the images, we’ll need to do some processing on the logo image. This will include resizing the image and adding a white background to make the logo stand out from the QR code.&lt;/p&gt; &lt;p&gt;Since we’re going to be doing some image processing, we’ll need to require the &lt;code class=&quot;highlighter-rouge&quot;&gt;mini_magick&lt;/code&gt; gem in the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; model.&lt;/p&gt; &lt;p&gt;First, we create a &lt;code class=&quot;highlighter-rouge&quot;&gt;MiniMagick::Image&lt;/code&gt; object for the logo image.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;logo_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Then we do the same for the QR code image.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;qr_code_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;original_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Next, we’ll resize the logo image and add a white background to help the logo stand out.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;updated_logo_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImageProcessing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logo_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resize_and_pad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;background: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;After creating the updated logo image, we can create a composite image with the QR code and logo.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;composite_qr_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;composite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;updated_logo_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compose&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Over&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gravity&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;center&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;colorspace&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sRGB&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;composite&lt;/code&gt; method is used to overlay the updated logo image on top of the QR code image. The &lt;code class=&quot;highlighter-rouge&quot;&gt;compose&lt;/code&gt; option is set to “Over” to overlay the logo on top of the QR code. The &lt;code class=&quot;highlighter-rouge&quot;&gt;gravity&lt;/code&gt; option is set to “center” to center the logo on the QR code. The &lt;code class=&quot;highlighter-rouge&quot;&gt;colorspace&lt;/code&gt; option is set to “sRGB” to ensure the colors are displayed correctly.&lt;/p&gt; &lt;p&gt;Finally, we’ll attach the composite image to the &lt;code class=&quot;highlighter-rouge&quot;&gt;combined_image&lt;/code&gt; attachment.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combined_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;combined_qr_code.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;composite_qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Then create the composite image and stores it as an Active Storage attachment.&lt;/p&gt; &lt;p&gt;Here’s what the final method looks like in the &lt;code class=&quot;highlighter-rouge&quot;&gt;QrCode&lt;/code&gt; model.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_combined_image!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logo_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;original_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated_logo_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImageProcessing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logo_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resize_and_pad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;background: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;composite_qr_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;composite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;updated_logo_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compose&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Over&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gravity&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;center&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;colorspace&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sRGB&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;combined_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;combined_qr_code.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;composite_qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;save!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you’d like to break this down into smaller steps, you can call each method individually in the Rails console. Here’s an example of how you could call each method individually in the Rails console to create the composite image and save it as an attachment.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# in the Rails console&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mini_magick&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;QrCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logo_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;original_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updated_logo_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ImageProcessing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MiniMagick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logo_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resize_and_pad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;background: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;composite_qr_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;composite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;updated_logo_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compose&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Over&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gravity&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;center&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;colorspace&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sRGB&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;combined_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;combined_qr_code.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;composite_qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;In the same way we called &lt;code class=&quot;highlighter-rouge&quot;&gt;generate_qr_code!&lt;/code&gt; after saving the record, we can call &lt;code class=&quot;highlighter-rouge&quot;&gt;generate_combined_image!&lt;/code&gt; to create the composite image with the logo from the Rails console on an existing record that has a logo and QR code attached.&lt;/p&gt; &lt;p&gt;The last step is to call the &lt;code class=&quot;highlighter-rouge&quot;&gt;generate_combined_image!&lt;/code&gt; method after the QR code has been generated and saved.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/controllers/qr_codes_controller.rb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;QrCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qr_code_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;respond_to&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generate_qr_code!&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generate_combined_image!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qr code was successfully created.&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :created&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;location: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We’ll also include this in the update action so that the combined image is updated whenever the QR code is updated.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;respond_to&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qr_code_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generate_qr_code!&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generate_combined_image!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qr code was successfully updated.&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;location: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:edit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Display the combined image on the show page for the QR code record to see the final result.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;%= image_tag qr_code.combined_image %&amp;gt;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;app/views/qr_codes/_qr_code.html.erb&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;%= dom_id qr_code %&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-5&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;block font-medium mb-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= qr_code.url %&amp;gt; &amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image_tag&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qr_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logo&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt; &amp;lt;%= image_tag qr_code.original_image %&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= image_tag qr_code.combined_image %&amp;gt; &amp;lt;/p&amp;gt; &amp;lt;/div&amp;gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If all goes as planned, you should be re-directed to the show page for the newly created QR Code record and see if all the logos are correct.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Combined QR Code with Logo&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/CombinedQRCodeExample.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;And with that, you’ve created a simple tool too add some style and flair to your QR codes.&lt;/p&gt; &lt;p&gt;This is a pretty simple example and there are a lot of ways to improve and expand on this feature. You could add more options for the QR code generation, add more image processing options, or even add a background color to the QR code for some additional flair.&lt;/p&gt; &lt;p&gt;You can view the full code for this example on &lt;a href=&quot;https://github.com/cnorm35/qr_code_generator_example&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">Creating a QR code with a logo is a great way to brand your QR codes and make them stand out. This post will go over how to create a QR code with a logo using Ruby on Rails.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">The Road To Rails Conf 2024</title><link href="https://codynorman.com/ruby/the_road_to_rails_conf/" rel="alternate" type="text/html" title="The Road To Rails Conf 2024"/><published>2024-06-01T17:54:46-04:00</published><updated>2024-06-01T17:54:46-04:00</updated><id>https://codynorman.com/ruby/the_road_to_rails_conf</id><content type="html" xml:base="https://codynorman.com/ruby/the_road_to_rails_conf/">&lt;h3 id=&quot;the-road-to-rails-conf&quot;&gt;The Road To Rails Conf&lt;/h3&gt; &lt;p&gt;Last month, I had a chance to give my first conference talk at what turned out to be the penultimate Rails Conf.&lt;/p&gt; &lt;p&gt;Sometime early last year, I decided I was going to start working on doing more public speaking and had the goal of being accepted to speak at a conference.&lt;/p&gt; &lt;p&gt;I put a lot of work into my first proposal but wasn’t selected. Honestly, that may have been for the best.&lt;/p&gt; &lt;p&gt;I don’t consider myself a sore loser. If I didn’t get picked or make the cut for something, it’s because I didn’t do a good enough job, plain and simple. That doesn’t mean I’m happy about it. I guess a good way to put this is I’m fine with losing, but I absolutely &lt;em&gt;hate&lt;/em&gt; the feeling, especially when it’s something I’ve set my mind to.&lt;/p&gt; &lt;p&gt;Getting that first stinging rejection let me know I had to step my game up. I started reading any article I could find on preparing a good conference proposal. Here are some of the resources that were a big help for me.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Conference Proposal Resources&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=l9JXH7JPjR4&quot; target=&quot;_blank&quot;&gt;Rails Conf 2013 How to Talk to Developers by Ben Orenstein&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://kevinjmurphy.com/posts/building-conference-talk-content/&quot; target=&quot;_blank&quot;&gt;Kevin Murphy - Building Conference Talk Content&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;http://www.sarahmei.com/blog/2014/04/07/what-your-conference-proposal-is-missing/?ref=rubycentral.org&quot; target=&quot;_blank&quot;&gt;Sarah Mei - What Your Conference Proposal Is Missing&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://noelrappin.com/blog/2014/01/conference-prompts-or-how-to-submit-proposals-and-influence-people/?ref=rubycentral.org&quot; target=&quot;_blank&quot;&gt;Noel Rappin - Conference Prompts: Or How To Submit Proposals and Influence People&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Another great resource I found was &lt;a href=&quot;https://speakerline.io/&quot; target=&quot;_blank&quot;&gt;Speakerline.io&lt;/a&gt; from &lt;a href=&quot;https://nadiaodunayo.com/&quot; target=&quot;_blank&quot;&gt;Nadia Odunayo&lt;/a&gt;&lt;/p&gt; &lt;h3 id=&quot;what-does-a-good-proposal-even-look-like&quot;&gt;What does a good proposal even look like?&lt;/h3&gt; &lt;p&gt;During Rafael Franca’s keynote at Rails Conf 2023 he mentioned one way he started getting more comfortable with contributing to Rails was that he started reading over &lt;em&gt;every single new commit&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;I loved this and decided to try something similar with conference proposals. I read over every single Ruby proposal at least once, some of them multiple times. I started to get a much better idea of what a good (and accepted) proposal looks like.&lt;/p&gt; &lt;p&gt;To round out my newfound knowledge of the proposal process, I also attended my first proposal coaching session courtesy of the fine folks at Ruby Central. It was a great chance to get feedback my ideas and proposals while also seeing what others were working on.&lt;/p&gt; &lt;p&gt;So…after reading dozens of proposals, getting some coaching on my specific proposals and submitting them to the next conference…&lt;/p&gt; &lt;p&gt;More rejections.&lt;/p&gt; &lt;h3 id=&quot;down-but-not-out&quot;&gt;Down but not out&lt;/h3&gt; &lt;p&gt;After a long year, I began wondering if I should put my talk ambitions on the back-burner and focus on other areas. I had some time to rest up and lick my wounds over the holidays and after a bit of recharging, decided I wasn’t done yet and was going to come out swinging in 2024.&lt;/p&gt; &lt;p&gt;Sometime around January or February of this year, I started compiling a list of all the ruby conferences I could potentially submit to along with the Call For Proposal dates.&lt;/p&gt; &lt;p&gt;I started writing some blog posts around some of my potential proposal topics. This was a big win because the blog content made it much easier to pull info an ideas from to include in the proposal. It also allowed me to get feedback on what parts people thought were the most valuable.&lt;/p&gt; &lt;p&gt;I decided to go all in on what I thought was my strongest proposal and reached out to Boulder Ruby to speak on that topic.&lt;/p&gt; &lt;p&gt;My thinking was…If I don’t get accepted, having a chance to get some more feedback would improve my chances in the future.&lt;/p&gt; &lt;p&gt;If I &lt;em&gt;did&lt;/em&gt; get accepted, I’d already be committed to having an early version done about 6 weeks before Rails Conf.&lt;/p&gt; &lt;p&gt;Hearing the theme of this year’s Rails Conf was ‘building with Rails’ I had some ideas for a talk on Action Mailbox that I thought would be a great fit. I thought this was my strongest proposal and represented my best chance of being accepted.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Thursday 2/29 @ 2:03 PM&lt;/strong&gt; (I saved the email) I finally got my breakthrough.&lt;/p&gt; &lt;p&gt;“Wait, What?!….”&lt;/p&gt; &lt;p&gt;I had to squint and re-read the email a couple times but there it was. The acceptance email I’ve been on the hunt for so long.&lt;/p&gt; &lt;h3 id=&quot;knocking-the-dust-off&quot;&gt;Knocking the dust off&lt;/h3&gt; &lt;p&gt;I took the rest of the week to take a bit of a victory lap and enjoy hitting such a big personal milestone.&lt;/p&gt; &lt;p&gt;A funny line I added to my planning notes that Saturday (March 2)&lt;/p&gt; &lt;p&gt;&lt;em&gt;“What’s the Rocky IV equivalent for preparing for a talk?”&lt;/em&gt;&lt;/p&gt; &lt;p&gt;For those unaware, &lt;a href=&quot;https://youtu.be/B_9FyTiq3SA?si=bRlLqS41zAfenxZK&quot;&gt;Rocky IV&lt;/a&gt; has the best training montage in existence. I will not be taking further questions.&lt;/p&gt; &lt;p&gt;I didn’t think I’d be able to realistically pull that off, but thinking about what that would look like, gave me some good ideas to think about for prep.&lt;/p&gt; &lt;p&gt;When Monday rolled around, it was time to get to work.&lt;/p&gt; &lt;p&gt;I immediately picked up a couple of Audio Books on speaking and how to craft an engaging and entertaining presentation. I also thought that audiobooks might have the advantage of allowing me to &lt;em&gt;hear&lt;/em&gt; the examples instead of just reading which I thought would be a big help.&lt;/p&gt; &lt;p&gt;Even though I had never given a conference talk, I thought that depending on books and recordings alone would be like reading a book on skydiving and thinking that’ll be enough to help me jump out of a plane.&lt;/p&gt; &lt;p&gt;In other words…practicing was where the majority of my focus should be. There’s absolutely no substitute for feeling more comfortable speaking in public than…you guessed it, speaking in public.&lt;/p&gt; &lt;h3 id=&quot;phillyrb&quot;&gt;Philly.rb&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;March 12 -&lt;/strong&gt; I was originally slated to speak at Philly.rb in April. I just so happened that their March meetup happened to be the day before I was already scheduled to speak at Boulder Ruby. After a bit of a scheduling mix up, I received a message asking me if I was still ready to present that night. Since I was almost done with the first version of my talk, I decided to just go for it. This would give me 2 chances to practice and get feedback with plenty of time for revisions and updates before the conference.&lt;/p&gt; &lt;p&gt;This was extra special for me since Philly.rb was the first meetup I ever spoke at and was the last meetup I spoke at in person back in 2016 before relocating to Colorado.&lt;/p&gt; &lt;p&gt;&lt;img alt=&quot;Person holding a laptop with a code editor open&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PhillyRb2016.jpg&quot; style=&quot;z-index: 10&quot; /&gt; (Philly.rb 2016)&lt;/p&gt; &lt;p&gt;I felt like it went really well and was also lucky enough to get some great feedback and speaker coaching from Kevin Murphy who was on the program committee this year. After going over some of the things he’d tweak or change, we started riffing on a couple of ‘Big Swing’ ideas. That really set me off on the path to think about how I could do some fun things to make this talk very ‘me’.&lt;/p&gt; &lt;h3 id=&quot;boulder-ruby&quot;&gt;Boulder Ruby&lt;/h3&gt; &lt;p&gt;&lt;strong&gt;March 13 -&lt;/strong&gt; That whole week I’d been tracking a potentially record-breaking blizzard that was due to hit us in Colorado right around the time I was supposed to speak at the Boulder Ruby meetup in person. My small mountain town is a little over an hour from Boulder under normal conditions and the last stretch requires me heading through a mountain road that’s pretty well known for people getting stranded on.&lt;/p&gt; &lt;p&gt;I don’t think I’ve ever prepped for heading to a meetup by throwing a sleeping bag and shovel into my car ahead of time but I suppose there’s a first time for everything. All indications were pointing to a rough drive home at a minimum with a reasonable possibility of being stranded and having to sleep in my car (this happens a lot in Colorado). I knew it would be risky but it also represented my only chance to speak in person to get some practice and feedback before Rails Conf.&lt;/p&gt; &lt;p&gt;I knew it would be a huge gamble, but if it paid off, I would have done 2 versions of my talk with tons of ideas for improvements and 6 weeks to go to Rails Conf.&lt;/p&gt; &lt;p&gt;With no exaggeration do I tell you I &lt;em&gt;barely&lt;/em&gt; made it home. We ended up with about 30 inches total with one area I drove through on my way home getting 36 inches in a 24-hour period.&lt;/p&gt; &lt;p&gt;I can’t thank the organizers for both meetups enough and incredibly grateful for having me.&lt;/p&gt; &lt;blockquote class=&quot;twitter-tweet&quot; data-media-max-width=&quot;560&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Here&amp;#39;s a quick clip from one of the worst sections last night. Luckily I was only a couple cars back from the plow, probably passed a dozen cars stuck by this point. &lt;a href=&quot;https://t.co/pe8vHXa8uq&quot;&gt;pic.twitter.com/pe8vHXa8uq&lt;/a&gt;&lt;/p&gt;&amp;mdash; Cody Norman (@cnorm35) &lt;a href=&quot;https://twitter.com/cnorm35/status/1768305308504367506?ref_src=twsrc%5Etfw&quot;&gt;March 14, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt; &lt;p&gt;Here are the slides from the Boulder and Philly Ruby meetups to give a better idea of what my first version looked like. You can compare them to the Rails Conf version to see how they evolved.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/codynorman/boulder-ruby-attraction-mailbox-why-i-love-action-mailbox&quot; target=&quot;_blank&quot;&gt;Attraction Mailbox Slides - Meetup Version&lt;/a&gt;&lt;/p&gt; &lt;h3 id=&quot;big-swings&quot;&gt;Big Swings&lt;/h3&gt; &lt;p&gt;One of my big takeaways from the meetups was that people really enjoyed the examples. Seeing examples of some different tasks you can accomplish with Action Mailbox got people’s wheels turning about ways they could use it in their applications.&lt;/p&gt; &lt;p&gt;With a couple of rounds of feedback, I start working on some ‘practical’ examples you could easily add with Action Mailbox. Since I had a practical example section, I thought it would be fun to close out with an ‘impractical’ example section. This would be a handful of the weirdest, interesting but probably not useful things built with Action Mailbox. This seemed like a great way to inject some personality into my talk while throwing out a wide range of things you can do with an inbound email. I was hoping the broad range of ideas would help people walk away with their own ideas for features they could add to their app (and some they shouldn’t)&lt;/p&gt; &lt;p&gt;I probably have a whole post’s worth of material covering all the stuff that &lt;em&gt;didn’t&lt;/em&gt; work or at least became too much to get working on time.&lt;/p&gt; &lt;p&gt;Seriously, the past month, my office has been littered with Hotwheels tracks, springs, button-pushers, mouse traps, skateboard wheels and more. I’m determined to get at least one of those working to include in any future talks.&lt;/p&gt; &lt;p&gt;There was just something about the idea of sending an inbound email that triggered stuff to happen in the real world that cracked me up.&lt;/p&gt; &lt;p&gt;One idea was to see if the after-market remote start device I had installed on my truck had an API available. The device uses LTE to stay connected and has a mobile app that allows you to monitor the status and perform different actions like lock/unlock and start the vehicle. My top idea soon became something along the lines of&lt;/p&gt; &lt;p&gt;“I wonder if I could send an inbound email to start my truck?”&lt;/p&gt; &lt;p&gt;Unfortunately, there wasn’t a public API I could find. However, I was fortunate enough to find someone who created an unofficial API wrapper with JS.&lt;/p&gt; &lt;p&gt;After skimming through the repo a bit, it seemed like it was possible and after getting a token, would be a pretty simple API request I could trigger in the mailbox.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/Hacksore/drone-mobile/tree/main&quot; target=&quot;_blank&quot;&gt;Drone Mobile Node API&lt;/a&gt;&lt;/p&gt; &lt;p&gt;I had casually floated out this idea a couple of times over the past couple of months and got some chuckles but it was clear that it would be tough to convey just how goofy this is through slides. I knew I had to record some kind of video demoing this for people to really get it.&lt;/p&gt; &lt;p&gt;After a long string of failures earlier in the week, I was able to get things working and recorded the process on Friday afternoon, just a couple of days before traveling for Rails Conf.&lt;/p&gt; &lt;p&gt;On Saturday, I finished editing the video and added it to my slides. I could not stop laughing when I watched it and was so tempted to share it publicly but thought that if I was able to keep this under wraps and come out of nowhere with a video demo of starting a 2006 Tacoma with an inbound email and a lil’ bit of Rails code.&lt;/p&gt; &lt;p&gt;My talk was right before lunch and wanted to do something that people would feel like they just &lt;em&gt;had&lt;/em&gt; to ask someone if they saw it or tell them what they just saw.&lt;/p&gt; &lt;h3 id=&quot;go-time&quot;&gt;Go Time&lt;/h3&gt; &lt;p&gt;This being the first time I spoke at a conference, I wasn’t sure what to expect. My talk was on the last day so I had a couple of days before I was up. One tip I kept hearing was&lt;/p&gt; &lt;p&gt;“Don’t let it weigh on you where you don’t enjoy the conference”&lt;/p&gt; &lt;p&gt;I tried to take that to heart and knew that I’d done everything I could do to prepare myself and there was nothing left but to do it.&lt;/p&gt; &lt;p&gt;The other thing I kept hearing was&lt;/p&gt; &lt;p&gt;“Are you nervous?”&lt;/p&gt; &lt;p&gt;I tried to answer every time with something like “I’m not nervous, I’m excited” which was very intentional. That wasn’t over-confidence, that was me trying to not even begin to internalize the idea of being nervous. I’d been endlessly visualizing giving a successful talk.&lt;/p&gt; &lt;p&gt;On the first day, I even popped up on the stage in the room I was going to be speaking in and took a picture of what it looked like from the podium. I would keep referring back to that picture over the next couple of days thinking about what it’s going to be like on stage.&lt;/p&gt; &lt;p&gt;By the time I got on stage, I was ready to go. If anything, I was anxious to get to the end to see how the truck email went.&lt;/p&gt; &lt;p&gt;Almost by some miracle, I kept the ending under wraps and was able to see people react to it for the first time in person. That’s absolutely something I’ll never forget and couldn’t think of a better closer.&lt;/p&gt; &lt;p&gt;After the talk was done and getting to see a room full of people react to it I decided to post a quick tweet with the video on my way back to the conference venue after stashing my bag at the hotel.&lt;/p&gt; &lt;p&gt;It was viewed over 10,000 times in less than two days.&lt;/p&gt; &lt;div class=&quot;my-5 text-center&quot;&gt; &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/xC-V_aBjmnw?si=17euozQmKXpQjvQQ&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;p&gt;That was wayyyyyyyy more than anything I expected and was glad to see people appreciated the whimsy and fun of it all.&lt;/p&gt; &lt;p&gt;If you’re interested in the slides from my Rails Conf talk, you can find them &lt;a href=&quot;https://speakerdeck.com/codynorman/attraction-mailbox-why-i-love-action-mailbox&quot; target=&quot;_blank&quot;&gt;Attraction Mailbox Slides - Rails Conf Version&lt;/a&gt;&lt;/p&gt; &lt;h3 id=&quot;next-steps&quot;&gt;Next steps?&lt;/h3&gt; &lt;p&gt;After getting some great feedback from the content in my talk, I’ve started work on a video course on Action Mailbox. Pre-sale is available now and the course is expected to be completed in August.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://store.codynorman.com/action-mailbox-pro&quot; target=&quot;_blank&quot;&gt;Action Mailbox Pro&lt;/a&gt;&lt;/p&gt; &lt;p&gt;If you’re interested in me speaking at your conference or meetup, please reach out!&lt;/p&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">Paths to important milestones are rarely straight lines. Here&apos;s a look at the winding road I took to my first conference talk at Rails Conf 2024.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Announcing Action Mailbox Pro</title><link href="https://codynorman.com/ruby/action_mailbox_course/" rel="alternate" type="text/html" title="Announcing Action Mailbox Pro"/><published>2024-04-01T17:54:46-04:00</published><updated>2024-04-01T17:54:46-04:00</updated><id>https://codynorman.com/ruby/action_mailbox_course</id><content type="html" xml:base="https://codynorman.com/ruby/action_mailbox_course/">&lt;p&gt;I’m very excited to finally share some more information on a project I’ve been working on for the past couple of months.&lt;/p&gt; &lt;p&gt;Recently, I’ve published some articles on Action Mailbox and some of my experiences with it. I’ve also been fortunate enough to have the opportunity to speak about Action Mailbox at Rails Conf this year. I’ve also been able to speak in person on this topic at some meetups and got great feedback on what people are interested in.&lt;/p&gt; &lt;p&gt;Starting today, I’m opening a waitlist for an in-depth and hands-on course with Action Mailbox.&lt;/p&gt; &lt;blockquote class=&quot;fw-bold&quot;&gt; &lt;cite&gt; The perfect starting point for developers looking to incorporate email processing capabilities into their Rails applications. This practical, hands-on course will guide you through the fundamentals of Action Mailbox, a powerful framework for handling incoming email within a Ruby on Rails application. &lt;/cite&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;a href=&quot;https://store.codynorman.com/action-mailbox-pro&quot;&gt;ActionMailbox Pro&lt;/a&gt;&lt;/p&gt; &lt;div class=&quot;mb-4&quot;&gt; &lt;script async=&quot;&quot; data-uid=&quot;1c5bb75712&quot; src=&quot;https://codynorman.ck.page/1c5bb75712/index.js&quot;&gt;&lt;/script&gt; &lt;/div&gt; &lt;p&gt;The hands-on sections will cover some real-world and practical use cases (maybe impractical too…)&lt;/p&gt; &lt;p&gt;Each section will include focused and short text and video lessons. The advanced sections and hands-on sections will be separate so you can skip or revisit the details as you need it.&lt;/p&gt; &lt;p&gt;I’m hoping to have the course completed sometime this summer. I should have a better idea of the dates after some sample videos and chapters are made available for feedback.&lt;/p&gt; &lt;p&gt;The idea is to record an intro lesson people can preview to make sure things like lighting, sound, screen resolution, etc are all in order before moving forward. I don’t want to record the whole course only to find out the sound is off or something else is wrong.&lt;/p&gt; &lt;p&gt;Join the waitlist now to get more information on early access and limited discount offers for pre-sales.&lt;/p&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">ActionMailbox Pro - The perfect starting point for developers looking to incorporate email processing capabilities into their Rails applications.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Entering my Purple Cow Era</title><link href="https://codynorman.com/personal-development/purple_cow_era/" rel="alternate" type="text/html" title="Entering my Purple Cow Era"/><published>2024-03-27T17:54:46-04:00</published><updated>2024-03-27T17:54:46-04:00</updated><id>https://codynorman.com/personal-development/purple_cow_era</id><content type="html" xml:base="https://codynorman.com/personal-development/purple_cow_era/">&lt;h3 id=&quot;have-you-ever-seen-a-purple-cow&quot;&gt;Have you ever seen a Purple Cow?&lt;/h3&gt; &lt;p&gt;First things first, I should probably fill those of you who may be unaware of the Purple Cow concept. Seth Godin has an &lt;a href=&quot;https://www.amazon.com/Purple-Cow-New-Transform-Remarkable/dp/1591843170&quot;&gt;entire book&lt;/a&gt; that does a great job of going into more detail on this concept.&lt;/p&gt; &lt;p&gt;The short version…&lt;/p&gt; &lt;p&gt;&lt;em&gt;Be Remarkable.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;Remarkable isn’t necessarily describing quality or that something is good. It’s more about whether it’s interesting, unique, delightful, or weird. So much so, you just have to tell people about it.&lt;/p&gt; &lt;h3 id=&quot;what-do-i-do&quot;&gt;What do I &lt;em&gt;do&lt;/em&gt;?&lt;/h3&gt; &lt;p&gt;Almost one year into my most recent foray into the land of self-employment, I’ve been thinking more about my long term goals and overall vision.&lt;/p&gt; &lt;p&gt;The past few months, I’ve had a chance to work on a few different types of projects and ventures.&lt;/p&gt; &lt;p&gt;I’ve also had some trouble articulating what it is I do…&lt;/p&gt; &lt;iframe src=&quot;https://giphy.com/embed/b7MdMkkFCyCWI&quot; width=&quot;480&quot; height=&quot;258&quot; frameborder=&quot;0&quot; class=&quot;giphy-embed&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt; &lt;p&gt;&lt;a href=&quot;https://giphy.com/gifs/work-games-tweets-b7MdMkkFCyCWI&quot;&gt;via GIPHY&lt;/a&gt;&lt;/p&gt; &lt;p&gt;The easiest title and the one I use most frequently is &lt;strong&gt;&lt;em&gt;Independent Rails Consultant&lt;/em&gt;&lt;/strong&gt;. This accounts for the lion’s share of my time and income so that’s what I usually lead with.&lt;/p&gt; &lt;p&gt;But it’s not the only thing I do. One of the main reasons for going self-employed was having more time to work on other projects and ventures.&lt;/p&gt; &lt;p&gt;Some of those have been:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Writing&lt;/li&gt; &lt;li&gt;Mentoring Early Career Developers&lt;/li&gt; &lt;li&gt;Offering Developer Coaching Services&lt;/li&gt; &lt;li&gt;Spot Squid (SaaS)&lt;/li&gt; &lt;li&gt;Speaking at meetups and conferences&lt;/li&gt; &lt;li&gt;Potentially organizing a Ruby retreat?&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The variety and breadth of these projects have been a huge jolt to my creativity. These projects have been stretching me to the edge of my comfort zone consistently. That’s always been a good metric that I’m heading in the right direction.&lt;/p&gt; &lt;p&gt;But still, I wouldn’t use writer, speaker, SaaS founder, conference organizer, or developer coach to describe what I do.&lt;/p&gt; &lt;h3 id=&quot;dawn-of-the-purple-cow-era&quot;&gt;Dawn of the Purple Cow Era&lt;/h3&gt; &lt;p&gt;Focus and consistency are two areas I’ve always had trouble with. I’ve made some great strides in the past couple of years but still feel a twinge of self-inflicted guilt for not focusing on the ‘one thing’.&lt;/p&gt; &lt;p&gt;Not long ago, I had the thought of just considering myself a &lt;em&gt;Purple Cow&lt;/em&gt;. A Purple Cow’s only mandate is to be unique, interesting and to turn some heads. I think at my core, I love making things. Those things could be code, blog posts, experiences, BBQ, skate ramps, or anything else that piques my interest.&lt;/p&gt; &lt;p&gt;The point is, thinking in Purple Cow terms has me feeling more creative and less boxed in.&lt;/p&gt; &lt;p&gt;My Purple Cow Era is about chasing down the ideas that I’m interested in and that excite me. These are the types of projects where ideas seem to pour out of me. That’s a good sign I should keep going.&lt;/p&gt; &lt;p&gt;Working on these different projects and ideas is the easiest way for me to put my unique stamp and style into what I’m working on.&lt;/p&gt; &lt;h3 id=&quot;let-it-rip&quot;&gt;Let It Rip&lt;/h3&gt; &lt;p&gt;There’s a saying you may have heard…&lt;/p&gt; &lt;blockquote&gt; &lt;cite&gt; &lt;strong&gt;I&apos;m here for a good time, not a long time.&lt;/strong&gt; &lt;/cite&gt; &lt;/blockquote&gt; &lt;p&gt;If you’ve heard this, it was probably right before someone did something really stupid.&lt;/p&gt; &lt;p&gt;Instead of viewing this in the light of reckless abandonment, I think about it more as a reminder we should all relish and make the best use of what time we have.&lt;/p&gt; &lt;p&gt;Changing my internal mandate to something along the lines of&lt;/p&gt; &lt;p&gt;&lt;em&gt;Do and make interesting, unique, and remarkable things that are good expressions of yourself.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;feels like it’s removed a huge bottleneck and opened me up to allowing more of these ideas in.&lt;/p&gt; &lt;p&gt;Time to get Moo-ving on that Purple Cow idea of yours.&lt;/p&gt;</content><author><name>Cody Norman</name></author><category term="personal development"/><summary type="html">Purple Cows are head-turners. I&apos;m not here to talk about what a purple cow is, I&apos;m here to talk about why that&apos;s the best description for my current era and how it&apos;s driving my focus.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Announcing Rawhide Ruby?</title><link href="https://codynorman.com/ruby/announcing_rawhide_ruby/" rel="alternate" type="text/html" title="Announcing Rawhide Ruby?"/><published>2024-03-07T16:54:46-05:00</published><updated>2024-03-07T16:54:46-05:00</updated><id>https://codynorman.com/ruby/announcing_rawhide_ruby</id><content type="html" xml:base="https://codynorman.com/ruby/announcing_rawhide_ruby/">&lt;p&gt;You may have noticed the question mark in the title. That’s because this is something that’s still in the very early stages of planning and seeing if there’s enough interest out there to make this happen.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Colorado Ranch View&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/RanchShot.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;&lt;strong&gt;Introducing the y’all-way track…&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://rawhideruby.com/&quot;&gt;Rawhide Ruby&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Come join us in the mountains of Colorado for a unique and special event.&lt;/p&gt; &lt;h3 id=&quot;what&quot;&gt;What&lt;/h3&gt; &lt;p&gt;A 2-3 day single track conference hosted at a private ranch. Accommodations would be available on-site and range from private cabins, glamping domes, RV sites and tent camping sites. Talks during the day with activities in the evening on the ranch.&lt;/p&gt; &lt;h3 id=&quot;where&quot;&gt;Where&lt;/h3&gt; &lt;p&gt;Location is still tentative but will most likely be in the gorgeous Arkansas River Valley area of Colorado. With amazing views of the collegiate peaks, the area also offers a number of other things to do in the area&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Hot Springs &amp;amp; Spas&lt;/li&gt; &lt;li&gt;ATV/Jeep Trails&lt;/li&gt; &lt;li&gt;White Water Rafting&lt;/li&gt; &lt;li&gt;Ghost Towns&lt;/li&gt; &lt;li&gt;Hiking &amp;amp; Back country Backpacking&lt;/li&gt; &lt;li&gt;Fishing&lt;/li&gt; &lt;/ul&gt; &lt;h3 id=&quot;when&quot;&gt;When&lt;/h3&gt; &lt;p&gt;Tentative date is Summer 2025&lt;/p&gt; &lt;h3 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h3&gt; &lt;p&gt;If you’d be interested in attending or sponsoring, join the list for additional information and updates.&lt;/p&gt; &lt;script async=&quot;&quot; data-uid=&quot;9d1f0c3595&quot; src=&quot;https://codynorman.ck.page/9d1f0c3595/index.js&quot;&gt;&lt;/script&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">Come join us in the mountains of Colorado for a unique and special event.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">In-Depth look at Action Mailbox</title><link href="https://codynorman.com/ruby/action_mailbox/" rel="alternate" type="text/html" title="In-Depth look at Action Mailbox"/><published>2024-02-07T16:54:46-05:00</published><updated>2024-02-07T16:54:46-05:00</updated><id>https://codynorman.com/ruby/action_mailbox</id><content type="html" xml:base="https://codynorman.com/ruby/action_mailbox/">&lt;p&gt;I wouldn’t say I love email, more like I’m in love with the &lt;em&gt;idea of email&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;Even though it feels like I’m barely treading water in a sea of emails, I almost always open and check transactional emails. It seems like I’m not alone. Some estimates show most transactional emails have an open rate of 80-85% &lt;a href=&quot;https://www.mailgun.com/blog/email/email-open-rates-decoded/&quot;&gt;source&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;With email being a great channel to reach your users, why not make it a two way street? It’s a familiar and low friction way for users to interact with your app and a powerful way to extend the capabilities of your Rails application.&lt;/p&gt; &lt;!-- I think leveraging email is a great example of focusing on solving problems for --&gt; &lt;!-- your users instead of just building software. --&gt; &lt;p&gt;Luckily, Rails has an unsung hero in Action Mailbox. Action Mailbox gives you a familiar Rails-y way to accept and process inbound emails for your app.&lt;/p&gt; &lt;p&gt;This post will walk through getting started with Action Mailbox, how to process your inbound emails and things to watch out for along the way.&lt;/p&gt; &lt;h3 id=&quot;action-mailbox-background&quot;&gt;Action Mailbox Background&lt;/h3&gt; &lt;p&gt;The Rails guides for the &lt;a href=&quot;https://guides.rubyonrails.org/action_mailbox_basics.html#what-is-action-mailbox-questionmark&quot; target=&quot;_blank&quot;&gt;Action Mailbox Basics&lt;/a&gt; do a pretty good job of explaining what Action Mailbox is. Here’s how they describe what Action Mailbox is:&lt;/p&gt; &lt;blockquote&gt; &lt;cite&gt; Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound mails directly via the built-in Exim, Postfix, and Qmail ingresses. &lt;/cite&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;cite&gt; The inbound emails are turned into `InboundEmail` records using Active Record and feature life cycle tracking, storage of the original email on cloud storage via Active Storage, and responsible data handling with on-by-default incineration. &lt;/cite&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;cite&gt; 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. &lt;/cite&gt; &lt;/blockquote&gt; &lt;p&gt;Two really important items to note are:&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt; storage of the original email on cloud storage via Active Storage...&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;These inbound emails are routed asynchronously using Active Job...&lt;/code&gt;&lt;/p&gt; &lt;p&gt;It’s easy to gloss over those requirements and can cause some issues when deploying to production. That will be covered in more detail and include my experiences deploying to production.&lt;/p&gt; &lt;p&gt;Here’s a rough breakdown of how Action Mailbox processes your inbound emails.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Action Mailbox Processing Flow&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/ActionMailboxFlow.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;h3 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h3&gt; &lt;p&gt;Getting started with Action Mailbox is a pretty familiar process&lt;/p&gt; &lt;p&gt;To install Action Mailbox run the following commands&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails action_mailbox:install &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bin/rails db:migrate &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The install task creates an &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt; and a new migration for the InboundEmails.&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 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 &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The migration for the &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; should look something like this&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# This migration comes from action_mailbox (originally 20180917164000)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CreateActionMailboxTables&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;6.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:action_mailbox_inbound_emails&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message_checksum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;timestamps&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message_checksum&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;index_action_mailbox_inbound_emails_uniqueness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;unique: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you’ve not already added Active Storage to your app, you’ll see the required migrations for Active Storage as well.&lt;/p&gt; &lt;p&gt;The other file that was created is our &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt;. You can think of the &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt; as the Post Office for your inbound emails. Mail is delivered, sorted then delivered to the correct mailbox. This file is going to route the &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; to a mailbox that inherits from &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/application_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailbox&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# routing /something/i =&amp;gt; :somewhere&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;&lt;strong&gt;Generating a new mailbox&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Like any good Rails tool, there’s a task to generate a new mailbox.&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bin/rails generate mailbox support &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The above command will create a new mailbox that looks something like this.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SupportMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;As mentioned earlier, our &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt; inherits from &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt; and a single method - &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method is where the magic happens but first, we need to direct our &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; to the &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;When your inbound email is routed to the &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt; the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method will be called. This is where you’ll put your processing logic.&lt;/p&gt; &lt;p&gt;For that to happen, we need to tell our &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt; how to route emails to this mailbox.&lt;/p&gt; &lt;!-- The `ApplicationMailbox` has a commented out example to help get us started `# --&gt; &lt;!-- routing /something/i =&gt; :somewhere` --&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt; has a commented out example to help get us started. In this commented out example, anything inbound email with a &lt;code class=&quot;highlighter-rouge&quot;&gt;to&lt;/code&gt; address that matches on &lt;code class=&quot;highlighter-rouge&quot;&gt;something&lt;/code&gt; will get routed to the &lt;code class=&quot;highlighter-rouge&quot;&gt;SomewhereMailbox&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;The routing uses the ruby regex matcher to route emails to a mailbox.&lt;/p&gt; &lt;p&gt;When getting started with Action Mailbox, instead of spending time thinking about how you’re going to route your emails, I’ve found it helpful to use the &lt;code class=&quot;highlighter-rouge&quot;&gt;:all&lt;/code&gt; option to direct &lt;em&gt;any&lt;/em&gt; inbound email to a specific mailbox.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;routing all: :support&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/mailboxes/application_mailbox.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailbox&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;routing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;all: :support&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# routing(/support\./i =&amp;gt; :support)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The above example will route all inbound emails to the &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt;. This lets you start focusing on the processing logic of the mailbox right away and update the routing as your needs evolve.&lt;/p&gt; &lt;p&gt;The commented out example shows how you would route any inbound email with a &lt;code class=&quot;highlighter-rouge&quot;&gt;to&lt;/code&gt; address containing &lt;code class=&quot;highlighter-rouge&quot;&gt;support&lt;/code&gt; to the &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;With everything setup to forward &lt;em&gt;any&lt;/em&gt; inbound email to the &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt; you may be wondering…&lt;/p&gt; &lt;p&gt;&lt;strong&gt;How do I send an email to my local Rails App?&lt;/strong&gt;&lt;/p&gt; &lt;!-- It&apos;s time to have your ticket punched by the Rails Conductor --&gt; &lt;p&gt;I would like to introduce you to the aptly named Rails Conductor.&lt;/p&gt; &lt;h3 id=&quot;sending-emails-with-rails-conductor&quot;&gt;Sending Emails with Rails Conductor&lt;/h3&gt; &lt;p&gt;With your app running, you can access the Rails Conductor at the following URL&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;http://localhost:3000/rails/conductor/action_mailbox/inbound_emails&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Rails Inbound Eamil Conductor&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/RailsConductorForm.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;This is one of my favorite hidden gems (yeah I said it) of Rails. It offers a no-frills UI for sending email directly to your app. You also have the option of creating and sending and email via the built in form or by pasting the source code of an email.&lt;/p&gt; &lt;p&gt;Using the form option should provide you with everything you need. The by source option is a bit more involved and takes to source code from an &lt;code class=&quot;highlighter-rouge&quot;&gt;.eml&lt;/code&gt; file. This is a great option for debugging and re-creating issues from production but is typically more than you need when getting started.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;New Inbound Email Form&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;The new inbound email form has some options you’re probably familiar with along with the option to include an attachment to your inbound email.&lt;/p&gt; &lt;p&gt;There’s also some additional fields for setting email headers that may be useful in your processing of the inbound emails.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;X-Original-To&lt;/strong&gt; - The email address listed here is the original recipient of the email that was received.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;In-Reply-To&lt;/strong&gt; - are header values taken from the Message-ID header of the original&lt;/p&gt; &lt;p&gt;If you set up an &lt;code class=&quot;highlighter-rouge&quot;&gt;:all&lt;/code&gt; route in your &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationMailbox&lt;/code&gt; to a specific inbox like the &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt; example, submitting this form should deliver an email to that mailbox. After submitting your email, it will show as &lt;code class=&quot;highlighter-rouge&quot;&gt;pending&lt;/code&gt; until Active Job sends the original inbound email to your Mailbox and processes it. If that’s successful, the &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; status will be updated to &lt;code class=&quot;highlighter-rouge&quot;&gt;delivered&lt;/code&gt; and at some point will be incinerated.&lt;/p&gt; &lt;p&gt;Now we’re sending email, let’s recap what’s happening:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;An email is sent to a specific email address.&lt;/li&gt; &lt;li&gt;Your Email provider accepts and forwards the inbound email to your Rails app.&lt;/li&gt; &lt;li&gt;Your Rails App creates an &lt;code class=&quot;highlighter-rouge&quot;&gt;ActtionMailbox::InboundEmail&lt;/code&gt; record and uploads the email file to Active Storage until it can be processed.&lt;/li&gt; &lt;li&gt;Active Job gets the email object and sends to the Mailbox.&lt;/li&gt; &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; object takes the uploaded email file from Active Storage, converts it to a string and creates a new &lt;code class=&quot;highlighter-rouge&quot;&gt;Mail&lt;/code&gt; object.&lt;/li&gt; &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method inside the mailbox is called on the mailbox where the email is delivered too.&lt;/li&gt; &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; is marked for incineration (record and raw_email attachment deleted).&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;There’s a surprising amount of things happening in the background at first glace. I think this is a great example of how Rails can handle so much for you allowing you to focus on the important things without setting up a ton of boilerplate on your own.&lt;/p&gt; &lt;h3 id=&quot;process-inbound-email-in-the-mailbox&quot;&gt;Process inbound email in the Mailbox&lt;/h3&gt; &lt;p&gt;We know that the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method is going to be called withing the mailbox the inbound email is routed.&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; is that object that’s delivered to the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method. If you look at the generated migration above, there’s not a lot to this class on the surface. It has a status, some references and an Active Storage attachment for the original email file sent &lt;code class=&quot;highlighter-rouge&quot;&gt;raw_email&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InboundEmail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Record&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;action_mailbox_inbound_emails&quot;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Incineratable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MessageId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Routable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_one_attached&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:raw_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;service: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActionMailbox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;storage_service&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[ pending processing delivered failed bounced ]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mail&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@mail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;raw_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;download&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;processed?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delivered?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;failed?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bounced?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The different statues are:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;pending&lt;/strong&gt; - Email has been received and uploaded to Active Storage.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;processing&lt;/strong&gt; - Original email is being downloaded from Active Storage and sent to the appropriate mailbox.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;delivered&lt;/strong&gt; - Email was successfully delivered to the mailbox with no exceptions or bounces.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;failed&lt;/strong&gt; - An exception was raised while the inbound email was being processed.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;bounced&lt;/strong&gt; - Mark the email as bounced using &lt;code class=&quot;highlighter-rouge&quot;&gt;boune_with&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Most of the information you’ll need to process the inbound email is provided by the &lt;code class=&quot;highlighter-rouge&quot;&gt;Mail&lt;/code&gt; object which is available with the &lt;code class=&quot;highlighter-rouge&quot;&gt;mail&lt;/code&gt; method within the mailbox.&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; class defines a &lt;code class=&quot;highlighter-rouge&quot;&gt;mail&lt;/code&gt; method that downloads the original email file from Active Storage and creates a new &lt;code class=&quot;highlighter-rouge&quot;&gt;Mail&lt;/code&gt; object.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/mikel/mail&quot; target=&quot;_blank&quot;&gt;Ruby Mail&lt;/a&gt; is an invaluable resource for working with email in Ruby.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mikel@test.lindsaar.net&apos;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;you@test.lindsaar.net&apos;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;This is a test email&apos;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;body.txt&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;From: mikel@test.lindsaar.net\r\nTo: you@...&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Ruby Mail also allows us to dig deeper into things like headers, attachments, html and text parts.&lt;/p&gt; &lt;p&gt;What does ‘processing’ the email actually look like?&lt;/p&gt; &lt;p&gt;Obviously, each case will be different but let’s take a look at an example for parsing the body of the email and creating a new support ticket.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SupportMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;support_ticket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SupportTicket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;from_email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;subject: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;body: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SupportMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new_ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;support_ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_now&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;In the example above, we’re creating a new &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportTicket&lt;/code&gt; record from the information contained in the inbound email.&lt;/p&gt; &lt;p&gt;We’re pulling the &lt;code class=&quot;highlighter-rouge&quot;&gt;mail.from&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;mail.subject&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;mail.body&lt;/code&gt; data provided by the &lt;code class=&quot;highlighter-rouge&quot;&gt;Mail&lt;/code&gt; gem and using that to create our new record.&lt;/p&gt; &lt;p&gt;Parsing inbound email allows you to collect the same information as if you directed your user to a form to submit a new support request. You have all the information to create a new &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportTicket&lt;/code&gt; and your user had a more streamlined and familiar process.&lt;/p&gt; &lt;p&gt;The outbound mailer that gets sent as a reply can also contain some type of unique identifier we can easily use to look up the original &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportTicket&lt;/code&gt; and link new responses to the conversation.&lt;/p&gt; &lt;p&gt;Let’s say our &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportMailbox&lt;/code&gt; has a UUID field on the model, we can include that value in the email address, parse from the reply and link new responses to the original &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportTicket&lt;/code&gt; and not create a new support ticket each time there’s a reply.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailbox&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;routing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;all: :support&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# routing(/support\./i =&amp;gt; :support)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;routing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/support-(.+)@inbound.yoursite.com/i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:support_reply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Rubular Regex Example&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/Rubular.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;Testing out the commented out match statement shows we get a match for our test string and we have a result in our capture group&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;(.+)&lt;/code&gt; is going to return a match group which will be that UUID included in the email address.&lt;/p&gt; &lt;p&gt;That should give you all you need to target the original &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportTicket&lt;/code&gt; you created and respond, escalate, close, etc.&lt;/p&gt; &lt;p&gt;That’s the ruby way to link emails together but there are also some options available in the Mail headers.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;In-Reply-to&lt;/code&gt; contains a hash value of the original message if the subject email message is a reply to another message. This is how your mail clients thread together your emails.&lt;/p&gt; &lt;p&gt;There are a few other considerations to keep in mind when running this in production (bounce_with, before_processing, raising errors, catch all mailboxes, etc.) but this is the basic flow of things.&lt;/p&gt; &lt;h3 id=&quot;advanced-usage&quot;&gt;Advanced Usage&lt;/h3&gt; &lt;p&gt;As you continue down the path of inbound email enlightenment, there are some additional tools and methods that could make your journey easier.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Callbacks&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Action Mailbox provides some callbacks that will be ran before, after or around the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method.&lt;/p&gt; &lt;p&gt;Action Mailbox Callbacks &lt;a href=&quot;https://github.com/rails/rails/blob/main/actionmailbox/lib/action_mailbox/callbacks.rb&quot; target=&quot;_blank&quot;&gt;Source Code&lt;/a&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# frozen_string_literal: true&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support/callbacks&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ActionMailbox&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# = Action Mailbox \Callbacks&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Defines the callbacks related to processing.&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Callbacks&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Callbacks&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TERMINATOR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mailbox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mailbox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finished_processing?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;included&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;define_callbacks&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;terminator: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TERMINATOR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;skip_after_callbacks_if_terminated: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class_methods&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;before_processing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;after_processing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;around_processing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:around&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Like mentioned above, these callbacks relate to the &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method. If you would like to perform some actions or ensure certain values are present the &lt;code class=&quot;highlighter-rouge&quot;&gt;before_processing&lt;/code&gt;, like confirming your can find a User record that matches the email for the inbound message. In our &lt;code class=&quot;highlighter-rouge&quot;&gt;SupportTicket&lt;/code&gt; example above, sending the confirmation email after a new ticket is created is a good example of something that would fit in the &lt;code class=&quot;highlighter-rouge&quot;&gt;after_processing&lt;/code&gt; method.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SupportMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before_processing&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ensure_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;### code for creating SupportTicket&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;from_email&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# mail.from returns Array&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ensure_user&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bounce_with&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;post_not_found&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Here’s a basic example of using a &lt;code class=&quot;highlighter-rouge&quot;&gt;before_processing&lt;/code&gt; callback to make sure we can find the User before doing any additional processing on the email.&lt;/p&gt; &lt;p&gt;Keeping your email delivery rates high and bounce rate low is an important part of getting your emails to actually land in people’s inbox. Taking some additional steps to ensure you at least &lt;em&gt;probably&lt;/em&gt; have a valid email address before doing more work than you need to.&lt;/p&gt; &lt;p&gt;You may have noticed another new method in the code above. &lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;bounce_with&lt;/code&gt; accepts a Mail message to send to the sender letting them know the email bounced. It also marks the &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; status as &lt;code class=&quot;highlighter-rouge&quot;&gt;bounced&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/rails/rails/blob/6b93fff8af32ef5e91f4ec3cfffb081d0553faf0/actionmailbox/lib/action_mailbox/base.rb#L105&quot; target=&quot;_blank&quot;&gt;source code&lt;/a&gt;&lt;/p&gt; &lt;p&gt;There’s another similar method &lt;code class=&quot;highlighter-rouge&quot;&gt;bounced!&lt;/code&gt;. This one silently prevents any additional processing without sending out an email alerting the sender of the bounce.&lt;/p&gt; &lt;p&gt;This stops the email from being processed and delivers an email (of your choosing) to notify the sender of the bounce.&lt;/p&gt; &lt;p&gt;The method accepts an &lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailer::Base::Mailer&lt;/code&gt; object along with any additional parameters the email might require.&lt;/p&gt; &lt;p&gt;This might look something like this:&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;bounce_with&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PostRequiredMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;missing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inbound_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;parsing-attachments&quot;&gt;Parsing Attachments&lt;/h3&gt; &lt;!-- Parsing Attachments from E-mail. As I&apos;ve mentioned many times before, people --&gt; &lt;!-- are going to be a lot more familiar with their email than the UI of your --&gt; &lt;!-- application. Allowing them to email attachments is a super familiar pattern and --&gt; &lt;!-- something pretty much everyone knows how to do. If they don&apos;t I&apos;m honestly --&gt; &lt;!-- quite impressed they managed to sign up for your app. --&gt; &lt;p&gt;It’s very likely people are exponentially more familiar with their chosen email client than they are the UI of your application. Attaching a file to an email is something they’ve probably done dozens if not hundreds of times. Leverage this familiarness by allowing attachments and documents to be sent and uploaded through email.&lt;/p&gt; &lt;p&gt;Let’s set I need some ‘attachments’ or files. For example, PDF documents from a User. I could give them the steps to walk through uploading everything on their own in some sort of self-service portal, have them upload the required documents in a forma…or I could lean on what they already know how to do.&lt;/p&gt; &lt;p&gt;In this example, We’ll look at what the process would be for grabbing an attached PDF from an email, creating an Active Storage object and alerting an admin that a new document is ready.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LegacyDocumentMailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationMailbox&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_legacy_document&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_legacy_document&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legacy_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LegacyDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# imported_document is the attachment name on LegacyDocument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legacy_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imported_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;legacy_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;All of our business logic is within the &lt;code class=&quot;highlighter-rouge&quot;&gt;create_legacy_document&lt;/code&gt; method, so let’s break down what’s happening.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;legacy_document&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LegacyDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We have a &lt;code class=&quot;highlighter-rouge&quot;&gt;LegacyDocument&lt;/code&gt; active record object that has &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;email&lt;/code&gt; attributes. Next, we’re creating a new object and setting the &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; to the value of the email subject and &lt;code class=&quot;highlighter-rouge&quot;&gt;email&lt;/code&gt; to the first returned sender.&lt;/p&gt; &lt;p&gt;Don’t forget the &lt;code class=&quot;highlighter-rouge&quot;&gt;mail&lt;/code&gt; gem returns an array of string values when calling &lt;code class=&quot;highlighter-rouge&quot;&gt;from&lt;/code&gt; so we grab the first one.&lt;/p&gt; &lt;p&gt;Assuming our &lt;code class=&quot;highlighter-rouge&quot;&gt;LegacyDocument&lt;/code&gt; has the Active Storage attachment set up correctly. Remember, Active Storage is a requirement of Action Mailbox so we should already have everything setup and only need to add the &lt;code class=&quot;highlighter-rouge&quot;&gt;has_one_attached :imported_document&lt;/code&gt; to the model.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/LegacyDocument&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;has_one_attached&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:imported_document&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This is how we can create a new Active Storage attachment of the PDF attachment from the &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;n&quot;&gt;legacy_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imported_document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;With our object created, this code is manually creating and attaching an &lt;code class=&quot;highlighter-rouge&quot;&gt;ActiveStorage::Attachment&lt;/code&gt; called &lt;code class=&quot;highlighter-rouge&quot;&gt;imported_document&lt;/code&gt; on the &lt;code class=&quot;highlighter-rouge&quot;&gt;LegacyWaiver&lt;/code&gt; model.&lt;/p&gt; &lt;!-- https://github.com/mikel/mail?tab=readme-ov-file#testing-and-extracting-attachments --&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;attachments&lt;/code&gt; is a method from Ruby mail that returns a list of the attachments.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/mikel/mail?tab=readme-ov-file#testing-and-extracting-attachments&quot; target=&quot;_blank&quot;&gt;Extracting Attachments&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Aside from the &lt;code class=&quot;highlighter-rouge&quot;&gt;mail&lt;/code&gt; methods for accessing data on the mail object, this code is mostly Active Storage. &lt;code class=&quot;highlighter-rouge&quot;&gt;mail.attachments.first.body.decoded&lt;/code&gt; will return a string representation of the attachment which we wrap in &lt;code class=&quot;highlighter-rouge&quot;&gt;StringIO&lt;/code&gt; and send to Active Storage.&lt;/p&gt; &lt;p&gt;Digging into Active Storage is out of scope for this post but the general idea is you ‘manually’ attach the file (imported_document) to the parent object (&lt;code class=&quot;highlighter-rouge&quot;&gt;LegacyWaiver&lt;/code&gt;)&lt;/p&gt; &lt;p&gt;Accepting attachments all willy-nilly like this does open yourself up to more potential issues. Some things you may want to consider are: file size, file type, dealing with multiple attachments with things like signatures, etc.&lt;/p&gt; &lt;!-- setting these limits and validations with a way to communicate the issue back to --&gt; &lt;!-- the sender if something goes wrong can help with hard to track down failures. --&gt; &lt;h3 id=&quot;ingress-options-and-production-considerations&quot;&gt;Ingress Options and Production Considerations&lt;/h3&gt; &lt;p&gt;Default ingress options Action Mailbox provides are:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Exim&lt;/li&gt; &lt;li&gt;Mailgun&lt;/li&gt; &lt;li&gt;Mandrill&lt;/li&gt; &lt;li&gt;Postfix&lt;/li&gt; &lt;li&gt;Postmark&lt;/li&gt; &lt;li&gt;Qmail&lt;/li&gt; &lt;li&gt;SendGrid.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;More information for each option can be found in the &lt;a href=&quot;https://guides.rubyonrails.org/action_mailbox_basics.html#configuration&quot; target=&quot;_blank&quot;&gt;configuration section&lt;/a&gt; of the Rails Guides.&lt;/p&gt; &lt;p&gt;Postmark is my preferred email service provider from the default options Action Mailbox provides. I have another articles with the detail for &lt;a href=&quot;/ruby/deploy_action_mailbox_with_postmark&quot;&gt;deploying to Postmark&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Active Storage Service&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;One thing I’ve found helpful with deploying Action Mailbox to production is to bite the bullet up front and configure an external service like Amazon S3 for Active Storage. Using the disk option can work but if you’re app is not running in a single process, it can potentially cause some headaches.&lt;/p&gt; &lt;p&gt;Here’s why.&lt;/p&gt; &lt;p&gt;When your inbound mail service (like Postmark) receives the inbound email, it forwards it to your Rails application.&lt;/p&gt; &lt;p&gt;After the inbound email is received by your app, it &lt;em&gt;uploads the original email file to Active Storage&lt;/em&gt;&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ActionMailbox::InboundEmail&lt;/code&gt; has an Active Storage attachment&lt;code class=&quot;highlighter-rouge&quot;&gt;raw_email&lt;/code&gt; that is used for storing the original inbound email file (&lt;code class=&quot;highlighter-rouge&quot;&gt;.eml&lt;/code&gt;). If we look back at the &lt;code class=&quot;highlighter-rouge&quot;&gt;source&lt;/code&gt; method, we see that that file is downloaded from the Active Storage storage service with &lt;code class=&quot;highlighter-rouge&quot;&gt;raw_email.download&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# rails/actionmailbox/app/models/action_mailbox/inbound_email.rb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;raw_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;download&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;!-- https://github.com/rails/rails/blob/6b93fff8af32ef5e91f4ec3cfffb081d0553faf0/actionmailbox/app/models/action_mailbox/inbound_email.rb#L40C19-L40C37 --&gt; &lt;p&gt;This process is also handled asynchronously with Active Job or other job service you have configured.&lt;/p&gt; &lt;p&gt;Let’s say you have your Rails app deployed to somewhere like Heroku or Render.&lt;/p&gt; &lt;p&gt;On Heroku, you probably have 2 dynos. One running your app, one running your background processes. If you have your Active Storage service set to &lt;code class=&quot;highlighter-rouge&quot;&gt;Disk&lt;/code&gt;, that will store the inbound email on your app dyno.&lt;/p&gt; &lt;p&gt;When the worker dyno attempts to process the inbound email by downloading the original attachment, it will look on the dyno it’s being executed on.&lt;/p&gt; &lt;p&gt;However, the &lt;em&gt;actual&lt;/em&gt; inbound email is stored on a different dyno.&lt;/p&gt; &lt;p&gt;This means the &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; will never be able to be processed because the dyno running that code can’t find the original attachment (it’s on the other server)&lt;/p&gt; &lt;p&gt;This means that the &lt;code class=&quot;highlighter-rouge&quot;&gt;InboundEmail&lt;/code&gt; record will raise an exception when trying to process the email since it can’t find the original attachment (it’s stored in the dyno running the app)&lt;/p&gt; &lt;p&gt;Setting up S3 from the get-go can eliminate a lot of these headaches. It also has the advantage of making it easier to grab the original email file for debugging. This is a great example of where the ‘create email from source’ in the Rails Conductor comes in handy.&lt;/p&gt; &lt;p&gt;Rob Zolkos has a great write up on how you can do that &lt;a href=&quot;https://world.hey.com/robzolkos/debugging-production-actionmailbox-issues-in-development-f5886579&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Thanks Rob!&lt;/p&gt; &lt;h3 id=&quot;using-a-subdomain-for-inbound-mail&quot;&gt;Using a subdomain for inbound mail&lt;/h3&gt; &lt;p&gt;Another thing that I’ve found helpful when setting up Action Mailbox for production is running all your inbound emails through a subdomain.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;inbound.yourapp.com&lt;/code&gt;&lt;/p&gt; &lt;p&gt;This also helps keeping your MX records separate on DNS. If you have internal email addresses for you app with something like &lt;code class=&quot;highlighter-rouge&quot;&gt;name@yourapp.com&lt;/code&gt; and try to accept inbound emails for your app at &lt;code class=&quot;highlighter-rouge&quot;&gt;reply-123@yourapp.com&lt;/code&gt; it’s pretty easy to accidentally overwrite your current DNS settings for existing email stuff.&lt;/p&gt; &lt;!-- Ask me how I know... --&gt; &lt;p&gt;I’ve learnt this lesson first hand…&lt;/p&gt; &lt;p&gt;Sometimes, mining for gems takes some diggin’.&lt;/p&gt; &lt;p&gt;Action Mailbox is a wonderful example of clean and concise code handling a complex problem and have learned a lot reviewing the source code.&lt;/p&gt; &lt;p&gt;I think Action Mailbox is a vastly underrated features of rails and can add powerful functionality in a way that feels very familiar to the standard Rails Controllers.&lt;/p&gt; &lt;p&gt;Allowing your users to perform actions from inbound emails is a great way to add convenience and flexibility to your Rails app. I hope this article has demystified the process and given you some ideas on how you can use inbound email in you Rails app.&lt;/p&gt; &lt;p&gt;Let me know if you have any questions, or spot any issues that should be updated. Keep your eye out for some more Action Mailbox content coming soon!&lt;/p&gt; &lt;h3 id=&quot;coming-soon---action-mailbox-pro&quot;&gt;Coming Soon - Action Mailbox Pro&lt;/h3&gt; &lt;script async=&quot;&quot; data-uid=&quot;ec40bc76b7&quot; src=&quot;https://codynorman.ck.page/ec40bc76b7/index.js&quot;&gt;&lt;/script&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">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.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Deploy Action Mailbox to Postmark</title><link href="https://codynorman.com/ruby/deploy_action_mailbox_with_postmark/" rel="alternate" type="text/html" title="Deploy Action Mailbox to Postmark"/><published>2024-02-07T16:54:46-05:00</published><updated>2024-02-07T16:54:46-05:00</updated><id>https://codynorman.com/ruby/deploy_action_mailbox_with_postmark</id><content type="html" xml:base="https://codynorman.com/ruby/deploy_action_mailbox_with_postmark/">&lt;p&gt;In my recent &lt;a href=&quot;/ruby/action_mailbox&quot;&gt;Action Mailbox Post&lt;/a&gt;, I reviewed a ton of information on how Action Mailbox works and how you can use it. One very important topic I didn’t go over was how to deploy your Action Mailbox code to one of the default providers from Rails.&lt;/p&gt; &lt;p&gt;With that post covering a lot of detail, I wanted to separate out the instructions for deploying.&lt;/p&gt; &lt;p&gt;Rails provides a few different options for providers to handle your inbound emails. You’ll see these referred to as &lt;code class=&quot;highlighter-rouge&quot;&gt;Ingresses&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;Ingress&lt;/code&gt; when referring to one.&lt;/p&gt; &lt;p&gt;The default options from Rails are&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Exim&lt;/li&gt; &lt;li&gt;Mailgun&lt;/li&gt; &lt;li&gt;Mandrill&lt;/li&gt; &lt;li&gt;Postfix&lt;/li&gt; &lt;li&gt;Postmark&lt;/li&gt; &lt;li&gt;Qmail&lt;/li&gt; &lt;li&gt;SendGrid&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;I’ve used Postmark quite a bit over the years for transactional email and have always had a good experience. It’s my default provider and the one we’re going to walk through in this post.&lt;/p&gt; &lt;p&gt;Before we get started, there are a couple of things you’ll need that will help your setup go smoother.&lt;/p&gt; &lt;ul&gt; &lt;li&gt;A Postmark Account&lt;/li&gt; &lt;li&gt;A domain to use for the mailer &lt;em&gt;or&lt;/em&gt; a secure tunnel to your localhost like ngrok.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;This post will assume your app is already deployed somewhere and won’t be covering deploying your Rails app. We’re only focusing on connecting Postmark for your inbound emails.&lt;/p&gt; &lt;p&gt;If you’re looking for some help deploying your app, I’ve been a long time customer of &lt;a href=&quot;https://hatchbox.io/&quot; target=&quot;_blank&quot;&gt;Hatchbox&lt;/a&gt; and can’t recommend them enough.&lt;/p&gt; &lt;p&gt;With your Action Mailbox app ready to go, let’s start adding the configuration needed for Postmark.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://guides.rubyonrails.org/action_mailbox_basics.html#postmark&quot; target=&quot;_blank&quot;&gt;Rails Guide - Postmark&lt;/a&gt;&lt;/p&gt; &lt;p&gt;There isn’t a lot of code required for the Postmark setup so we can add some of the required code before configuring our mail servers in Postmark.&lt;/p&gt; &lt;p&gt;The first thing we need to is set our ingress for Action Mailbox in &lt;code class=&quot;highlighter-rouge&quot;&gt;config/environments/production.rb&lt;/code&gt;&lt;/p&gt; &lt;h4 id=&quot;setting-the-ingress&quot;&gt;Setting the Ingress&lt;/h4&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config/environments/production.rb&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action_mailbox&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ingress&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:postmark&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h4 id=&quot;generating-and-setting-the-ingress-password&quot;&gt;Generating and setting the ingress password&lt;/h4&gt; &lt;p&gt;We also need to generate a password and set that value in our encrypted credentials or as an environment variable.&lt;/p&gt; &lt;p&gt;In your Rails console, we can use &lt;code class=&quot;highlighter-rouge&quot;&gt;SecureRandom&lt;/code&gt; to generate a password for us.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;SecureRandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;alphanumeric&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;12312ddfgdfg&quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;&lt;strong&gt;Encrypted Credentials Side Note&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Running &lt;code class=&quot;highlighter-rouge&quot;&gt;bin/rails credentials:edit&lt;/code&gt; will open the main encrypted credentials file. All the credentials in this file will apply to &lt;em&gt;all&lt;/em&gt; environments unless we have a specific environment file.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt; For VSCode you may need to run &lt;code class=&quot;highlighter-rouge&quot;&gt;EDITOR=&quot;code --wait&quot; bin/rails credentials:edit&lt;/code&gt;. If your using another editor besides VSCode and are seeing issues getting your encrypted credentials to open, swap &lt;code class=&quot;highlighter-rouge&quot;&gt;EDITOR=code&lt;/code&gt; with whatever editor your using &lt;code class=&quot;highlighter-rouge&quot;&gt;EDITOR=sublime&lt;/code&gt;&lt;/p&gt; &lt;!-- VS Code credentials edit --&gt; &lt;!-- `EDITOR=&quot;code --wait&quot; bin/rails credentials:edit` --&gt; &lt;p&gt;In other words, these credentials are going to apply to our &lt;code class=&quot;highlighter-rouge&quot;&gt;development&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;production&lt;/code&gt; environment unless I created a &lt;code class=&quot;highlighter-rouge&quot;&gt;credentials/development.yml&lt;/code&gt; (or whatever that is) file. Eventually you’ll probably want to separate that out with different settings for different environments. However, making the password available in our &lt;code class=&quot;highlighter-rouge&quot;&gt;development&lt;/code&gt; environment is going to make it easier connecting this to our local environment with ngrok.&lt;/p&gt; &lt;h4 id=&quot;add-generated-password-to-encrypted-credentials&quot;&gt;Add generated password to encrypted credentials&lt;/h4&gt; &lt;p&gt;Now we’re done detouring into Rails encrypted credentials, we can the password we generated earlier to our credentials.&lt;/p&gt; &lt;p&gt;You’ll need to copy the output of that command and add it to our encrypted credentials.&lt;/p&gt; &lt;p&gt;I’m a huge fan of using Rails encrypted credentials but if you’d like to stick with using environment variables, you can use the &lt;code class=&quot;highlighter-rouge&quot;&gt;RAILS_INBOUND_EMAIL_PASSWORD&lt;/code&gt; environment variable for your password.&lt;/p&gt; &lt;p&gt;Once your encrypted credentials are open add the following code with your generated password.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ss&quot;&gt;action_mailbox: ingress_password: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12312&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ddfgdfg&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;That’s pretty much it for the code changes we need to make. The rest of the setup and configuration is going to be on Postmark.&lt;/p&gt; &lt;h4 id=&quot;creating-a-postmark-server&quot;&gt;Creating A Postmark Server&lt;/h4&gt; &lt;p&gt;If you haven’t already, create a new Postmark account and create a new ‘Server’&lt;/p&gt; &lt;p&gt;In Postmark, a server is a way to organize or group incoming or outgoing email. You can read more about &lt;a href=&quot;https://postmarkapp.com/developer/user-guide/managing-your-account/managing-servers&quot; target=&quot;_blank&quot;&gt;Postmark Servers&lt;/a&gt;&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Server Setup&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkServerSetup.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;After creating the server, you’ll see 3 different ‘Streams’ listed on the dashboard.&lt;/p&gt; &lt;p&gt;To try to keep things consistent. I’ll be using the same terminology as Postmark does. A server is contains different types of ‘Streams’ which are used to send different types of email. Here’s how Postmark defines Streams:&lt;/p&gt; &lt;blockquote&gt; &lt;cite&gt; Message Streams is how we separate different types of mail to ensure the highest deliverability for each. Pick the one that’s right for your email: &lt;/cite&gt; &lt;/blockquote&gt; &lt;blockquote&gt; &lt;cite&gt; Transactional streams are for sending time-sensitive messages triggered for one recipient at a time. Broadcasts are for messages sent to many recipients at once, like marketing campaigns or newsletters. Inbound stream is a way for your application to receive email. &lt;/cite&gt; &lt;/blockquote&gt; &lt;p&gt;Your new Postmark server should have 3 different streams.&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Default Broadcast Stream (marketing emails)&lt;/li&gt; &lt;li&gt;Default Inbound Stream (inbound email)&lt;/li&gt; &lt;li&gt;Default Transactional Stream (transactional email from your app like password reset emails)&lt;/li&gt; &lt;/ul&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Default Streams&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkDefaultStreams.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;h4 id=&quot;configuring-the-default-inbound-stream&quot;&gt;Configuring the Default Inbound Stream&lt;/h4&gt; &lt;p&gt;For configuring our inbound emails, we’ll be using the &lt;em&gt;Default Inbound Stream&lt;/em&gt; option.&lt;/p&gt; &lt;p&gt;After first clicking on the Default Inbound Stream link, you should land on the ‘Setup Instructions’ page. Right at the top, you’ll see the option to get your servers inbound email address.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Default Inbound Stream Settings&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/DefaultInboundStreamSettings.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;Setting up production email is fraught with peril. Well…maybe that’s being a &lt;em&gt;tad&lt;/em&gt; dramatic but it can still be a bumpy ride.&lt;/p&gt; &lt;p&gt;Debugging and diagnosing production issues with email can feel like trying to look inside a black box that has another black box inside. In other words, there’s lot of small points of failure.&lt;/p&gt; &lt;p&gt;For this reason, in order to preserve my time and sanity as best as possible, I try to do a series of small checks along the way to make sure things are working as expected.&lt;/p&gt; &lt;p&gt;When I’m trying to resolve production issues, I’ll often start with focusing on things to rule-out to begin with. Performing some small checks along the way can give you a better idea of what things &lt;em&gt;are&lt;/em&gt; working as intended and rule them out as possible causes.&lt;/p&gt; &lt;h4 id=&quot;sending-a-test-email&quot;&gt;Sending a Test Email&lt;/h4&gt; &lt;p&gt;Just to make sure out Inbound Stream has been set up correctly and ready for use, we can fire off a quick email to the server’s inbound email address at the top-section. It should look something like this&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;123456e8b05e594a3a18129f293281469@inbound.postmarkapp.com&lt;/code&gt;&lt;/p&gt; &lt;p&gt;After sending your inbound email to the server, clicking on the ‘Activity’ tab for your inbound server should show the inbound email you just sent.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Inbound Server Logs&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkInbound.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;This lets us know that things should be configured correctly on the Postmark side and can move on to the next step.&lt;/p&gt; &lt;p&gt;Speaking of checking things along the way, now would be a good time to make sure these changes are &lt;em&gt;deployed&lt;/em&gt; and checked to make sure the values are set.&lt;/p&gt; &lt;h4 id=&quot;checking-your-credentials&quot;&gt;Checking your credentials&lt;/h4&gt; &lt;p&gt;Speaking of checking things along the way, now would be a good time to check if your changes adding the ingress_password have been deployed to your server. Missing or incorrect keys is very close to the top of my list for most common issues when setting up a new service.&lt;/p&gt; &lt;p&gt;A quick visit to our old friend the Rails console is all we need to confirm our password has been set.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;credentials&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action_mailbox&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;ingress_password: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12312&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ddfgdfg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Or if you set the value with an environment variable.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RAILS_INBOUND_EMAIL_PASSWORD&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12312&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ddfgdfg&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h4 id=&quot;add-webhook-url-to-postmark&quot;&gt;Add Webhook URL to Postmark&lt;/h4&gt; &lt;p&gt;With the password on the server, we need to setup an inbound webhook for Postmark. This inbound webhook is going to receive the incoming email and send the data to our postmark inbound email controller Action Mailbox provides for us.&lt;/p&gt; &lt;p&gt;If you’ve never seen this syntax for authentication in a URL like this I’m sure this looks weird but here’s how you structure your URL for the webhook to add to Postmark:&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails&lt;/code&gt;&lt;/p&gt; &lt;p&gt;This is using HTTP basic authentication with our username &lt;code class=&quot;highlighter-rouge&quot;&gt;actionmailbox&lt;/code&gt; and password &lt;code class=&quot;highlighter-rouge&quot;&gt;PASSWORD&lt;/code&gt; (the one we generated and added to the credentials) in the URL. This is how Rails confirms this is a valid request instead of being an open endpoint for requests.&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;@example.com&lt;/code&gt; is the URL for our server, as in the URL for where your app is deployed (not postmark).&lt;/p&gt; &lt;p&gt;The final portion is the route that goes to the Action Mailbox controller for Postmark.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;rails/action_mailbox/postmark/inbound_emails&lt;/code&gt;&lt;/p&gt; &lt;p&gt;We now have all the info we need to create our inbound webhook URL.&lt;/p&gt; &lt;p&gt;Click on the ‘Default Inbound Stream’ for your server and look for the section ‘Set your server’s inbound webhook URL’&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Inbound Empty Webhook URL&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkInboundWebhookBlank.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;You &lt;em&gt;could&lt;/em&gt; set up the inbound webhook URL on the ‘Setup Instructions’ page for the Inbound Stream, but there’s 1 small change we need to make that’s not visible on that page.&lt;/p&gt; &lt;p&gt;There’s a helpful little note in the Rails guides about including raw email content in the payload.&lt;/p&gt; &lt;blockquote&gt; &lt;cite&gt; &lt;strong&gt;When configuring your Postmark inbound webhook, be sure to check the box labeled &quot;Include raw email content in JSON payload&quot;. Action Mailbox needs the raw email content to work.&lt;/strong&gt; &lt;/cite&gt; &lt;/blockquote&gt; &lt;p&gt;If you navigate to the ‘Settings’ section for your Inbound Stream, you’ll see a checkbox in the Webhook section that says ‘Include raw email content in JSON payload’&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Webhook URL JSON Payload&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkWebhookJSON.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;That’s what the Rails guides were referring to so make sure that option is checked.&lt;/p&gt; &lt;p&gt;With that option checked, add your webhook URL (&lt;code class=&quot;highlighter-rouge&quot;&gt;https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails&lt;/code&gt;) to the field.&lt;/p&gt; &lt;p&gt;Now we’re added new configs, it would be a good time to confirm they’re working.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Testing The Webhook URL&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;After saving your Inbound webhook URL, you should see a button to check the URL confirming Postmark can correctly send requests to your sever. Clicking on the button should show a loading state and if everything goes well, you’ll see:&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Remote server returned an HTTP status code of 204&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Webhook URL Success&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkWebhookSuccess.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;If you’re not seeing that, you know that there’s an issue connecting your Rails app and Postmark so gives you a good idea of where to look too (check your rails logs to see if the request is hitting your server and if so, if there’s anything obvious happening)&lt;/p&gt; &lt;p&gt;Seeing a successful request doesn’t necessarily mean everything is ready to go, just that Postmark is connecting with your app at the correct URL. This request should be visible in your server logs as well.&lt;/p&gt; &lt;p&gt;If your request fails, you’ll see an error message.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Webhook URL Error&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkWebhookError.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;If you’re not getting a successful request to your webhook URL, checking your logs to see if the request hit your app and any errors that may have been raised afterwards is the first place to look.&lt;/p&gt; &lt;h4 id=&quot;connecting-a-domain&quot;&gt;Connecting a Domain&lt;/h4&gt; &lt;p&gt;If you’d like to connect a domain you have to handle all your inbound mailers (hot tip: subdomains save a ton of headache) you can read more about that &lt;a href=&quot;https://postmarkapp.com/developer/user-guide/inbound/inbound-domain-forwarding&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;&lt;/p&gt; &lt;p&gt;The short version is you need to create a MX record for your inbound email domain and set the value to &lt;code class=&quot;highlighter-rouge&quot;&gt;inbound.postmarkapp.com&lt;/code&gt;&lt;/p&gt; &lt;p&gt;If I wanted to set up something like &lt;code class=&quot;highlighter-rouge&quot;&gt;inbound.example.com&lt;/code&gt; as my inbound email URL, I would create a new DNS record, set the type to MX, add &lt;code class=&quot;highlighter-rouge&quot;&gt;inbound&lt;/code&gt; in the Name section, set the priority 10 (from their docs) and the value to &lt;code class=&quot;highlighter-rouge&quot;&gt;inbound.postmark.com&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Postmark Webhook DNS Settings&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/PostmarkDNS.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;The screenshot illustrates adding the DNS record for &lt;code class=&quot;highlighter-rouge&quot;&gt;example.yourdomain.com&lt;/code&gt;&lt;/p&gt; &lt;p&gt;After adding your DNS record, head back to the Inbound Stream settings page and add your inbound domain to the ‘Inbound domain forwarding’ section and click ‘Save Changes’&lt;/p&gt; &lt;p&gt;And Voila, now you have a production setup for your inbound email.&lt;/p&gt; &lt;h4 id=&quot;testing-and-debugging-locally&quot;&gt;Testing and debugging locally&lt;/h4&gt; &lt;p&gt;It’s pretty likely that at some point, you’ll have some issues you’d like to try to diagnose or re-create in your local environment.&lt;/p&gt; &lt;p&gt;Using ngrok or cloudflare’s secure tunnel service exposes your local development environment (localhost:3000) to a public URL. This is great for sending webhooks from a 3rd party service to your local environment.&lt;/p&gt; &lt;p&gt;I’ll be covering how to use a secure tunnel to debug mailer issues with &lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Start ngrok with &lt;code class=&quot;highlighter-rouge&quot;&gt;ngrok http 3000&lt;/code&gt;. In this case our local Rails app is running on &lt;code class=&quot;highlighter-rouge&quot;&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Your screen should look something like this. Note the URL ngrok is making your app available on. There’s one change we’ll need to make to access our running app on this public URL.&lt;/p&gt; &lt;div class=&quot;my-5&quot;&gt; &lt;img alt=&quot;Example Ngrok Screen&quot; class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;/images/NgrokScreen.png&quot; style=&quot;z-index: 10&quot; /&gt; &lt;/div&gt; &lt;p&gt;There are a couple of different variations of the ngrok URL (.io, .app, etc.) so make note of which one is being used.&lt;/p&gt; &lt;p&gt;We need to whitelist this host in our &lt;code class=&quot;highlighter-rouge&quot;&gt;config/environments/development.rb&lt;/code&gt; file to tell Rails it’s ok to accept connections from this URL.&lt;/p&gt; &lt;p&gt;You can add your specific ngrok domain with: &lt;code class=&quot;highlighter-rouge&quot;&gt;config.hosts &amp;lt;&amp;lt; &quot;f515c7854739.ngrok.app&quot;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;&lt;em&gt;or&lt;/em&gt; if you would like to allow &lt;strong&gt;any domain&lt;/strong&gt; to access localhost you set hosts to &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; with &lt;code class=&quot;highlighter-rouge&quot;&gt;config.hosts = nil&lt;/code&gt;. This can be handy when testing multi-tenant apps.&lt;/p&gt; &lt;p&gt;Without a premium ngrok account, your URL will be different each time. If you don’t want to update that URL each time you start ngrok, you can use a regex to whitelist _any ngrok domain.&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; # config/environments/development.rb # whitelist ngrok domains. config.hosts &amp;lt;&amp;lt; /[a-z0-9-]+\.ngrok\.app/ &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The other change for allowing Postmark to send email to your local environment is setting the ingress value in &lt;code class=&quot;highlighter-rouge&quot;&gt;config/environments/development.rb&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# config/environments/development.rb # use ngrok and update postmark settings to test locally config.action_mailbox.ingress = :postmark &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Re-start your server, paste your ngrok URL into your browser and make sure your app loads.&lt;/p&gt; &lt;h4 id=&quot;update-postmark-settings-for-development&quot;&gt;Update Postmark Settings for development&lt;/h4&gt; &lt;p&gt;Swap your domain name for the ngrok domain in the webhook settings, it should look something like this:&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;https://actionmailbox:PASSWORD@f515c7854739.ngrok.app/rails/action_mailbox/postmark/inbound_emails&lt;/code&gt;&lt;/p&gt; &lt;p&gt;where &lt;code class=&quot;highlighter-rouge&quot;&gt;f515c7854739.ngrok.app&lt;/code&gt; is the URL sending traffic to my local environment.&lt;/p&gt; &lt;p&gt;If we hit the ‘Check’ button, even without saving, that should check the URL to make sure it’s working. You should see a request from Postmark hit your local environment a couple seconds after clicking the button. If all goes well, you’ll see the green text saying Postmark received a 204 status code.&lt;/p&gt; &lt;p&gt;If it failed, hopefully it made it to your app and you’ll have more information in your local logs. If you don’t see anything in your Rails server logs, it’s most likely an issue with the webhook URL.&lt;/p&gt; &lt;p&gt;If your webhook URL returned an successful response, sending an email to the inbound email address for the Default Inbound Stream (&lt;code class=&quot;highlighter-rouge&quot;&gt;123456e8b05e594a3a18129f293281469@inbound.postmarkapp.com&lt;/code&gt;) should forward that email to your local Rails app.&lt;/p&gt; &lt;p&gt;To confirm the email made it to Postmark, click on the ‘Activity’ section for the Default Inbound Stream and look for the email you just sent. The email should have a status like queued, retry or processed.&lt;/p&gt; &lt;p&gt;If everything is configured correctly, you should see some activity in your local server logs from Postmark at the corresponding controller:&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Processing by ActionMailbox::Ingresses::Postmark::InboundEmailsController#create as JSON&lt;/code&gt;&lt;/p&gt; &lt;p&gt;Now we have these real emails being forwarded from Postmark to our local dev environment. This is a huge boost to your feedback loop and a great way to debug tough to re-create issues with inbound email.&lt;/p&gt; &lt;p&gt;Since this works more or less in the same manner as sending an email from the Rails Conductor, we can also do things like adding &lt;code class=&quot;highlighter-rouge&quot;&gt;debugger&lt;/code&gt; to our &lt;code class=&quot;highlighter-rouge&quot;&gt;process&lt;/code&gt; method inside a mailbox to access an interactive debugger with the data from the real inbound email.&lt;/p&gt; &lt;p&gt;Josh Smith has a great article walking through &lt;a href=&quot;https://dev.to/joshdevhub/connecting-debugger-to-rails-applications-2p4p&quot; target=&quot;_blank&quot;&gt;connecting a debugger to a Rails application&lt;/a&gt; if you’re not familiar with that process.&lt;/p&gt; &lt;p&gt;Thanks Josh!&lt;/p&gt; &lt;h4 id=&quot;using-raw-source-in-development&quot;&gt;Using Raw Source in development&lt;/h4&gt; &lt;p&gt;If you’re unable or unwilling to get everything working with forwarding your emails to your local environment, you still have some options.&lt;/p&gt; &lt;p&gt;If you click on one of the inbound emails listed in your ‘Activity’ section of the Default Inbound Stream, you’ll see some additional information about the inbound email.&lt;/p&gt; &lt;p&gt;Clicking on the tab that says ‘Raw Source’ will display the source code of the email. If you remember from earlier, the Rails Conductor has the option to send an inbound email using the email source code. That’s the information contained in this ‘Raw Source’ section.&lt;/p&gt; &lt;p&gt;Copying that Raw Source and pasting that info into the input for the email by source option in the Rails Conductor should give you a pretty similar result.&lt;/p&gt; &lt;p&gt;Hopefully this gives you some additional tools and methods for debugging and inspecting your inbound emails.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Other Providers&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;If your intersted in seeing how this same process can be accomplished with Postfix or SendGrid Prabin Poudel has some great articles detailing that process.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://prabinpoudel.com.np/articles/action-mailbox-with-sendgrid/&quot;&gt;Setup Action Mailbox with SendGrid&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://prabinpoudel.com.np/articles/action-mailbox-with-postfix-part-1/&quot;&gt;Setup Action Mailbox with Postfix&lt;/a&gt;&lt;/p&gt; &lt;h4 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h4&gt; &lt;p&gt;While these steps have been specific for Postmark, a lot of the same principles, including using a local tunnel like ngrok to send email to your local environment should be pretty similar.&lt;/p&gt; &lt;p&gt;Please let me know if you feel like I left anything out or when Postmark changes their UI making all of these screenshots and steps outdated.&lt;/p&gt; &lt;h3 id=&quot;coming-soon---action-mailbox-pro&quot;&gt;Coming Soon - Action Mailbox Pro&lt;/h3&gt; &lt;script async=&quot;&quot; data-uid=&quot;ec40bc76b7&quot; src=&quot;https://codynorman.ck.page/ec40bc76b7/index.js&quot;&gt;&lt;/script&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">Step by Step walkthrough for configuring your Action Mailbox Rails app with Postmark.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Using Concerns with Ruby on Rails</title><link href="https://codynorman.com/ruby/rails_concerns/" rel="alternate" type="text/html" title="Using Concerns with Ruby on Rails"/><published>2023-07-23T17:54:46-04:00</published><updated>2023-07-23T17:54:46-04:00</updated><id>https://codynorman.com/ruby/rails_concerns</id><content type="html" xml:base="https://codynorman.com/ruby/rails_concerns/">&lt;p&gt;For the past few months, I’ve been holding standups and office hours with a top-notch group of early career developers. After everyone gives their update, we take some time and try to go into more detail about one of the issues they’re having. Topics have ranged from database migrations and when to use the &lt;code class=&quot;highlighter-rouge&quot;&gt;up&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;down&lt;/code&gt; methods over &lt;code class=&quot;highlighter-rouge&quot;&gt;change&lt;/code&gt;, helper methods, how and what to test and plenty more.&lt;/p&gt; &lt;p&gt;On one of these in-depth conversations, we found ourselves tracing down a method definition that led us to a module using &lt;code class=&quot;highlighter-rouge&quot;&gt;ActiveSupport::Concern&lt;/code&gt;, as I’ll refer to it from here on out, a Concern.&lt;/p&gt; &lt;p&gt;I’ve used Concerns a ton over the years but it’s a topic I’ve never had to look into more than just a quick skim of the docs to check the syntax or make a few changes to an existing Concern.&lt;/p&gt; &lt;p&gt;I realized that while I knew how to use Concerns, I didn’t know enough to be able to explain all of the details without re-visiting the documentation.&lt;/p&gt; &lt;p&gt;This is something that comes up a lot in these mentorship sessions. I have a &lt;em&gt;decent&lt;/em&gt; idea how something works. When I don’t, I can usually make an educated guess based on how other things work and are set up in Rails.&lt;/p&gt; &lt;p&gt;When working with these early career devs, I always try to highlight when I don’t know something, which is well…a lot. One of the things I try to stress is you don’t have to know or memorize everything about Rails (or any other framework for that matter).&lt;/p&gt; &lt;p&gt;If you know enough to find what you’re looking for and can read over some documentation or source code and get what you need, there’s no point in memorizing a ton of methods and other documentation. You’ll start to learn and memorize the right things the more you work and figure out the things you’re having to look up consistently.&lt;/p&gt; &lt;p&gt;I thought this was a great opportunity to refresh myself on some of the details around Concerns in Rails, why they’re useful and how to implement them.&lt;/p&gt; &lt;h3 id=&quot;what-is-a-concern&quot;&gt;What is a Concern?&lt;/h3&gt; &lt;p&gt;A &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/Concern.html&quot;&gt;Concern&lt;/a&gt; is a Module with added methods that makes it easier to define instance and class methods that will be evaluated in the context of the Class they’re included into.&lt;/p&gt; &lt;p&gt;From the &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/Concern.html&quot;&gt;Rails Docs&lt;/a&gt; we can look at an example of a Concern, and what an equivalent module would look like without the extras provided by &lt;code class=&quot;highlighter-rouge&quot;&gt;ActiveSupport::Concern&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# example using ActiveSupport::Concern&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support/concern&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;M&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;included&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:disabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;disabled: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class_methods&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# plain ruby module version&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;M&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;included&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ClassMethods&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class_eval&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:disabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;disabled: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ClassMethods&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Not only does &lt;code class=&quot;highlighter-rouge&quot;&gt;ActiveSupport::Concern&lt;/code&gt; give us a cleaner interface to dynamically define methods into the classes they’re included in, it can also resolve module dependencies.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;included&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class_eval&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method_injected_by_foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Bar&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;included&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method_injected_by_foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# We need to include this dependency for Bar&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Bar&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Bar is the module that Host really needs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Bar&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;included&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;method_injected_by_foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Bar&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;why-concerns&quot;&gt;Why Concerns?&lt;/h3&gt; &lt;p&gt;Concerns make it easy to share code across different Classes and Modules. Concerns allow us to define either class methods or instance methods on the objects they will be included to.&lt;/p&gt; &lt;p&gt;Looking at the first example above, we see the Concern is defining a new scope called &lt;code class=&quot;highlighter-rouge&quot;&gt;disabled&lt;/code&gt;&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# MyConcern&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;included&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:disabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;disabled: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The code within the &lt;code class=&quot;highlighter-rouge&quot;&gt;included&lt;/code&gt; block will be added to any Class it’s included in.&lt;/p&gt; &lt;p&gt;Say we have a &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; model with a &lt;code class=&quot;highlighter-rouge&quot;&gt;disabled: boolean&lt;/code&gt; column&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyConcern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Pending that all the data is set up correctly, we would now be able to use the scope defined in the Concern&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;disabled_users = User.disabled # returns collection of Users &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;!-- Now, when we need that same functionality, we don&apos;t have to re-write or --&gt; &lt;!-- duplicate that method, we can just include the Concern --&gt; &lt;p&gt;Let’s say we would like to have that same &lt;code class=&quot;highlighter-rouge&quot;&gt;disabled&lt;/code&gt; scope available in an &lt;code class=&quot;highlighter-rouge&quot;&gt;Account&lt;/code&gt; class. Instead of defining the scope in this class, we can just include the Concern.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Account&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyConcern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;disabled_accounts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disabled&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# returns collection of Accounts&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This gives us a clean and powerful way to define logic that we’d like to share across different objects.&lt;/p&gt; &lt;p&gt;We’re also not limited to class methods, we can also define instance methods. Assuming both the &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Account&lt;/code&gt; models from above have a &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; field that’s a string.&lt;/p&gt; &lt;p&gt;Let’s say we want to format the &lt;code class=&quot;highlighter-rouge&quot;&gt;name&lt;/code&gt; field a specific way to create a filename.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FileNames&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_filename&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Inflector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parameterize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;separator: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;_&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FileNames&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Account&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FileNames&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_filename&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cody_norman&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_filename&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_business_account&quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you find yourself including the same concern many times, you may be able to include it in a parent class like ApplicationController or ApplicationRecord to make that functionality available across any child class.&lt;/p&gt; &lt;h3 id=&quot;where-to-put-your-concerns&quot;&gt;Where to put your concerns?&lt;/h3&gt; &lt;p&gt;If you look inside your &lt;code class=&quot;highlighter-rouge&quot;&gt;app/controllers&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;app/models&lt;/code&gt; directories, both have a &lt;code class=&quot;highlighter-rouge&quot;&gt;concerns&lt;/code&gt; directory.&lt;/p&gt; &lt;p&gt;If I’m not mistaken, there isn’t any Rails magic that requires them to be in a specific spot, those directories are there for organization and clarity.&lt;/p&gt; &lt;p&gt;Without overthinking it too much, if your Concern will be added into a controller, it should be in the &lt;code class=&quot;highlighter-rouge&quot;&gt;app/controllers/concerns&lt;/code&gt; directory and the same applies for models.&lt;/p&gt; &lt;p&gt;If you’re Concern is only interacting with a single model, it’s best to namespace your concern with the same name as the model you’re interacting with.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/post.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# app/models/post/foo.rb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Post::Foo&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupprt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Thanks to &lt;a href=&quot;https://github.com/kaspth&quot;&gt;Kasper Timm Hansen&lt;/a&gt; for this suggestion on namespacing!&lt;/p&gt; &lt;p&gt;When you’re working with something that interacts with both Models and Controllers and don’t want to break into smaller chunks, consider placing somewhere like &lt;code class=&quot;highlighter-rouge&quot;&gt;lib&lt;/code&gt;.&lt;/p&gt; &lt;h3 id=&quot;custom-errors&quot;&gt;Custom Errors&lt;/h3&gt; &lt;p&gt;One thing I like to add to Concerns is a custom error class I raise if there are any errors. Raising the specific error allows error handling to be more specific and explicit. Seeing something like &lt;code class=&quot;highlighter-rouge&quot;&gt;NameIsNilError&lt;/code&gt; is a lot more intuitive than something like a &lt;code class=&quot;highlighter-rouge&quot;&gt;method_missing&lt;/code&gt; error you have to dig through the stack trace for.&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FileNames&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NameIsNilError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StandardError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_filename&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NameIsNilError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unable to generate filename, name is nil&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Inflector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parameterize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;separator: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;_&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt; &lt;p&gt;There are a few different ways to test Concerns. Some people prefer doing something like a shared context for the functionality the Concern adds, others prefer testing the methods directly in the objects they’re included in.&lt;/p&gt; &lt;p&gt;There’s also the option of creating a dummy class to include your Concern into and testing that class. This is the method I go for most of the time. In the same way concerns help keep duplication to a minimum, I like this option since I’m not continuously adding testing to new objects&lt;/p&gt; &lt;p&gt;I think this is especially a good option for concerns included at a higher level like the &lt;code class=&quot;highlighter-rouge&quot;&gt;ApplicationController&lt;/code&gt; &lt;!-- make a note about custom errors then give a quick example on testing --&gt;&lt;/p&gt; &lt;p&gt;If we have a &lt;code class=&quot;highlighter-rouge&quot;&gt;Users::TimeZone&lt;/code&gt; concern included to our ApplicationController to check the user’s timezone, this is an example of including into a dummy class and testing the dummy class&lt;/p&gt; &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#controller_spec.rb&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# create a dummy class to include the concern we&apos;re testing&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MockController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeZone&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MockController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :controller&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:browser_time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;America/Los_Angeles&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# setup for cookies&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;browser_time_zone: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;browser_time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#browser_time_zone&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;browser_time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;America/Los_Angeles&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;I simplified that example a bit to make it more readable to focus on creating a class within the spec, including our subject Concern to that class and testing the Concern’s functionality.&lt;/p&gt; &lt;h3 id=&quot;closing&quot;&gt;Closing&lt;/h3&gt; &lt;p&gt;Concerns are a great way to keep your code DRY. When you find yourself re-writing or repeating similar methods across different classes, think about moving that logic to a concern. Hopefully, this post has prepared you to better handle the next time to have to update existing concerns or find an opportunity to add a new one.&lt;/p&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">Rails Concerns are a great way to keep your code DRY by making it easy to share code across Modules and Classes. The post will be going through some of the in-and-outs of Concerns, why they&apos;re useful and how to use them.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry><entry><title type="html">Upgrading your Jumpstart Pro App</title><link href="https://codynorman.com/ruby/upgrading_jumpstart_pro/" rel="alternate" type="text/html" title="Upgrading your Jumpstart Pro App"/><published>2023-07-19T17:54:46-04:00</published><updated>2023-07-19T17:54:46-04:00</updated><id>https://codynorman.com/ruby/upgrading_jumpstart_pro</id><content type="html" xml:base="https://codynorman.com/ruby/upgrading_jumpstart_pro/">&lt;p&gt;If you’re reading this, there’s a good chance this will sound familiar…&lt;/p&gt; &lt;p&gt;You’ve decided to build your latest idea for a SaaS app with &lt;a href=&quot;https://jumpstartrails.com/&quot;&gt;Jumpstart Pro&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Things are going great and you’re shipping code left and right. Then…as it tends to do, life gets in the way.&lt;/p&gt; &lt;p&gt;Sometime later, you find some time and renewed vigor to get back at it. But before getting back into development, you’d like to take advantage of any bug fixes or new features merged to Jumpstart since you initially started the app.&lt;/p&gt; &lt;p&gt;Blowing the dust off your old side project and getting things updated again can be a pretty daunting task. There’s a point most of us hit where we start wondering if the ol’ &lt;em&gt;kill it with fire&lt;/em&gt; approach is the best path forward and start thinking about scrapping what you have and starting over.&lt;/p&gt; &lt;p&gt;I’ve been there…a lot. I’ve also been on projects that go so long without being updated or maintained, it hits a point where it seems impossible or at least improbable to get approval from the higher-ups.&lt;/p&gt; &lt;p&gt;That was something I wanted to avoid when I first started building &lt;a href=&quot;https://spotsquid.com&quot; target=&quot;_blank&quot;&gt;SpotSquid&lt;/a&gt;. I wanted to take a long-term view and make sure things never get to the point of no return.&lt;/p&gt; &lt;p&gt;I have a few processes I’ve started using to keep my running SaaS app updated as a solo developer. I’ll be going through how I keep my Jumpstart Pro app updated while still shipping features and other changes.&lt;/p&gt; &lt;h3 id=&quot;dependabot&quot;&gt;Dependabot&lt;/h3&gt; &lt;p&gt;Dependabot updates have been a &lt;em&gt;huge&lt;/em&gt; help boost to keeping my app up to date. If you don’t make the time to try to stay on top of them though, they can quickly pile up and become a distraction.&lt;/p&gt; &lt;p&gt;Jumpstart Pro ships with configuration for Dependabot so you’ll start seeing these pull requests start coming in shortly after pushing to GitHub.&lt;/p&gt; &lt;p&gt;The current Jumpstart Pro default for how often Dependabot checks for updates is &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;weekly&quot;&lt;/code&gt;. If this is a little optimistic for your taste, you can change the &lt;code class=&quot;highlighter-rouge&quot;&gt;interval&lt;/code&gt; value to &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;monthly&quot;&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;If you want to go in the opposite direction, you can also configure Dependabot to check for updates &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;daily&quot;&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#scheduleinterval&quot; target=&quot;_blank&quot;&gt;Dependabot Interval Options&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file&quot; target=&quot;_blank&quot;&gt;Dependabot Configuration Options&lt;/a&gt;&lt;/p&gt; &lt;p&gt;The three &lt;code class=&quot;highlighter-rouge&quot;&gt;package-ecosystems&lt;/code&gt; we’re telling Dependabot to watch for changes are &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;github-actions&quot;&lt;/code&gt;, for the actions you’ve added and configured as part of your Build/CI process on GitHub, as well as &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;npm&quot;&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;bundler&quot;&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;This will check NPM for updates to the JS packages and Bundler for ruby gem updates and open a pull request to our app bumping the versions.&lt;/p&gt; &lt;h4 id=&quot;merging-changes&quot;&gt;Merging changes&lt;/h4&gt; &lt;p&gt;&lt;img class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;https://personal-blog-assets.s3.amazonaws.com/DependabotPullRequests.png&quot; /&gt;&lt;/p&gt; &lt;p&gt;This is an example of what you’ll see when you view the Dependabot pull requests in GitHub.&lt;/p&gt; &lt;p&gt;You’ll notice some nice labels are showing if each one of these is a dependency update and if it’s for JS or Ruby. You may also notice the failing builds, I haven’t had time or desire to fix that but I think this process can be pretty close to automatic where it merges after tests pass if you choose to do so.&lt;/p&gt; &lt;p&gt;One option is to merge each of these pull requests individually. This is how I was doing things at first, but it was a little time-consuming. Also, if any issues popped up, it was hard to pinpoint exactly where the problems were introduced.&lt;/p&gt; &lt;p&gt;What I’ve found works best for me is to create a new branch to merge the gem updates into and one to merge the JS updates into.&lt;/p&gt; &lt;p&gt;I used to only do a single upgrade branch but after running into a couple of issues with JS package updates, I keep those separate from &lt;em&gt;everything else&lt;/em&gt; to be able to point a finger in the right direction if something goes wrong.&lt;/p&gt; &lt;p&gt;This way, if and when something does go wrong, you don’t have to worry about reverting it right away. You can just delete or ignore the upgrade branch or come back to it when you have more time.&lt;/p&gt; &lt;p&gt;Especially when this is a side-project, you may have hard stops on your time, this keeps your app from getting to a broken state so the next time you have some free time to work on it, you’ll spend it just getting things running again.&lt;/p&gt; &lt;p&gt;I start with the Ruby gems since that’s where I have the most experience with upgrades.&lt;/p&gt; &lt;p&gt;To start, I create a new branch from &lt;code class=&quot;highlighter-rouge&quot;&gt;main&lt;/code&gt; using something like:&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;upgrades/ruby/07-19-23&lt;/code&gt;&lt;/p&gt; &lt;p&gt;This branch can be named anything but it’s not uncommon to have to change gears or run out of time in the middle of things so this is something that helps give me more context when I come back to things.&lt;/p&gt; &lt;p&gt;Then I push this branch up to GitHub, update the target branch for each gem upgrade PR to my upgrade branch &lt;code class=&quot;highlighter-rouge&quot;&gt;upgrades/ruby/07-19-23&lt;/code&gt; and merge the changes.&lt;/p&gt; &lt;p&gt;After merging the updates to the upgrade branch, I pull down that branch and do some quick checks to make sure everything is working. I also rely on my test suite to let me know when things go south, but running things locally before merging makes sure when you come back to things, everything starts and works. Things like conflicts with local dependencies or gems that haven’t released a patch for known issues are all little snags you can hit that slow you down and steal precious time from your side project.&lt;/p&gt; &lt;p&gt;If everything looks good locally and all my tests pass on CI, I merge the changes and move on.&lt;/p&gt; &lt;p&gt;&lt;em&gt;If&lt;/em&gt; things do go south, at least I know it’s an issue with some of the recent gems and can either&lt;/p&gt; &lt;p&gt;A) revert the changes and try to merge each gem individually to try to find where the problems are being introduced&lt;/p&gt; &lt;p&gt;or&lt;/p&gt; &lt;p&gt;B) Declare upgrade bankruptcy on this one and get try again the next round of updates. If you’re on the monthly interval, that might be a little more time than you’d like to put things off. The important thing is to find what works best for &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;Another big perk of merging things in groups like this is those issues are contained within the upgrade branch instead of merged into your main branch where you feel like you have to try to frantically fix a broken deployment.&lt;/p&gt; &lt;p&gt;Then I repeat the same process for the JS packages. I’m not pointing fingers but the JS packages seem more likely to introduce breaking changes in minor versions so I &lt;em&gt;really&lt;/em&gt; try to keep these separate and make sure everything is working as a whole before merging.&lt;/p&gt; &lt;p&gt;Having a set of steps and processes for merging these updates lowers some of the friction to upgrading and makes it a little more enticing and manageable. Especially if you’re working solo, you have to get the most out of the limited time you have.&lt;/p&gt; &lt;p&gt;I would say currently, if most of the version bumps are pretty small, it takes me about 15-20 mins per week to keep everything updated.&lt;/p&gt; &lt;h3 id=&quot;scheduled-reminders-and-recurring-tasks&quot;&gt;Scheduled reminders and recurring tasks&lt;/h3&gt; &lt;p&gt;Depending on your notifications, you probably see something when Dependabot opens a new pull request. However, this will &lt;em&gt;not&lt;/em&gt; let you know when there are updates to the upstream repo (the repo you generated your Jumpstart Pro app from). It’s easy to forget about this and let your versions drift further and further from upstream. The longer it goes, the more involved the upgrade process can be.&lt;/p&gt; &lt;p&gt;As a way of gently nudging and reminding myself to upgrade, I have a monthly Slack reminder to ‘Check on upgrades’. That’s kind of a placeholder for Dependabot updates if they’re not already covered &lt;em&gt;and&lt;/em&gt; to check on the upstream changes from Jumpstart Pro.&lt;/p&gt; &lt;p&gt;I also have a recurring Trello card that gets created once per month to at least &lt;em&gt;check&lt;/em&gt; on the upgrades.&lt;/p&gt; &lt;p&gt;I don’t always have the time to upgrade my application as much as I like, but at least this way, I can stay updated with the changes and plan accordingly. An example of this was seeing there were updates to the Pay gem sending subscription reminders which was something I was planning on working on but was able to focus on other areas and pull the updates once they were merged.&lt;/p&gt; &lt;p&gt;It also helped to re-frame how I thought about these updates. Instead of thinking of pulling upstream changes as a chore, I started thinking about it more along the lines of leveraging what’s available to me.&lt;/p&gt; &lt;p&gt;Pulling these updates and staying on top of the changes allows you to take full advantage of the work being done to Jumpstart. Thinking of it that way as a time-strapped solo founder, why &lt;em&gt;wouldn’t&lt;/em&gt; I want the GoRails team and other contributors porting changes that update and enhance my app?&lt;/p&gt; &lt;p&gt;Thinking of it as a perk instead of a chore made it sit a little better with me.&lt;/p&gt; &lt;h3 id=&quot;upstream-changes&quot;&gt;Upstream Changes&lt;/h3&gt; &lt;p&gt;I’ve done quite a few upgrades to Jumpstart at this point and this is the process I’ve ended up with. Feel free to tweak as needed but I think having a process or some rules for how you approach these things saves your mental bandwidth for the interesting challenges your app is tackling. Finding a flow and set of steps that works well for you is important to making this something that’s repeatable and not so draining.&lt;/p&gt; &lt;p&gt;These steps are assuming you’ve set up your local repo according to the &lt;a href=&quot;https://jumpstartrails.com/docs/upgrading&quot;&gt;Jumpstart Pro Docs&lt;/a&gt; in particular &lt;code class=&quot;highlighter-rouge&quot;&gt;git remote rename origin jumpstart&lt;/code&gt;&lt;/p&gt; &lt;p&gt;First, I pull the latest changes from upstream (the main Jumpstart Pro repo).&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git fetch jumpstart &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;&lt;img class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;https://personal-blog-assets.s3.amazonaws.com/JumpstartUpstreamFetch.png&quot; /&gt;&lt;/p&gt; &lt;p&gt;Then, I create a branch in the same way I do for the gem updates&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git checkout &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; upgrades/july-19 &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;I don’t usually have any other open upgrade branches while updating Jumpstarst so I don’t include anything noting this is for upstream changes but that might help.&lt;/p&gt; &lt;p&gt;After creating my upgrade branch, I merge the &lt;code class=&quot;highlighter-rouge&quot;&gt;jumpstart/main&lt;/code&gt; branch into my upgrade branch and push to GitHub.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git merge jumpstart/main &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you’ve been updating regularly, this is usually pretty smooth. You may have to resolve some conflicts before completing the merge. If it’s only a handful of conflicts and/or you feel comfortable resolving conflicts once everything is resolved and you’ve merged your branch you can push that branch up to GitHub to start comparing the changes.&lt;/p&gt; &lt;p&gt;If it’s been more than a couple of months since you’ve pulled updates from the upstream version of Jumpstart Pro, you’ll probably have a lot of conflicts to resolve. If you don’t want to tackle all of those conflicts at once, this is my process for making that a little easier.&lt;/p&gt; &lt;p&gt;When I need more time and more clarity to work through all the changes, I will create a branch with the code from Jumpstart upstream main branch. Then push that to GitHub and open a pull request to an upgrade branch or my main branch (depending on how brave I’m feeling).&lt;/p&gt; &lt;p&gt;Fetch the latest version on the Jumpstart remote and checkout the main branch&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git fetch jumpstart $ git checkout jumpstart/main &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You’ll see a notice about ‘detached HEAD’ which is read-only. We need to switch branches so we’ll be able to make commits.&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git switch -c jumpstart-updates-july &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This gives us a local branch with the code we were trying to merge with &lt;code class=&quot;highlighter-rouge&quot;&gt;git merge jumpstart/main&lt;/code&gt;&lt;/p&gt; &lt;p&gt;Push the new branch to GitHub&lt;/p&gt; &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git push origin jumpstart-updates-july &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;After pushing, I open a pull request on GitHub from the upgrade branch to the main branch. This gives me a nice visual diff of all the changes in a single spot. I’ve found this is much easier for me to digest than trying to do everything locally in Git.&lt;/p&gt; &lt;p&gt;It’s also helpful to mark the changes you’re comfortable with as ‘Viewed’ to help remind you of the files you’ve already reviewed if (and when) you have to come back to this and finish up.&lt;/p&gt; &lt;p&gt;(Screenshots of Diffs and collapsed changes) &lt;img class=&quot;position-relative mx-auto rounded w-100 shadow-lg&quot; src=&quot;https://personal-blog-assets.s3.amazonaws.com/JumpstartProUpstreamDiff.png&quot; /&gt;&lt;/p&gt; &lt;p&gt;This gives you a nice visual to see all the changes. After reviewing some of the changes you may want to do something like “just accept all the changes in lib” and “ignore the changes in app/views”. That’s really up to you and how you prefer to do things.&lt;/p&gt; &lt;p&gt;For me, I’m not actively working or making changes to &lt;code class=&quot;highlighter-rouge&quot;&gt;lib&lt;/code&gt; so feel ok marking those as reviewed and generally ignore all the changes to my views and cherry-pick or copy-paste any changes I’d like to include.&lt;/p&gt; &lt;p&gt;Resolving conflicts is beyond the scope of this post but the &lt;code class=&quot;highlighter-rouge&quot;&gt;--ours&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;--theirs&lt;/code&gt; flags for git can be a big help. You can read some more info on those &lt;a href=&quot;https://howchoo.com/git/git-merge-conflicts-rebase-ours-theirs&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;&lt;/p&gt; &lt;p&gt;As mentioned before, keeping this potentially hairy upgrade within its own branch giving you plenty of time to review and test before merging allows you to skip it entirely or pick back up where you left off whenever you run out of time. Merging the upgrade branch into your main branch this way also gives you the option to revert those changes with a single click.&lt;/p&gt; &lt;p&gt;The worst thing as a solo developer is being stuck trying to get my app running whenever I have time to work on it. Quarantining your upgrades lets you keep rolling as needed.&lt;/p&gt; &lt;p&gt;After running the upgrade branch locally and making sure everything seems to be in order, after feeling pretty comfortable about the changes I’ll merge and deploy to my staging server.&lt;/p&gt; &lt;p&gt;If everything looks good, or at least nothing looks bad, I deploy those changes to production.&lt;/p&gt; &lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt; &lt;p&gt;I wouldn’t say I &lt;em&gt;love&lt;/em&gt; all of the upgrading I’ve been doing but I will say it’s been a huge help in keeping my apps up to date and finding ways to help myself and others keep theirs updated as well.&lt;/p&gt; &lt;h3 id=&quot;video&quot;&gt;Video&lt;/h3&gt; &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/eYTCpvrYjFI?si=Ek4i4orMPwjTmJT2&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;</content><author><name>Cody Norman</name></author><category term="ruby"/><summary type="html">An in-depth look at how I keep my Jumpstart Pro SaaS apps updated</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codynorman.com/images/cropped_headshot.png"/><media:content medium="image" url="https://codynorman.com/images/cropped_headshot.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry></feed>