Rails and environment variables - Lesson Learned 5

I was trying  to send emails from my Rails app and I was NOT getting any success.

As per Rails documentation, I did everything I was supposed to do.

$rails generate mailer UserMailer

Then I created method inside UserMailer class /app/mailers/user_mailer.rb and built the email templates in folder /app/views/user_mailer folder.

Then I added code to invoke the user mailer with following code.

UserMailer.with(user: @user).welcome_email.deliver_later

There were no errors, but I was not getting any emails either.

Finally, I figured out that I was missing action_mailer configuration in the config/development.rb file.

So I added it as follows:

  config.action_mailer.delivery_method = :smtp
  # SMTP settings for gmail
  config.action_mailer.smtp_settings = {
   :address              => "smtp.gmail.com",
   :port                 => 587,
   :user_name            => ENV['GMAIL_USERNAME'],
   :password             => ENV['GMAIL_PASSWORD'],
   :authentication       => "plain",
   :enable_starttls_auto => true

I was STILL not getting emails, so I hardcoded GMAIL_USERNAME and GMAIL_PASSWORD and THEN I got my first email.

Now, anyone can tell that hardcoding sensitive information in a config file is not a good idea. And to check-in that file to git repository is even worse.

So I tried to set environment variables.

$export GMAIL_USERNAME=my_username

$export GMAIL_PASSWORD=my-pwd

This did not work. I started debugging again.

From rails console, I tried  ENV["GMAIL_USERNAME"] and ENV["GMAIL_PASSWORD"] and I was not getting the result.

$rails console



What was I doing wrong? (I had not added environment variables to .bashrc file and so Rails was not able to read them.)

So I tried following, to pass on environment variables at the time of starting the rails server.


$GMAIL_USERNAME=my_username GMAIL_PASSWORD=my-pwd rails server

NOW emails started working again.

I figured that setting environment variables in production environment manually is going to be a pain. I have to add it as part of .bashrc file in production, but then they will be available to ALL the applications on the server, I want them to be available for my rails app only. If a hacker gets access to the filesystem, he can read the sensitive information easily. It will be much better if I could make them as part of my code. That way, it will work even if a new developer tried to test the code and it just works.

I found a few options. There are gems like dotenv or Figaro to manage environment variables in key=value or key: :value format.

For dotenv gem, we need to save the credentials in .env file at the root of my app directory.

For Figaro, the concept is similar, but the credentials are in a YAML file /config/application.yml

This is STILL a problem in my opinion. Why?

Because I must add these files (.env or /config/application.yml) as part of .gitignore so that these credentials are not sent to git repository. Note that these are simple text files and anyone can open and read them easily. There is no security whatsoever.

So I thought of using credentials file that is available in Rails now.

I added my gmail credentials in the credentials file. I know your objection that we must copy master.key manually to the server. But at least, 1) the sensitive information is not available as environment variables to every app on the server, 2) you can't read the credentials file from a text editor because it is encrypted (unlike .env or /config/application.yml )

I am happy that I learned something interesting. :)

Final code in config/environments/development.rb file

  config.action_mailer.delivery_method = :smtp
  # SMTP settings for gmail
  config.action_mailer.smtp_settings = {
   :address              => "smtp.gmail.com",
   :port                 => 587,
   :user_name            => Rails.application.credentials.gmail[:username], #ENV['GMAIL_USERNAME'],
   :password             => Rails.application.credentials.gmail[:password], #ENV['GMAIL_PASSWORD'],
   :authentication       => "plain",
   :enable_starttls_auto => true