Rails - Generating email attachments that work in Apple Mail - ruby-on-rails

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.

Related

Sendgrid API for Ruby on Rails

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.

How to Specify the Layout When Attaching a PDF to an Email Using Wicked PDF

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

Multiple render error in rails app while generating pdf file via wicked pdf

I'm using wicked PDF in my rails app to generate PDF. and that is done by a private function generate_pdf in my controler becase after the purchase process i want to generate that pdf and then mailer function to send that pdf to user.
error
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
code is something lie this.
def place_order
... some code
download_new_pdf(#pur)
UserMailer.confirm_order(#pur).deliver
format.html { redirect_to(:action=>'order_confirmed/'+#pur[:id].to_s) }
end
and the helper function will be
def download_new_pdf(id)
#my_ticket = Purchase.find(id)
#event = Event.find(#my_ticket[:event_id])
render :pdf => "#{#my_ticket[:random_no]}.pdf",
:template => 'tickets/download_invoice.html.erb',
:save_to_file => Rails.root.join('public/data/docs', "#{#my_ticket[:random_no]}.pdf")
end
place_order will do all the DB stuff and at the end generate pdf, send mail and redirect to conformation page. But here it's giving me multiple render error coz of
format.html { redirect_to(:action=>'order_confirmed/'+#pur[:id].to_s) }
and
render :pdf => "#{#my_ticket[:random_no]}.pdf",
:template => 'tickets/download_invoice.html.erb',
:save_to_file => Rails.root.join('public/data/docs', "#{#my_ticket[:random_no]}.pdf")
i tried putting and return but still no luck,Any help would be appreciated. Thanks !!!
Your error is simple. You are render in download_new_pdf and redirecting in place_orderat the same time. It looks like you need to create the pdf file and store in the file system, not to show it immediately to the user, so, check the readme, here is explained how to just create the PDF and don't render it to the client

How to convert pdf file into binary in Ruby on Rails

I filled the pdf using pdf_forms gem , now i want to convert that pdf into binary and want to send via email.
you dont need to convert a pdf to binary to attach it to an email. just add this line inside the method of your mailer
attachments["filename.pdf"] = File.read("/path/to/filename.pdf")
You probably just want to send the pdf as an attachment. The official Rails ActionMailer has an example on how to do this
class UserMailer < ActionMailer::Base
def welcome_email(user)
#user = user
#url = user_url(#user)
attachments['terms.pdf'] = File.read('/path/terms.pdf')
mail(:to => user.email,
:subject => "Please see the Terms and Conditions attached")
end
end
http://guides.rubyonrails.org/action_mailer_basics.html#sending-emails-with-attachments

Rails mailer without view

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

Resources