I can't seem to find a step by step tutorial on how to integrate the Sendgrid web API in to a Ruby on Rails application. I'm pretty new to this so maybe I'm missing something obvious.
I would like to use the Sendgrid web API instead of the smtp delivery method (mailgun talks about the benefits of the web API over the SMTP method here: https://documentation.mailgun.com/quickstart-sending.html, and I was thinking that Sendgrid would either have the same benefits or I would potentially switch to mailgun later).
After installing the sendgrid gem (https://github.com/sendgrid/sendgrid-ruby), the documentation tells me to "Create a new client with your SendGrid API Key", and that I can do it 2 ways:
require 'sendgrid-ruby'
# As a hash
client = SendGrid::Client.new(api_key: 'YOUR_SENDGRID_APIKEY')
# Or as a block
client = SendGrid::Client.new do |c|
c.api_key = 'YOUR_SENDGRID_APIKEY'
end
Where specifically in my application am I supposed to put this code? Should I put this in my mailer, my application mailer or in the config/environments/production.rb file?
I took a look at this tutorial that walks through how to set up the Mailgun API: https://launchschool.com/blog/handling-emails-in-rails
According to this tutorial it looks like the line client = SendGrid::Client.new(api_key: 'YOUR_SENDGRID_APIKEY') should actually go in to the mailer method itself. See below for the launchschool.com example (presumably replacing the mailgun specific info with the sendgrid info):
class ExampleMailer < ActionMailer::Base
def sample_email(user)
#user = user
mg_client = Mailgun::Client.new ENV['api_key']
message_params = {:from => ENV['gmail_username'],
:to => #user.email,
:subject => 'Sample Mail using Mailgun API',
:text => 'This mail is sent using Mailgun API via mailgun-ruby'}
mg_client.send_message ENV['domain'], message_params
end
end
Additionally, how do I get my mailer method to send a mailer view instead of simple text as outlined in the launchschool example? For example, instead of sending the text 'This mail is sent using...' I would like to send a mailer view (something like account_activation.html.erb).
Finally, I am using Devise in my application, and I would like to have Devise use the web API to send emails (ie password reset, etc). Does this mean I need to create a custom mailer for Devise? If so, how do I do that?
According to Devise (https://github.com/plataformatec/devise/wiki/How-To:-Use-custom-mailer), I should "create a class that extends Devise::Mailer". Does that mean I simply make a file within my mailer folder with the info laid out in the docs? Do I need a separate mailer for Devise or can I have an existing mailer inherit from the Devise mailer? Finally, how do I tell devise to use the sendgrid web api to send emails (instead of the simple smtp method)?
Sorry for the long question, but hopefully others find it useful.
Thanks!
I would create a mailer class to do this
class SendgridWebMailer < ActionMailer::Base
include Sendgrid
def initialize
#client = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY']).client
end
def send_some_email(record, token)
mail = Mail.new
// do your mail setup here
mail = Mail.new
mail.from = Email.new(email: YOUR_EMAIL_HERE)
mail.subject = YOUR_SUBJECT
// I personally use sendgrid templates, but if you would like to use html -
content = Content.new(
type: 'text/html',
value: ApplicationController.render(
template: PATH_TO_TEMPLATE,
layout: nil,
assigns: IF_NEEDED || {}
)
mail.contents = content
personalization = Personalization.new
personalization.to = Email.new(email: EMAIL, name: NAME)
personalization.subject = SUBJECT
mail.personalizations = personalization
#client.mail._('send').post(request_body: mail.to_json)
end
end
Call it using
SendgridWebMailer.send_some_email(record, token).deliver_later
For Devise
class MyDeviseMailer < Devise::Mailer
helper :application # gives access to all helpers defined within `application_helper`.
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
def reset_password_instructions(record, token, opts={})
SendgridWebMailer.send_some_email(record, token).deliver_later
end
end
in config/devise.rb
# Configure the class responsible to send e-mails.
config.mailer = 'MyDeviseMailer'
EDIT:
This method will, unfortunately, only work with remote images.
Okay, so here's how I did it. There may be a better way to do this, but this worked for me.
So with sending an email you would originally send it by doing something like UserMailer.reset_email(user).deliver. So remove the deliver and save it to a variable:
object = UserMailer.reset_email(user)
Deeply nested in this ish is the body of the email. The place where it lies may change with context, so my advice is to return the object to the frontend so that you can dig into it and find it. For me, the body resided here:
object = UserMailer.reset_email(user)
body = object.body.parts.last.body.raw_source
Okay so now you got the raw source. Now to send it, here's a method I created:
def self.sendEmail(from,to,subject,message)
from = Email.new(email: from)
to = Email.new(email: to)
content = Content.new(type: 'text/html', value: message)
mail = Mail.new(from, subject, to, content)
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
response = sg.client.mail._('send').post(request_body: mail.to_json)
puts response.status_code
puts response.body
puts response.headers
end
Call it as such (in my case it's in the user model):
User.sendEmail('noreply#waydope.com',user.email,'Reset Password', body)
Make sure to change the content type from plain to html, or you will just get the raw code. Hope this helps.
Here's a step-by-step guide to help you integrate SendGrid into your RoR's app. Hope it helps!
Create a SendGrid account first at : http://sendgrid.com/
Create a new rails folder (sendgrid_confirmation)
rails new sendgrid_confirmation
Navigate to ‘sendgrid_confirmation’ folder
cd sendgrid_confirmation
Open ‘sendgrid_confirmation’ in your text editor (Sublime)
Create a user model (User is a test model, you can create any other model as per your project’s use)
rails generate scaffold user name email login
rake db:migrate
Include sendgrid-rails gem in your Gemfile
gem 'sendgrid-rails', '~> 2.0'
Run bundle install in your terminal
bundle install
Use secrets.yml to define the SendGrid API credentials: (config/secrets.yml)
production:
sendgrid_username: your-sendgrid-username
sendgrid_password: your-sendgrid-password
(Emails are not sent in development and test environments. So you can define it only for production.)
Generate a Mailer class. Mailer classes function as our controllers for email views.
rails generate mailer UserNotifier
Open app/mailers/user_notifier.rb and add the following mailer action that sends users a sign-up mail
class UserNotifier < ActionMailer::Base
default :from => 'any_from_address#example.com'
# send a signup email to the user, pass in the user object that contains the user's email address
def send_signup_email(user)
#user = user
mail(:to => #user.email,
:subject => 'Thanks for signing up for our amazing app')
end
end
Create a file app/views/User_notifier/send_signup_email.html.erb as follows: (This will create a view that corresponds to our action and outputs HTML for our email)
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>Thanks for signing up, <%= #user.name %>!</h1>
<p>Thanks for joining and have a great day! Now sign in and do awesome things!</p>
</body>
</html>
Go to the Users Controller (app/controllers/users_controller.rb) and add a call to UserNotifier.send_signup_email when a user is saved.
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
UserNotifier.send_signup_email(#user).deliver
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Update your config/environment.rb to point your ActionMailer settings to SendGrid’s servers. Your environment.rb file should look like the following:
# Load the Rails application.
require File.expand_path('../application', __FILE__)
# Initialize the Rails application.
Rails.application.initialize!
ActionMailer::Base.smtp_settings = {
:user_name => ‘your_sendgrid_username’,
:password => 'your_sendgrid_password’,
:domain => ‘your_domain.com',
:address => 'smtp.sendgrid.net',
:port => 587,
:authentication => :plain,
:enable_starttls_auto => true
}
Point your config/routes.rb file to load the index page on load. Add the following in your routes.rb file:
get ‘/’ => ‘users#index’
And that is it! Now when you create a new user, you should receive an email (on the user.email you provided) from any_from_address#example.com. You can change this from:e-mail from app/mailers/user_notifier.rb. Change the default :from address and it should do that. You can add email parameters in the send_signup_mail method inside the same file and add details that you’d like to add. You can also change the message body from app/views/user_notifier/send_signup_email.html.erb to display whatever content you want to.
Related
I am on Ch 20 of the Learn Rails tutorial 'Send Mail'. There is a create contact form and a notification email is supposed to be sent to my email address. I have set up configuration as per the book and the email is being generated correctly i the terminal log. however the email never sends to my email inbox. I am getting an internal server error. see below for details.
Completed 500 Internal Server Error in 30831ms
Net::OpenTimeout (execution expired):
app/controllers/contacts_controller.rb:10:in `create'
contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(secure_params)
if #contact.valid?
UserMailer.contact_email(#contact).deliver_now
flash[:notice] = "Message sent from #{#contact.name}."
redirect_to root_path
else
render :new
end
end
private
def secure_params
params.require(:contact).permit(:name, :email, :content)
end
end
user_mailer.rb
class UserMailer < ApplicationMailer
default from: "do-not-reply#example.com"
def contact_email(contact)
#contact = contact
mail(to: Rails.application.secrets.owner_email, from: #contact.email, :subject => "Website Contact")
end
end
i also have my email address set in the config/secrets.yml file like so :
owner_email: <%= ENV["my_email_address#*******.com"] %>
I have also added the following to my .bashrc file as per the early chater of the book about configuration:
export SENDGRID_USERNAME="user_name"
export SENDGRID_PASSWORD="password"
export MAILCHIMP_API_KEY="long_random_api_key_here"
export MAILCHIMP_LIST_ID="list_id_here"
export OWNER_EMAIL="**********#*********.com"
so as far as i see i have set everything up according to the tutorial but the mail is not sending. any ideas why?
From the above share code description and log trace, it seems like mailer settings is not configured properly.
Note: You need to set the mailer settings based on the environment on which you are working(eg: development.rb)
Follow the below given link for the configuration of mailer settings:
http://guides.rubyonrails.org/action_mailer_basics.html#example-action-mailer-configuration
I'm using Rails, Mongoid and Sidekiq in this app.
In my Mailer I have the following:
def send_invoice(invoice, domain)
#invoice = Invoice.find(invoice)
#user = User.find(domain)
attachments["invoice.pdf"] = File.read("#{Rails.root}/invoices/pdf/55f5c596019e2b51af000000-55f82b520540a78f43000000.pdf")
mail to: "wagner.matos#mac.com", subject: "Invoice from MyJarvis", reply_to: #user.email, :from => #user.email, :bcc => 'wagner.matos#mac.com'
end
And in my email template I have something like this (haml):
!!!
%html
= "Hello #{#invoice[:name]}"
And if I use ActiveMailer (not using Sidekiq) all works fine. Now in my worker:
def perform(data, count)
message = JSON.load(data)
UserMailer.send_invoice(message['invoice'], message['domain']).deliver
end
Then I get an error saying ActionView::Template::Error: undefined method '[]' for nil:NilClass
After some investigation I found out that the problem is with the variable used in the email. When I tried the same email without using the variable, all worked fine.
Now I understand this is an issue on how Sidekiq stores data in Redis (as json). What I don't know is, how can I pass variables to the email template using Sidekiq?
EDIT: Here's my Mailer and my Worker:
Mailer:
def send_invoice(invoice, domain)
#invoice = Invoice.find(invoice)
#user = User.find(domain)
attachments["invoice.pdf"] = File.read("#{Rails.root}/invoices/pdf/55f5c596019e2b51af000000-55f82b520540a78f43000000.pdf")
mail to: "email#example.com", subject: "Invoice", reply_to: #user.email, :from => #user.email
end
Worker:
def perform(data, count)
message = JSON.load(data)
UserMailer.send_invoice(message['invoice'], message['domain']).deliver
end
And in my controller:
...
h = JSON.generate({ 'invoice' => #invoice.id.to_s, 'domain' => set_db })
PostmanWorker.perform_async(h, 1)
...
So as you can see I'm passing the id for it to be processed inside the worker rather than passing a Hash. But how can I pass the #invoice hash to the email template?
According to the docs (https://github.com/mperham/sidekiq/wiki/Active-Job), there is a newer syntax (API) for delivering email:
UserMailer.welcome_email(#user).deliver_later!(wait: 1.hour)
Checkout the section under "Action Mailer", as that may solve your problem.
I'm trying to set up my ActionMailer so that it will read the URL it is being posted from. When this application is deployed, it could end up on many different servers with different domain names. Instead of having the user go into the code and force enter the URL statically, I would like the reset password url to include the domain that it was generated from (including http:// or https://).
I've tried ::Rails.root, request.host_with_port and ::Rails.root_path within the Mailer, but none have produced results. request.host_with_port generates an undefined method error.
def reset_password_email(user)
#user = user
#url = "#{::Rails.root_path}/password_resets/#{user.reset_password_token}/edit"
mail(:to => user.email,
:subject => "Your password has been reset")
end
I assume that you call reset_password_email(user) from one of your app controllers.
You can update this method definition and send with user the current host and port to it:
def reset_password_email(user, request)
#user = user
#url = "#{request.protocol}#{request.host_with_port}/password_resets/#{user.reset_password_token}/edit"
mail(:to => user.email, :subject => "Your password has been reset")
end
Don't forget to update your Controller's code.
My app creates a unique email for each user, and users send email to that address for processing. Using Sendgrid, I've piped incoming emails to my domain (hosted on Heroku) to an address:
site.com/receive_email
I use the TO field to determine the user, since the email address is randomly generated.
I've experimented using an external script like Mailman, but since I'm hosted on Heroku I'd need to have a worker running full time to keep this process going. Not really looking for that at the moment for this test app.
That leaves processing it as a POST request. I have access to POST hash (params["subject"], etc.) at receive_emails.
This is where I get stuck
Would you suggest to deal with raw data from the POST params, or can I use something like Mailman or ActionMailer to process the email for me?
I haven't used Sendgrid to turn emails into post requests, but it works fine with cloudmailin, a heroku addon. Here is an example where someone sends an email to your application, it is processed by cloudmailin/sendgrid and turned into a post, and then sends it to your controller, and then the controller looks through the message params, finds the sender from the email address, and if the sender doesn't already exist, creates an account for her:
class CreateUserFromIncomingEmailController < ApplicationController
require 'mail'
skip_before_filter :verify_authenticity_token
parse_message(params[:message])
def create
User.find_or_create_by_email(#sender)
end
private
def parse_message(message_params)
#message = Mail.new(message_params)
#recipients = #message.to
#sender = #message.from.first
end
end
Good luck.
ActionMailer already depends on the Mail gem, you could use it to parse the incoming email and extract the parts that you want. It is specially useful to deal with multipart emails.
require 'mail'
class IncomingEmails < ApplicationController
skip_before_filter :verify_authenticity_token
def receive_email
comment = Comment.new(find_user, message_body)
comment.save
rescue
# Reject the message
logger.error { "Incoming email with invalid data." }
end
private
def email_message
#email_message ||= Mail.new(params[:message])
# Alternatively, if you don't have all the info wrapped in a
# params[:message] parameter:
#
# Mail.new do
# to params[:to]
# from params[:from]
# subject params[:subject]
# body params[:body]
# end
end
def find_user
# Find the user by the randomly generated secret email address, using
# the email found in the TO header of the email.
User.find_by_secret_email(email_message.to.first) or raise "Unknown User"
end
def message_body
# The message body could contain more than one part, for example when
# the user sends an html and a text version of the message. In that case
# the text version will come in the `#text_part` of the mail object.
text_part = email_message.multipart? ? email_message.text_part : email_message.body
text_part.decoded
end
end
Is it possible to have a Rails mailer without any view ?
When dealing with text-only mails, it would be cool to be able to put the body right in the mailer itself and not have to use a view for the action with just one line of text (or one I18n key).
In a way, I'm looking for something like ActionController's "render :text =>" but for ActionMailer.
Much simpler, just use the body option :
def welcome(user)
mail to: user.email,
from: "\"John\" <admin#domain.ch>",
subject: 'Welcome in my site',
body: 'Welcome, ...'
end
And if you plan to use html, don't forget to specify that with the content_type option which is by default text/plain.
content_type: "text/html"
So with body option rails skips the template rendering step.
I found the way by experimentation :
mail(:to => email, :subject => 'we found the answer') do |format|
format.text do
render :text => '42 owns the World'
end
end
This is also said in the Rails Guide :
http://guides.rubyonrails.org/action_mailer_basics.html at section 2.4