[![CI Build][🚎dl-cwfi]][🚎dl-cwf]
[![Test Coverage][🔑cc-covi]][🔑cc-cov]
[![Maintainability][🔑cc-mnti]][🔑cc-mnt]
[![Depfu][🔑depfui]][🔑depfu]
[🚎dl-cwf]: https://github.com/pboling/sanitize_email/actions/workflows/supported.yml
[🚎dl-cwfi]: https://github.com/pboling/sanitize_email/actions/workflows/supported.yml/badge.svg
[comment]: <> ( 🔑 KEYED LINKS )
[🔑cc-mnt]: https://codeclimate.com/github/pboling/sanitize_email/maintainability
[🔑cc-mnti]: https://api.codeclimate.com/v1/badges/65af4948d859903a0372/maintainability
[🔑cc-cov]: https://codeclimate.com/github/pboling/sanitize_email/test_coverage
[🔑cc-covi]: https://api.codeclimate.com/v1/badges/65af4948d859903a0372/test_coverage
[🔑depfu]: https://depfu.com/github/pboling/sanitize_email
[🔑depfui]: https://badges.depfu.com/badges/bba430e8f19a2ba3273fb20d5e8c82d6/count.svg
-----
[![Liberapay Patrons][⛳liberapay-img]][⛳liberapay]
[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor]
[⛳liberapay-img]: https://img.shields.io/liberapay/patrons/pboling.svg?logo=liberapay
[⛳liberapay]: https://liberapay.com/pboling/donate
[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
[🖇sponsor]: https://github.com/sponsors/pboling
This gem allows you to override your mail delivery settings, globally or in a local context.
It is like a Ruby encrusted condom for your email server,
just in case it decides to have intercourse with other servers via sundry mail protocols.
Seriously though, this gem solves similar problems as the excellent [`mailcatcher`](https://mailcatcher.me/) gem,
and mailcatcher solves those problems far more easily.
In addition, this gem solves problems that mailcatcher does not solve. I recommend using both!
To make an analogy, `mailcatcher` is akin to `webmock`, entirely preventing interaction with your real live mail server,
while this gem allows you to effectively use your real live (production!) mail server, while
intercepting and modifying recipeients on the way out, so that testing emails go to safe locations.
It is a bit like using the "test" Visa credit card number `4701322211111234` with a real payment gateway.
## Encryption
Making special note of this use case because it is important for companies working on HIPAA-compliant products.
When you are sending emails through an encrypted email provider, e.g. [Paubox](https://www.paubox.com/),
testing your email in the aforementioned `mailcatcher` may not be enough.
If you want to test all the way through Paubox's system, but have the email go to a safe testing account address,
then this is the gem for you.
## Compatibility
- ⚙️ Compatible with all versions of Ruby >= 2.3, plus JRuby and Truffleruby.
- ⚙️ Compatible with all Ruby web Frameworks (Hanami, Roda, Sinatra, Rails).
- ⚙️ Compatible with all versions of Rails from 3.0 - 7.1+.
- ⚙️ Compatible with scripted usage of Mail gem outside a web framework.
- ⚙️ Compatible with [`sendgrid-actionmailer`](https://github.com/eddiezane/sendgrid-actionmailer)'s support for personalizations, and will override email addresses there according to the configuration.
- ⚙️ If this gem is not compatible with your use case, and you'd like it to be, I'd like to hear about it!
It was a slog getting (very nearly) the entire compatibility matrix working with Github Actions, [`appraisal`](https://github.com/thoughtbot/appraisal), and [`combustion`](https://github.com/pat/combustion), and I'm very interested in hearing about ways to improve it!
## 🛞 DVCS
This project does not trust any one version control system,
so it abides the principles of ["Distributed Version Control Systems"][💎d-in-dvcs]
Find this project on:
| Any | Of | These | DVCS |
|----------------|------------------|----------------|----------------|
| [🐙hub][🐙hub] | [🧊berg][🧊berg] | [🛖hut][🛖hut] | [🧪lab][🧪lab] |
[comment]: <> ( DVCS LINKS )
[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
[🧊berg]: https://codeberg.org/pboling/sanitize_email
[🐙hub]: https://gitlab.com/pboling/sanitize_email
[🛖hut]: https://sr.ht/~galtzo/pboling/sanitize_email
[🧪lab]: https://gitlab.com/pboling/sanitize_email
| | Project | bundle add sanitize_email |
|:----|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1️⃣ | name, license, docs, standards | [![RubyGems.org][⛳️name-img]][⛳️gem] [![License: MIT][🖇src-license-img]][🖇src-license] [![RubyDoc.info][🚎yard-img]][🚎yard] [![YARD Documentation](http://inch-ci.org/github/pboling/sanitize_email.svg)][🚎yard] [![SemVer 2.0.0][🧮semver-img]][🧮semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] |
| 2️⃣ | version & activity | [![Gem Version][⛳️version-img]][⛳️gem] [![Total Downloads][🖇DL-total-img]][⛳️gem] [![Download Rank][🏘DL-rank-img]][⛳️gem] [![Source Code][🚎src-main-img]][🚎src-main] [![Open PRs][🖐prs-o-img]][🖐prs-o] [![Closed PRs][🧮prs-c-img]][🧮prs-c] |
| 3️⃣ | maintenance & linting | [![Maintainability][🔑cc-mnti]][🔑cc-mnt] [![Helpers][🖇triage-help-img]][🖇triage-help] [![Depfu][🔑depfui]][🔑depfu] [![Contributors][🚎contributors-img]][🚎contributors] [![Style][🖐style-wf-img]][🖐style-wf] |
| 4️⃣ | testing | [![Supported][🏘sup-wf-img]][🏘sup-wf] [![Heads][🚎heads-wf-img]][🚎heads-wf] [![Heads][🖐uns-wf-img]][🖐uns-wf] |
| 5️⃣ | coverage & security | [![CodeClimate][🔑cc-covi]][🔑cc-cov] [![CodeCov][🖇codecov-img♻️]][🖇codecov] [![Coveralls][🏘coveralls-img]][🏘coveralls] [![Security Policy][🚎sec-pol-img]][🚎sec-pol] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Code Coverage][🧮cov-wf-img]][🧮cov-wf] |
| 6️⃣ | resources | [![Get help on Codementor][🖇codementor-img]][🖇codementor] [![Chat][🏘chat-img]][🏘chat] [![Blog][🚎blog-img]][🚎blog] [![Wiki][🖐wiki-img]][🖐wiki] |
| 7️⃣ | `...` 💖 | [![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] [![Sponsor Me][🖇sponsor-img]][🖇sponsor] [![Follow Me on LinkedIn][🖇linkedin-img]][🖇linkedin] [![Find Me on WellFound:][✌️wellfound-img]][✌️wellfound] [![Find Me on CrunchBase][💲crunchbase-img]][💲crunchbase] [![My LinkTree][🌳linktree-img]][🌳linktree] [![Follow Me on Ruby.Social][🐘ruby-mast-img]][🐘ruby-mast] [![Tweet @ Peter][🐦tweet-img]][🐦tweet] [💻][coderme] [🌏][aboutme] |
[⛳️gem]: https://rubygems.org/gems/sanitize_email
[⛳️name-img]: https://img.shields.io/badge/name-sanitize__email-brightgreen.svg?style=flat
[🖇src-license]: https://opensource.org/licenses/MIT
[🖇src-license-img]: https://img.shields.io/badge/License-MIT-green.svg
[🚎yard]: https://www.rubydoc.info/gems/sanitize_email
[🚎yard-img]: https://img.shields.io/badge/documentation-rubydoc-brightgreen.svg?style=flat
[🧮semver]: http://semver.org/
[🧮semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat
[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat
[⛳️version-img]: http://img.shields.io/gem/v/sanitize_email.svg
[🖇DL-total-img]: https://img.shields.io/gem/dt/sanitize_email.svg
[🏘DL-rank-img]: https://img.shields.io/gem/rt/sanitize_email.svg
[🚎src-main]: https://gitlab.com/pboling/sanitize_email
[🚎src-main-img]: https://img.shields.io/badge/source-gitlab-brightgreen.svg?style=flat
[🖐prs-o]: https://gitlab.com/pboling/sanitize_email/-/merge_requests
[🖐prs-o-img]: https://img.shields.io/github/issues-pr/pboling/sanitize_email
[🧮prs-c]: https://github.com/pboling/sanitize_email/pulls?q=is%3Apr+is%3Aclosed
[🧮prs-c-img]: https://img.shields.io/github/issues-pr-closed/pboling/sanitize_email
[🖇triage-help]: https://www.codetriage.com/pboling/sanitize_email
[🖇triage-help-img]: https://www.codetriage.com/pboling/sanitize_email/badges/users.svg
[🚎contributors]: https://gitlab.com/pboling/sanitize_email/-/graphs/main
[🚎contributors-img]: https://img.shields.io/github/contributors-anon/pboling/sanitize_email
[🖐style-wf]: https://github.com/pboling/sanitize_email/actions/workflows/style.yml
[🖐style-wf-img]: https://github.com/pboling/sanitize_email/actions/workflows/style.yml/badge.svg
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
[🧮kloc-img]: https://img.shields.io/tokei/lines/github.com/pboling/sanitize_email
[🏘sup-wf]: https://github.com/pboling/sanitize_email/actions/workflows/supported.yml
[🏘sup-wf-img]: https://github.com/pboling/sanitize_email/actions/workflows/supported.yml/badge.svg
[🚎heads-wf]: https://github.com/pboling/sanitize_email/actions/workflows/heads.yml
[🚎heads-wf-img]: https://github.com/pboling/sanitize_email/actions/workflows/heads.yml/badge.svg
[🖐uns-wf]: https://github.com/pboling/sanitize_email/actions/workflows/unsupported.yml
[🖐uns-wf-img]: https://github.com/pboling/sanitize_email/actions/workflows/unsupported.yml/badge.svg
[🖇codecov-img♻️]: https://codecov.io/gh/pboling/sanitize_email/graph/badge.svg?token=Joire8DbSW
[🖇codecov]: https://codecov.io/gh/pboling/sanitize_email
[🏘coveralls]: https://coveralls.io/github/pboling/sanitize_email?branch=main
[🏘coveralls-img]: https://coveralls.io/repos/github/pboling/sanitize_email/badge.svg?branch=main
[🚎sec-pol]: https://gitlab.com/pboling/sanitize_email/-/blob/main/SECURITY.md
[🚎sec-pol-img]: https://img.shields.io/badge/security-policy-brightgreen.svg?style=flat
[🖐codeQL]: https://github.com/pboling/sanitize_email/security/code-scanning
[🖐codeQL-img]: https://github.com/pboling/sanitize_email/actions/workflows/codeql-analysis.yml/badge.svg
[🧮cov-wf]: https://github.com/pboling/sanitize_email/actions/workflows/coverage.yml
[🧮cov-wf-img]: https://github.com/pboling/sanitize_email/actions/workflows/coverage.yml/badge.svg
[🖇codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
[🖇codementor-img]: https://cdn.codementor.io/badges/get_help_github.svg
[🏘chat]: https://gitter.im/pboling/sanitize_email
[🏘chat-img]: https://img.shields.io/gitter/room/pboling/sanitize_email.svg
[🚎blog]: http://www.railsbling.com/tags/sanitize_email/
[🚎blog-img]: https://img.shields.io/badge/blog-railsbling-brightgreen.svg?style=flat
[🖐wiki]: https://gitlab.com/pboling/sanitize_email/-/wikis/home
[🖐wiki-img]: https://img.shields.io/badge/wiki-examples-brightgreen.svg?style=flat
[🐦tweet-img]: https://img.shields.io/twitter/follow/galtzo.svg?style=social&label=Follow%20%40galtzo
[🐦tweet]: http://twitter.com/galtzo
[🚎blog]: http://www.railsbling.com/tags/debug_logging/
[🚎blog-img]: https://img.shields.io/badge/blog-railsbling-brightgreen.svg?style=flat
[🖇linkedin]: http://www.linkedin.com/in/peterboling
[🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-blue?style=plastic&logo=linkedin
[✌️wellfound]: https://angel.co/u/peter-boling
[✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=plastic&logo=wellfound
[💲crunchbase]: https://www.crunchbase.com/person/peter-boling
[💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=plastic&logo=crunchbase
[🐘ruby-mast]: https://ruby.social/@galtzo
[🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=plastic&logo=mastodon&label=Ruby%20%40galtzo
[🌳linktree]: https://linktr.ee/galtzo
[🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=plastic&logo=linktree
[aboutme]: https://about.me/peter.boling
[coderme]: https://coderwall.com/Peter%20Boling
## Summary
It's particularly helpful when you want to prevent the delivery of email (e.g. in development/test environments) or alter the to/cc/bcc (e.g. in staging or demo environments) of all email generated from your application.
* compatible without Rails! Can work with just the `mail` gem.
* compatible with Rails >= 3.0. See gem versions 1.x for older versions of Rails.
* compatible with Ruby >= 2.3. See gem versions 1.x for older versions of Ruby.
* compatible with any Ruby app with a mail handler that uses the `register_interceptor` API (a la ActionMailer and `mail` gems)
* configure it and forget it
* little configuration required
* solves common problems in ruby web applications that use email
* provides test helpers and spec matchers to assist with testing email content delivery
## Working Locally with Production Data
1. Have a production site with live data
2. Dump the live data and securely transfer it to another machine (e.g. rync -e ssh)
3. Import it into a development database
4. Test features which send out email (registration/signup, order placement, etc.)
5. Emails get sent (in real-life!) but to sanitized email recipients
6. Verify what they look like when sent
7. Iterate on email content design
8. No risk of emailing production addresses
## Re-routing Email on a Staging or QA Server
Another very important use case for me is to transparently re-route email generated from a staging or QA server to an appropriate person. For example, it's common for us to set up a staging server for a client to use to view our progress and test out new features. It's important for any email that is generated from our web application be delivered to the client's inbox so that they can review the content and ensure that it's acceptable. Similarly, we set up QA instances for our own QA team and we use [rails-caddy](http://github.com/jtrupiano/rails-caddy) to allow each QA person to configure it specifically for them.
## Testing Email from a Hot Production Server
If you install this gem on a production server (which I don't always do), you can load up script/console and override the to/cc/bcc on all emails for the duration of your console session. This allows you to poke and prod a live production instance, and route all email to your own inbox for inspection. The best part is that this can all be accomplished without changing a single line of your application code.
## Monitoring all email sent by server to a backup account
You may want to add a BCC automatically (e.g. to account-history@my-company.com) to every email sent by your system, for customer service purposes, and this gem allows that. Note that this may not be a good idea for all systems, for many reasons, e.g security!
## Using with a test suite as an alternative to the heavy email_spec
[email_spec](https://github.com/bmabey/email-spec) is a great gem, with awesome rspec matchers and helpers, but it has an undeclared dependency on ActionMailer. Sad face.
SanitizeEmail comes with some lightweight RspecMatchers covering most of what email_spec can do. It will help you test email functionality. It is useful when you are creating a gem to handle email features, or are writing a simple Ruby script, and don't want to pull in le Rails. SanitizeEmail has two dependencies, `mail` gem, and `version_gem`. Your Mail system just needs to conform to `mail` gem's `register_interceptor` API.
## Install Like a Boss
In Gemfile:
```ruby
gem 'sanitize_email'
```
Then:
```bash
$ bundle install
```
## Setup with Ruby
*keep scrolling for Rails, but read this for a better understanding of Magic*
There are three ways SanitizeEmail can be turned on; in order of precedence they are:
1. Only useful for local context. Inside a method where you will be sending an email, set `SanitizeEmail.force_sanitize = true` just prior to delivering it. Also useful in the console.
```ruby
SanitizeEmail.force_sanitize = true # by default it is nil
```
2. If SanitizeEmail seems to not be sanitizing you have probably not registered the interceptor. SanitizeEmail tries to do this for you. *Note*: If you are working in an environment that has a Mail or Mailer class that uses the register_interceptor API, the interceptor will already have been registered by SanitizeEmail:
```ruby
# The gem will probably have already done this for you, but some really old versions of Rails may need you to do this manually:
Mail.register_interceptor(SanitizeEmail::Bleach)
```
Once registered, SanitizeEmail needs to be engaged:
```ruby
# in config/initializers/sanitize_email.rb
SanitizeEmail::Config.configure {|config| config[:engage] = true }
```
3. If you don't need to compute anything, then don't use this option, go with the previous option.
```ruby
SanitizeEmail::Config.configure {|config| config[:activation_proc] = Proc.new { true } } # by default :activation_proc is false
```
### Examples
#### Only allow email to a specific domain
This works by ensuring that all recipients have the "allowed" domain.
In other words, none of the recipients have a domain other than the allowed domain.
```ruby
ALLOWED_DOMAIN = 'example.com'
# NOTE: you may need to check CC and BCC also, depending on your use case...
config[:activation_proc] = ->(message) do
!Array(message.to).any? { |recipient| Mail::Address.new(recipient).domain != ALLOWED_DOMAIN }
end
```
### Notes
Number 1, above, is the method used by the SanitizeEmail.sanitary block.
If installed but not configured, sanitize_email DOES NOTHING. Until configured the defaults leave it turned off.
### Troubleshooting
IMPORTANT: You may need to setup your own register_interceptor. If sanitize_email doesn't seem to be working for you find your Mailer/Mail class and try this:
```ruby
# in config/initializers/sanitize_email.rb
Mail.register_interceptor(SanitizeEmail::Bleach)
SanitizeEmail::Config.configure {|config| config[:engage] = true }
```
If that causes an error you will know why sanitize_email doesn't work.
Otherwise it will start working according to the rest of the configuration.
## Setup With Rails
Create an initializer, if you are using rails, or otherwise configure:
```ruby
SanitizeEmail::Config.configure do |config|
config[:sanitized_to] = 'to@sanitize_email.org'
config[:sanitized_cc] = 'cc@sanitize_email.org'
config[:sanitized_bcc] = 'bcc@sanitize_email.org'
# run/call whatever logic should turn sanitize_email on and off in this Proc:
config[:activation_proc] = Proc.new { %w(development test).include?(Rails.env) }
config[:use_actual_email_prepended_to_subject] = true # or false
config[:use_actual_environment_prepended_to_subject] = true # or false
config[:use_actual_email_as_sanitized_user_name] = true # or false
end
```
Keep in mind, this is ruby (and possibly rails), so you can add conditionals or utilize different environment.rb files to customize these settings on a per-environment basis.
## Override the override
But wait there's more:
Let's say you have a method in your model that you can call to test the signup email. You want to be able to test sending it to any user at any time... but you don't want the user to ACTUALLY get the email, even in production. A dilemma, yes? Not anymore!
To override the environment based switch use `force_sanitize`, which is normally `nil`, and ignored by default. When set to `true` or `false` it will turn sanitization on or off:
```ruby
SanitizeEmail.force_sanitize = true
```
When testing your email in a console, you can manipulate how email will be handled in this way.
There are also two methods that take a block and turn SanitizeEmail on or off (see section on Thread Safety below):
Regardless of the Config settings of SanitizeEmail you can do a local override to force unsanitary email in any environment.
```ruby
SanitizeEmail.unsanitary do
Mail.deliver do
from 'from@example.org'
to 'to@example.org' # Will actually be sent to the specified address, not sanitized
reply_to 'reply_to@example.org'
subject 'subject'
end
end
```
Regardless of the Config settings of SanitizeEmail you can do a local override to send sanitary email in any environment.
You have access to all the same configuration options in the parameter hash as you can set in the actual
`SanitizeEmail.configure` block.
```ruby
SanitizeEmail.sanitary({:sanitized_to => 'boo@example.com'}) do # these config options are merged with the globals
Mail.deliver do
from 'from@example.org'
to 'to@example.org' # Will actually be sent to the override addresses, in this case: boo@example.com
reply_to 'reply_to@example.org'
subject 'subject'
end
end
```
## Configuration Options
As used in the "Description" column below, `engaged` means: `SanitizeEmail.activate?(message) # => true`.
This happens in a few different ways, and two of them are in the config below (`engage` and `activation_proc`).
| Option | Type (Yard format) | Description |
|---------------------------------------------|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| sanitized_to | [String, Array[String]] | (when engaged) Override CC field with these addresses |
| sanitized_cc | [String, Array[String]] | (when engaged) Override CC field with these addresses |
| sanitized_bcc | [String, Array[String]] | (when engaged) Override BCC field with these addresses |
| good_list | [Array[String]] | (when engaged) Email addresses to allow to pass-through without overriding |
| bad_list | [Array[String]] | (when engaged) Email addresses to be removed from message's TO, CC, & BCC |
| environment | [String, #to_s, Proc, Lambda, #call] | (when engaged) The environment value to use wherever it is added to message (e.g. in the subject line) |
| use_actual_email_as_sanitized_user_name | [Boolean] | (when engaged) Use "real" email address as username for sanitized email address (e.g. "real at example.com