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
Related
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.
I am unable to get an email to generate when I try to specify a layout for a pdf I want to attach to an email using Wicked PDF. This is a rails 4 app.
When I use the following code, the application successfully sends the email with the pdf attachment, but without the desired styling:
def invoice_created(invoice_id)
#invoice = Invoice.find(invoice_id)
#subscription = Subscription.find(#invoice.subscription_id)
attachments['invoice.pdf'] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "invoice", :template => 'invoices/show.pdf.erb'))
mail(to: 'you#example.com', subject: "Your Invoice is Ready")
end
But when I add the layout, nothing generates.
def invoice_created(invoice_id)
#invoice = Invoice.find(invoice_id)
#subscription = Subscription.find(#invoice.subscription_id)
attachments['invoice.pdf'] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "invoice",
:template => 'invoices/show.pdf.erb',
:layout => 'pdf.html.erb'))
mail(to: 'you#example.com', subject: "Your Invoice is Ready")
end
In my controller, I display the pdf in the browser and it correctly uses the pdf.html.erb layout:
def show
#invoice = current_account.invoices.find(params[:id])
respond_to do |format|
format.html
format.pdf do
render :pdf => 'file_name',
:template => 'invoices/show.pdf.erb',
:layout => 'pdf.html.erb'
end
end
end
I'm not seeing any obvious errors in the dev log, other than the email file not being created.
I am seeing the following so it looks like it sees the layout but won't show it.
Rendered invoices/show.pdf.erb within layouts/pdf.html.erb (13.7ms)
Could there a problem with conflicting layouts?
If I try to generate several emails with the layout specified, then remove the layout code from the mailer, restart the server, request a new email, then the emails I previously requested get sent.
I tried adding
:show_as_html => params[:debug].present?
to the mailer to see if it would shed some light but this also caused the email not to be sent.
The problem code was in pdf layout:
<%= csrf_meta_tags %>
Not sure why I didn't receive any error messages in my app. I have my application set up to display errors with gem 'better_errors' which didn't display any errors. I set up a new application just to play with wicked_pdf, but didn add better_errors to the gem file.
Now I received the following error message:
undefined method `protect_against_forgery?'
Once this line was removed from the layouts/pdf/html.erb file the app correctly attached the styled pdf.
Here's a link to the sample wicked_pdf example I used to find the solution: https://github.com/stevejc/WickedPDF-Example
I would like to send what is normally a view as the body of an email.
Whats the simplest way to do this?
eg: I have pages/reports.html.erb and I want to send the page that you'd see if you visited that path as the body of an email.
Is there some way to take the html that is rendered and assign it to the body variable of an email?
Additionally, I'd like to include the PDF version as an attachment. (I'm using wicked_pdf)
def reports
#email = params[:email]
#something here?
respond_to do |format|
format.html
format.pdf do
render :pdf => "usage_report"
end
end
end
note: I'm using rails 3.1 with mongoid 2 for the DB and sendgrid on heroku if that helps
EDIT: What I ended up doing:
replace #something here? with:
email_obj = {}
email_obj.to = params[:email]
email_obj.from = 'reports#company.com'
email_obj.subject = 'Report'
email_obj.body = render_to_string(:template => "pages/reports", :layout => false)
ReportsMailer.deliver_report(email_obj).deliver
and in the mailer class mailers/reports_mailer.rb
class ReportsMailer < ActionMailer::Base
default from: "from#example.com"
def deliver_report(email)
#email = email
mail( :to => #email.to,
:subject => #email.subject,
:from => #email.from)
end
end
and in reports_mailer/deliver_report.html.erb
<%= render :inline => #email.body %>
The rails mailer views are stored in a sub folder in views by the name of the mailer just like controller views. It is hard to say if this will work completely for you not knowing what the view/controller for that method looks like but what you could do is take the rendering for that page and turn it into a partial which you can then render in both the controller view and the mailer view as such:
given a partial: _my_partial.html.erb
within the views:
render "my_partial"
Using the partial you can even pass any variable that would be necessary for the view to be rendered to it with:
render "my_partial", :variable => variable
Update:
This may be of interest as well: Rendering a Rails view to string for email
I have an interesting situation. I am testing the following simple create action:
# will only be accessed via Ajax
def create
click = Click.new(params[:click])
click.save # don't really care whether its success or failure
end
Then I have the following very simple controller spec:
require 'spec_helper'
describe ClicksController, "creating a click" do
it "should create a click for event" do
xhr :post, :create, :click => {:event_id => 1}
# more test to come...
end
end
Seems trivial, yet I get the following:
Missing template clicks/create
Any tips would be appreciated.
Add to the controller action:
render :nothing => true
This one will automatically create the appropriate server's respone. More here
You will get this error if your controller renders only JSON or XML, yet you don't specify a format in the spec; your request then defaults to unsupported HTML. In that case, simply specify the supported format when you invoke the controller method from your spec. For example, change this:
post :create, registration: #user_hash
to this:
post :create, registration: #user_hash, format: :json
If you do not render anything in a controller action, rails will attempt to default to rendering a template (in this case clicks/create). I'd suggest rendering back at least a success message like so:
render :json => {:success => true}
Building on megas's answer, if you're looking to test a controller action that's only accessed via a UJS link and only has a .js.erb template, I'd put this in the controller to avoid breaking your UJS functionality:
respond_to do |f|
f.html { render nothing: true } # prevents rendering a nonexistent template file
f.js # still renders the JavaScript template
end
This will enable you to call the controller action by simply calling ActionController::TestCase::Behavior's get/post/put/delete methods instead of needing to call xhr, because it will successfully call the method, render nothing, and continue on, while leaving your UJS behavior intact.
I have a Rails app that sends a PDF to the user. I am producing this email with the following mailer code:
def new_file_notification
mail(:to => 'test#test.com', :subject => "File for #{Time.now.to_s(:uk)}") do |format|
format.html
format.pdf do
attachments['my_file.pdf'] = File.read(Rails.root.join('tmp', 'my_files', "my_file_#{#my_file.id}.pdf"))
end
end
end
Whilst this produces an email that is fine in Sparrow and the GMail interface, this does not come out well in Apple Mail. I see the attachment, but do not see any of the email HTML, but also have a second blank attachment called "Mail Attachment.pdf"
What am I doing wrong?
I think you should remove format.pdf as doesn't this suggest that PDF is a viable format for the e-mail itself? It should only be .html or .text I think. Your attachment should just go under HTML format.