Reputation: 767
I have read through so many tutorials and answers online and am not finding my fix. I have setup my ActionMailer to send emails through Mailgun's service. I can verify that in production it does send emails through my online contact form without any issues. But, when I try to use one of Devise's restore templates (ie: Forgot Password) the email is not being sent.
Code
--> config/environments/production.rb
# ActionMailer Config
config.action_mailer.perform_caching = false
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_url_options = { :host => 'thechristianchain.org' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.default :charset => "utf-8"
config.action_mailer.smtp_settings = {
:authentication => :plain,
:address => "smtp.mailgun.org",
:port => '587',
:domain => "mx.tccf.co",
:user_name => ENV["MAILGUN_USERNAME"],
:password => ENV["MAILGUN_PASSWORD"]
}
To try and debug I set raise_deliever_errors to true as seen in the code above.
When I look through the logs I see this (I removed the log timestamps for easier reading):
I, [2018-02-23T20:17:28.511441 #4] INFO -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Sent mail to [email protected] (1028.0ms)
D, [2018-02-23T20:17:28.511562 #4] DEBUG -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Date: Fri, 23 Feb 2018 20:17:27 +0000
From: [email protected]
Reply-To: [email protected]
To: [email protected]
Message-ID: <5a9076d776379_4233fb2465758@2694484e-c3cb-44d0-8ece-832cd70adf2c.mail>
Subject: Reset password instructions
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<p>Hello [email protected]!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><a href="http://thechristianchain.org/password/edit?reset_password_token=1gtx9XHmTzUjf97YhJdG">Change my password</a></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
I, [2018-02-23T20:17:28.511950 #4] INFO -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Completed 401 Unauthorized in 1044ms (ActiveRecord: 5.7ms)
F, [2018-02-23T20:17:28.513684 #4] FATAL -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba]
F, [2018-02-23T20:17:28.513764 #4] FATAL -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Net::SMTPAuthenticationError (535 5.7.0 Mailgun is not loving your login or password
):
The key is in the last line where it fails because 401 Unauthorized
and Net::SMTPAuthenticationError
and basically stating that Mailgun did not like my login credentials.
QUESTION
Here's my question. Where is Devise pulling the information? When I send through the contact form, Mailgun accepts the login credentials that I submit; but when Devise sends, they are not accepted. This makes me think that there is a setting somewhere that is not pulling the information from production.rb
.
Upvotes: 1
Views: 2004
Reputation: 767
After 4 days of working on this issue, I was able to come up with a solution. Not sure if it is the best or most eloquent way of doing it, but just in case anyone else in the future has this same issue, here is what I did.
First, some explanation. I am not sure why updating the development.rb
and production.rb
files with my config.action_mailer.smtp_settings
didn't work. By all of the documentation, it seems that Devise should work seamlessly with those settings. Now, the reason my contact form worked and Devise didn't though is because my contact form was utilizing contact_us_email_mailer.rb
which had the code for sending email through Mailgun's API. (Thank you @kuwantum. Your comment helped me make the connection that I was sending the emails differently.)
So, my mentality was to figure out how Devise sends it's emails, where it calls the SMTP or API to email, and then enter my information there. I went to the Devise Wiki and found a "How To" for setting up a custom mailer file. Following these steps, I setup membership_mailer.rb
file under app/mailers/
that looked like this:
class MembershipMailer < Devise::Mailer
helper :application
include Devise::Controllers::UrlHelpers
end
And then changed my config/initializers/devise.rb
file to point to this new mailer by changing the following line:
# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
config.mailer = 'MembershipMailer'
I then needed to grab the original Devise mailer code and enter it into this document. I got that code here.
devise/mailer.rb
def confirmation_instructions(record, token, opts={})
@token = token
devise_mail(record, :confirmation_instructions, opts)
end
def reset_password_instructions(record, token, opts={})
@token = token
devise_mail(record, :reset_password_instructions, opts)
end
def unlock_instructions(record, token, opts={})
@token = token
devise_mail(record, :unlock_instructions, opts)
end
def email_changed(record, opts={})
devise_mail(record, :email_changed, opts)
end
def password_change(record, opts={})
devise_mail(record, :password_change, opts)
end
Looking at this code, I see the devise_mail
being called often, so I needed to find out what that code is. And I got to this page. Searching this site gave me the code that I needed.
def devise_mail(record, action, opts = {}, &block)
initialize_from_record(record)
mail headers_for(action, opts), &block
end
This code then showed a few more function calls that I searched for on the site mentioned above and the code that I needed to understand was the headers_for
section. This is that code:
def headers_for(action, opts)
headers = {
subject: subject_for(action),
to: resource.email,
from: mailer_sender(devise_mapping),
reply_to: mailer_reply_to(devise_mapping),
template_path: template_paths,
template_name: action
}.merge(opts)
@email = headers[:to]
headers
end
Running byebug, I was able to call this function and get a hash of responses, and calling each gave me it's value. So, typing headers_for(action, opts)[:from]
gave me the default email that I had setup in the devise.rb
file. So, that works.
Now, for the part that took some trial and error. I was having difficulty with the SMTP settings for Mailgun but know that the API worked as it was working with my contact form. So, I needed to get the variables working. This is the code that worked for my contact form (note that the hidden key was being called from a .env
file created with the 'dotenv-rails' gem and the require
statement utilized the 'rest-client' gem.):
contact_us_email_mailer.rb
require 'rest-client'
def send_email(from, to, subject, text)
RestClient.post ENV.fetch("MAILGUN_MX_URL"),
:from => from,
:to => to,
:subject => subject,
:text => text
end
Now, combining this with the devise_mail
function I got this code:
def mail(record, action, opts = {}, block)
initialize_from_record(record)
RestClient.post ENV.fetch("MAILGUN_MX_URL"),
:from => headers_for(action, opts)[:from],
:to => headers_for(action, opts)[:to],
:subject => headers_for(action, opts)[:subject],
end
I then needed to figure out how to get the email templates created into the email. That is when I came across this StackOverflow answer that pointed me in the right direction and I came up with this code:
html_output = render_to_string(:action => action, :layout => false)
:html => html_output.to_str
Adding that to the previous code I got this:
def mail(record, action, opts = {}, block)
initialize_from_record(record)
html_output = render_to_string(:action => action, :layout => false)
RestClient.post ENV.fetch("MAILGUN_MX_URL"),
:from => headers_for(action, opts)[:from],
:to => headers_for(action, opts)[:to],
:subject => headers_for(action, opts)[:subject],
:html => html_output.to_str
end
The next issue I had to do was change the input of the 'action' from a symbol to a string. For instance :confirmation_instructions
became 'confirmation_instructions'
.
The last issue was that the system was trying to find all of the mailer templates in a membership_mailer
folder instead of the devise/mailers/
folder. So, I just moved the devise templates into the new folder and that solved that problem.
And that did it. The new mailer was being called, sent correctly to the Mailgun API, calling the devise mailer templates from the new folder and sending the emails to the user. Hopefully this detailed explanation will help someone in the future.
Here is the final mailer code:
class MembershipMailer < Devise::Mailer
helper :application
include Devise::Controllers::UrlHelpers
require 'rest-client'
def mail(record, action, opts = {}, block)
initialize_from_record(record)
html_output = render_to_string(:action => action, :layout => false)
RestClient.post ENV.fetch("MAILGUN_MX_URL"),
:from => headers_for(action, opts)[:from],
:to => headers_for(action, opts)[:to],
:subject => headers_for(action, opts)[:subject],
:html => html_output.to_str
end
def confirmation_instructions(record, token, opts={})
@token = token
mail(record, 'confirmation_instructions', opts)
end
def reset_password_instructions(record, token, opts={})
@token = token
mail(record, 'reset_password_instructions', opts)
end
def unlock_instructions(record, token, opts={})
@token = token
mail(record, 'unlock_instructions', opts)
end
def email_changed(record, opts={})
mail(record, 'email_changed', opts)
end
def password_change(record, opts={})
mail(record, 'password_change', opts)
end
end
Upvotes: 2