22

I'm trying to generate emails with rendered PDF attachements using ActionMailer and wicked_pdf.

On my site, I'm using already both wicked_pdf and actionmailer separately. I can use wicked_pdf to serve up a pdf in the web app, and can use ActionMailer to send mail, but I'm having trouble attaching rendered pdf content to an ActionMailer (edited for content):

class UserMailer < ActionMailer::Base
  default :from => "webadmin@mydomain.com"

  def generate_pdf(invoice)
    render :pdf => "test.pdf",
     :template => 'invoices/show.pdf.erb',
     :layout => 'pdf.html'
  end

  def email_invoice(invoice)
    @invoice = invoice
    attachments["invoice.pdf"] = {:mime_type => 'application/pdf',
                                  :encoding => 'Base64',
                                  :content => generate_pdf(@invoice)}
    mail :subject => "Your Invoice", :to => invoice.customer.email
  end
end

Using Railscasts 206 (Action Mailer in Rails 3) as a guide, I can send email with my desired rich content, only if I don't try to add my rendered attachment.

If I try to add the attachment (as shown above), I get an attachement of what looks to be the right size, only the name of the attachment doesn't come across as expected, nor is it readable as a pdf. In addition to that, the content of my email is missing...

Does anyone have any experience using ActionMailer while rendering the PDF on the fly in Rails 3.0?

Thanks in advance! --Dan

Unixmonkey
  • 18,485
  • 7
  • 55
  • 78
Daniel D
  • 3,637
  • 5
  • 36
  • 41
  • FYI - I was using http://edgeguides.rubyonrails.org/action_mailer_basics.html to guide me as well... – Daniel D Mar 24 '11 at 15:36

4 Answers4

31

WickedPDF can render to a file just fine to attach to an email or save to the filesystem.

Your method above won't work for you because generate_pdf is a method on the mailer, that returns a mail object (not the PDF you wanted)

Also, there is a bug in ActionMailer that causes the message to be malformed if you try to call render in the method itself

http://chopmode.wordpress.com/2011/03/25/render_to_string-causes-subsequent-mail-rendering-to-fail/

https://rails.lighthouseapp.com/projects/8994/tickets/6623-render_to_string-in-mailer-causes-subsequent-render-to-fail

There are 2 ways you can make this work,

The first is to use the hack described in the first article above:

def email_invoice(invoice)
  @invoice = invoice
  attachments["invoice.pdf"] = WickedPdf.new.pdf_from_string(
    render_to_string(:pdf => "invoice",:template => 'documents/show.pdf.erb')
  )
  self.instance_variable_set(:@lookup_context, nil)
  mail :subject => "Your Invoice", :to => invoice.customer.email
end

Or, you can set the attachment in a block like so:

def email_invoice(invoice)
  @invoice = invoice
  mail(:subject => 'Your Invoice', :to => invoice.customer.email) do |format|
    format.text
    format.pdf do
      attachments['invoice.pdf'] = WickedPdf.new.pdf_from_string(
        render_to_string(:pdf => "invoice",:template => 'documents/show.pdf.erb')
      )
    end
  end
end
Unixmonkey
  • 18,485
  • 7
  • 55
  • 78
  • I had to revisit my PDF generation - and was just disappointed of the complexity/lack of elegance in prawn and thought I'd try wicked again.... and it worked! The only thing I got caught on - the view for the actual attachment needs to live at the root of the view folder, where I'd expect it to use the view from within the folder of the object I'm working with... – Daniel D Oct 03 '11 at 14:01
  • 2
    On Rails 3.2.2, the first way works without the hacking into @lookup_context. The second way leads to duplicates or missing attachments on Mail.app, Sparrow and GMail. – Philippe Creux Apr 12 '12 at 12:38
4

I used of Unixmonkey's solutions above, but then when I upgraded to rails 3.1.rc4 setting the @lookup_context instance variable no longer worked. Perhaps there's another way to achieve the same clearing of the lookup context, but for now, setting the attachment in the mail block works fine like so:

  def results_email(participant, program)
    mail(:to => participant.email,
         :subject => "my subject") do |format|
      format.text
      format.html
      format.pdf do
        attachments['trust_quotient_results.pdf'] = WickedPdf.new.pdf_from_string(
          render_to_string :pdf => "results",
               :template => '/test_sessions/results.pdf.erb',
               :layout => 'pdf.html')
      end
   end
  end
Sean O'Hara
  • 1,218
  • 1
  • 11
  • 19
  • 3
    The first solution does not work anymore since the name of the lookup context instance variable was changed with this commit: https://github.com/rails/rails/commit/894bdbd53dd82b6b28ff2672b85f57ed5ce97759 So it is now called @_lookup_context, and that's what I'm using. – Jure Triglav Jul 07 '12 at 14:44
  • 1
    `self.instance_variable_set(:@_lookup_context, nil)` (note the underscore) also ensured that Sparrow shows the attachment preview. – Dominic Jul 16 '13 at 14:09
0

Heres' how I fixed this issue:

  1. Removed wicked_pdf
  2. Installed prawn (https://github.com/sandal/prawn/wiki/Using-Prawn-in-Rails)

While Prawn is/was a bit more cumbersome in laying out a document, it can easily sling around mail attachments...

Daniel D
  • 3,637
  • 5
  • 36
  • 41
  • 1
    wicked_pdf is "more fun" to use than prawn (IMO) - and now, using the first hack from unix_monkey, it works... – Daniel D Oct 03 '11 at 14:04
  • have some experience using prawn and wicked_pdf. so I choose wicket_pdf - very fast, complete html support and painless. – ajahongir Nov 26 '13 at 19:08
0

Better to use PDFKit, for example:

class ReportMailer < ApplicationMailer

  def report(users:, amounts:)
    @users = users
    @amounts = amounts

    attachments["proveedores.pdf"] = PDFKit.new(
      render_to_string(
        pdf: 'bcra',
        template: 'reports/users.html.haml',
        layout: false,
        locals: { users: @users }
      )
    ).to_pdf

    mail subject: "Report - #{Date.today}"
  end
end