Telex
Email for our Rails apps through Lettermint, a Dutch provider with servers in the EU.
-
Sending: a
:lettermintdelivery method for Action Mailer, over Lettermint's SMTP relay. Mailers, previews,deliver_laterand gems that send mail (Devise…) work as usual. - Receiving: a Lettermint ingress for Action Mailbox, next to the ones Rails ships for Postmark or Mailgun. Lettermint posts a signed webhook with a link to the original message; Telex checks the signature, downloads the message in a job and hands it to Action Mailbox, which routes it to the app's mailboxes.
Each app has its own Lettermint project: its own API token, logs, webhook and receiving subdomain (reply.hq.box.paris, reply.ticket.box.paris…).
Install in an app
# Gemfile
gem "telex"Or gem "telex", github: "boxprod/telex" to follow main between releases.
# bin/rails credentials:edit
lettermint:
api_token: lm_... # the project's API token, also the SMTP password
webhook_secret: whsec_... # the inbound route's webhook secret, whsec_ includedOr LETTERMINT_API_TOKEN and LETTERMINT_WEBHOOK_SECRET in the environment.
Sending
# config/environments/production.rb
config.action_mailer.delivery_method = :lettermintThe sending domain must be verified in the Lettermint project (SPF, DKIM, return path). Lettermint's SMTP headers (X-Lettermint-Route, X-LM-Tag, X-LM-Metadata-*…) can be set in a mailer with headers[...].
Receiving
Install Action Mailbox if the app does not have it yet (it needs Active Storage). Without it, Telex only sends:
bin/rails action_mailbox:install db:migrate# config/environments/production.rb
config.action_mailbox.ingress = :lettermintIn the Lettermint project, add an inbound route on the receiving subdomain (its MX records point to Lettermint) with the webhook
https://<the app>/rails/action_mailbox/lettermint/inbound_emails
Then route messages in app/mailboxes/application_mailbox.rb as with any Action Mailbox app. Inbound needs Lettermint's Starter plan or above.
What Telex adds to each message before Action Mailbox sees it:
| Header | |
|---|---|
X-Original-To |
The address it was delivered to, which Action Mailbox routes on even when it is not in To or Cc |
X-Lettermint-Spam |
yes or no, Lettermint's verdict; nothing is dropped |
X-Lettermint-Spam-Score |
Lettermint's score |
How it behaves
- A wrong or missing signature, or one more than 5 minutes old, gets a
401. Other events (deliveries, bounces) get a204and are ignored for now. - The webhook answers once the download is queued. If the app cannot queue it, Lettermint gets a
500and tries again. - The job tries again for several hours when Lettermint cannot be reached, and gives up at once if the link has expired (28 days after the message arrived).
- A message that arrives twice is kept once: Action Mailbox ignores a source it already has.
- The links are secrets: the webhook's
datais filtered from the request log and the job does not log its arguments.
Testing in an app
To test a mailbox, Action Mailbox's own receive_inbound_email_from_mail is the simplest. To test the webhook end to end:
require "telex/test_helper"
class InboundTest < ActionDispatch::IntegrationTest
include Telex::TestHelper
test "a reply reaches its quote" do
body = lettermint_inbound_payload(recipient: "reply+quote-12@reply.hq.box.paris")
post rails_lettermint_inbound_emails_path, params: body, headers: lettermint_webhook_headers(body)
# Telex.download = an object answering #fetch(url) with the message's source, to run the job without the network
end
endNot done yet
- Delivery events (bounces, complaints) are ignored. They would come through the same webhook.
- Whether Lettermint keeps the Message-ID Rails generates, which replies point to in
In-Reply-To: Lettermint has anX-LM-Preserve-Message-IDheader for it, to check against a real reply before relying on threading.
Development
bundle install
bin/rails db:migrate
bin/rails testTo release, bump lib/telex/version.rb, commit, then bundle exec rake release: it tags, pushes the tag and pushes the gem to RubyGems, asking for a one-time code.