Going native with ActionMailbox
Going native with ActionMailbox
Concurrently to my recent job search, I embarked on a new hobby project called Laterbox. It’s a web application where one can bookmark all the content - blog articles, videos, websites - one finds online, and have an inbox of sorts (namely the laterbox) to group all resources one has yet to checkout.
The idea is not new, and there’s already a ton of existing tools out there that achieve a similar goal. However none of them clicked with me, so I decided to build my own. Plus it was a perfect excuse to start a new Rails project, and test all the cool new features the eighth major version of the framework had to offer: Authentication template (more about it here), the Solid Trifecta, production SQLite support and Kamal 2 to name the main ones.
But the subject of this article is not a shiny new Rails 8 library, rather a grandfather of the framework introduced in Rails 6: ActionMailbox. To the neophyte, ActionMailbox helps with inbound emails, by handling incoming webhooks from your email service, parsing and storing emails and offering a controller-like structure to handle the work to be done.
In my opinion, ActionMailbox has the most underrated fun-to-feature ratio. Some have already demonstrated it already, either by starting up their truck by the send of an email, or literaly setting emails on fire. This is a neat little library that can do a LOT. No suprise here then, that as soon as I had the chance to work with it on Laterbox, I jumped on the occasion.
The feature
Laterbox helps with bookmarking resources you find online. The main entrypoint to the app is the resource form, used to save a new resource in your laterbox:
This works really well for in-app experience, however it does not fit the “real” flow of discovering new content online. Most content is found either in another app, in an email newsletter, or browsing the internet on your phone. Having to copy paste the link between apps make this quite tedious and don’t add up to a great experience. The answer, as you might have guessed it, would be to go mobile, and build a native share feature (ShareExtension on iOS or Sharesheet on Android), similarly to the apps featured in the menu below on iOS:
Hotwire native seems promising, but I am guessing there’s a bit of a learning curve here and I don’t have the bandwith available to build an experiste of the matter. Looking more closely at the available apps in the share menu, I realised something: what if I could use my email application to save resources on Laterbox? ActionMailbox has entered the room,
Building the feature
Note: I won’t cover ActionMailbox configuration for your email service, as it’s pretty well covered in the official guides and other blog articles and conference talks.
Each user in Laterbox gets an inbound email address, of the form username_eufdnt45@inbound.laterbox.io
. This is the email address that needs to be sent to when sharing a resource via email:
Every email sent to @inbound.laterbox.io
is routed to a dedicated InboundsMailbox
:
# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
routing /@inbound\./i => :inbounds
end
The Inbounds mailbox’ role is to retrieve the user for the corresponding inbound email address, extract urls from the body and create resources if there are any:
# app/mailboxes/inbounds_mailbox.rb
class InboundsMailbox < ApplicationMailbox
before_processing :require_user
def process
extracted_urls = InboundEmailParser.new(mail.body.decoded).urls
resource_title = mail.subject || "New resource"
extracted_urls.each_with_index do |url|
user.resources.create!(
name: resource_title,
url: url,
source: "email"
)
end
end
private
def require_user
bounced! unless user
end
def user
@user ||= InboundEmailAddress.find_by(email_address: mail.to)&.user
end
end
A few intersting notes on the mailbox code:
- We use
bounced!
to stop processing the email if a corresponding user for the inbound email address does not exist - The mail share extension will populate the mail subject with the resource page title, which is pretty handy !
The InboundEmailParser.new
is also fairly simple, thanks to Ruby’s URI
module and its extract
method (we only store https
resources on Laterbox, hence the scheme validation):
class InboundEmailParser
def initialize(*body*)
@body = *body*
end
def urls
extract_urls
end
private
def extract_urls
URI.extract(@body, [ "https" ])
end
end
And that’s it! Now resources can be saved natively from any mobile photo with email capabilities. This code took a couple of hours of work for implementation, testing and release. Not bad for a feature that adds native capabilities to a web app and big improvement in user experience! Once again, ActionMailbox proved its value by offering a simple and famaliar controller-like interface to inbound emails.