In the past, I built several e-Mail processing features using Ruby that predate the official ActionMailbox
- Such as: Bounce processing, e-mail notifications, newsletters, order confirmations, forwardings etc.
So before, I always used a IMAP client run by a great mail_room
to fetch emails from a mailbox and process them by directly supplying them to Sidekiq queue. The internal Mail routing (Which email should be processed and shown to which customer etc.) was handled internal, as well as bounces etc.
So it was time, that I try out the whole ActionMailbox stack instead, which is a more standardized solution and has great testing support.
We are using Postfix so handle our own e-mail servers - set up, using Mail-In-A-Box. Here I will explain, how to add an additional alias to postfix alongside your existing mail-aliases.
Rails / ActionMailbox Setup
ActionMailbox is installed by default in newer Rails apps. If you do not use require "rails/all"
you might have to require it manually in your application.rb
:
require "action_mailbox/engine"
Enable relay ingress in the environment, where you want to process/test it config/environments/development.rb
/ production.rb
:
config.action_mailbox.ingress = :relay
If you want to test it in development, you need to find a way to make your server reachable from the mail receiving server - VPN, Proxy, or remote/cloud development machine. In our case, we are using development machines in the cloud, so we can just use the public hostname per app.
Now, add a default routing, in our example we want to handle everything sent to input@
, but you can later add identification markers, such as input+9332eas@
or even per-customer aliases.
# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
routing /input@/i => :support_input
# routing /something/i => :somewhere
end
# app/mailboxes/support_input_mailbox.rb
class SupportInputMailbox < ApplicationMailbox
def process
# do something with `mail` or `inbound_email`
binding.irb
end
end
Also, set a secret credential for ingress_password
BTW: you can run rails secret
to generate a secret key any time.
# rails credentials:edit --environment development
action_mailbox:
ingress_password: 0565be1430a7f2868f123ofoaosdjosiio23eoiqoio23o123p120493i90ASDihquwdas
Don’t forget to restart the server.
Postfix Setup
As mentioned, we have an existing alias-mapping supplied by a sqlite3 database (Managed by Mail-In-A-Box). But, postfix supports having multiple virtual_alias_maps
and will look up from the left, until any DB has a match:
# before:
virtual_alias_maps=sqlite:/etc/postfix/virtual-alias-maps.cf
# after:
virtual_alias_maps=sqlite:/etc/postfix/virtual-alias-maps.cf,regexp:/etc/postfix/app_postback
And create the file /etc/postfix/app_postback
file with the mapping of emails to a new alias
. Here you can also use regex to match wildcard customer mails or process identification codes (order-id, customer-id, etc.)
/^input@mydomain.de/ upload_to_dev@localhost
/^bounce\+.*@mydomain.de$/ upload_to_dev@localhost
/^customer-.*@mydomain.de$/ upload_to_dev@localhost
Anytime you change the /etc/postfix/app_postback
you need to run the postmap
command to update the regexp file:
postmap /etc/postfix/app_postback
You can test the matching against an email address by using the postmap
with the -q
flag:
$ postmap -q "bounce+29d9@domain.de" regexp:/etc/postfix/app_postback
# if you get upload_to_dev@localhost, it's working, otherwise the output will be empty
> upload_to_dev@localhost
Now we also need to define the upload_to_dev
alias in /etc/aliases
:
upload_to_dev: "|/usr/bin/curl -u actionmailbox:0565be1430a7f2868f123ofoaosdjosiio23eoiqoio23o123p120493i90ASDihquwdas -H 'Content-Type: message/rfc822' --data-binary @- http://my-cloud-dev-instance.com/rails/action_mailbox/relay/inbound_emails"
And, this file to, needs to be hashed by postfix every single time you change it:
postalias /etc/aliases
service postfix reload
Now, tail the mail.log, send a testmail and wait the binding.irb to show up. If you are using ActiveJob adapters in development, you will also need to start the processor (sidekiq/solid-queue), because the mail is always processed asynchroneously.
Improving failure mode
When your Rails-app is down, the mail will be lost, as there is no retry mechanism in place. The sending party will probably receive a DSN (Delivery Status Notification) by Postfix, though.
We could improve this by wrapping the curl command in a small script, that outputs a “temporary delivery error” on CURL failure, so Postfix responds to the sending e-mail server with a temporary failure, instead of a permanent. Most e-mail server will then retry delivery later on.
#!/bin/bash
/usr/bin/curl -u actionmailbox:0565be1430a7f2868f811fbf82543642975a0a08b6eff1d812d6096a0626 \
-H 'Content-Type: message/rfc822' \
-f --silent \
--data-binary @- \
http://dev.our-app.com/rails/action_mailbox/relay/inbound_emails
if [[ $? -ne 0 ]]; then
# Print a 4.4.1 temporary failure message for Postfix logging
echo "4.4.1 Temporary failure: Unable to reach the Action Mailbox server"
exit 1 # Signal a temporary failure to Postfix
fi
echo "2.0.0 Successfully delivered"
exit 0
vim /etc/postfix/upload_to_dev
chmod +x /etc/postfix/upload_to_dev
And change the alias to:
upload_to_dev: "|/etc/postfix/upload_to_dev"
Don’t forget to run postalias /etc/aliases
and service postfix reload
Alternatively, you can also try:
- ActionMailbox supplies a rake task for importing mails from command line, that does the processing very similarly, but using Ruby. That requires a functioning Ruby + Rails environment on the mail-server
- ActionMailbox Docker Relay - instead of using Postfix, you can run this. It’s running as a SMTP server and forwards all mail to your Rails app.
- Loumaris/ActionMailboxDockerPostfixRelay - seems to be similar to the above.