Attach Prawn pdf to email

2019-07-13 21:31发布

问题:

I have been searching for ages and I still cant seem to figure this out,

I am currently using the prawn gem and I want to be able to attach a pdf to an email in my invoice controllers create action. I am currently able to link to a generated pdf from my invoices/show page through http://localhost:3000/invoices/297.pdf but I cant figure out how to attach this pdf to an email.

Currently I am not storing the generated PDF anywhere and my mailer looks like this

Mailer

class InvoiceMailer < ActionMailer::Base
default :from => "notifications@example.com"

 def invoice_email(user)
   @user = user
   mail(:to => user.email, :subject => "Invoice Recieved")
 end
end

And my InvoicesController Create Action

{...}
respond_to do |format|
  if @invoice.save
    InvoiceMailer.invoice_email(@invoice.user).deliver
    format.html { redirect_to invoice_url(@invoice, back_path: 
{...}

How can I add my invoice as an attachment to this mailer? Do I need to store the invoice somewhere before I can send it?

回答1:

How you do this depends on how long the PDF generation takes and/or how much load it places on your server and whether you care about that. In my case I was generating PDFs from user-generated-content and I was seeing some PDF creation times up in the 30+ seconds range. Solving for that becomes a run-the-job-somewhere-else and cache-it (whether DB or cloud storage) issue.

@toddmetheny is quite right in suggesting cloud storage for all but the simplest solutions. It gets more interesting if you are hosting on something like Heroku with ephemeral storage, or if you are separating PDF creation from email sending from user requests (e.g. from Heroku again, web dynos vs worker dynos). You can generate the PDF to a local temporary file, but that temporary file may not be there by the time you need to read it in your Mailer running on a 'worker'.

Really simple option

In your Mailer you could generate the PDF to a local file, read it back into memory, then attach it:

def invoice_email(user)
  @user = user

  attachments['filename_for_user.pdf'] = generate_pdf_content

  mail(:to => user.email, :subject => "Invoice Recieved")
end

private

  # I had troubles trying to get Prawn to generate 'in memory'
  # so just write to, then read, then unlink a tempfile
  def generate_pdf_content
    pdf = some_method_that_generates_a_prawn_pdf_object
    Tempfile.create do |f|
      pdf.render_file f
      f.flush
      File.read(f)
    end
  end

I suggest you start here to get things working.

More complicated option

Someday you may want to separate the job that generates the PDF (which can take a long time) from the jobs that send email, which are usually much faster. The way I do this is to have a job that generates the PDF to a local Tempfile, then uploads that file to S3 storage and records the S3 object id (I do it in my database, you could just do it as a job attribute you push around).

When complete, that job creates a new mailer job. The mailer job (which may execute on a different server) downloads the PDF from S3 storage to a local file, then adds it to the email much like the simple option above.



回答2:

You'll need a url you can work with. Any cloud storage solution is an option if you don't want to store it on your database.

Here's some pertinent info on adding attachments to mailers from rails guides on action mailer:

2.3.1 Adding Attachments

Action Mailer makes it very easy to add attachments.

Pass the file name and content and Action Mailer and the Mail gem will automatically guess the mime_type, set the encoding and create the attachment.

attachments['filename.jpg'] = File.read('/path/to/filename.jpg')

When the mail method will be triggered, it will send a multipart email with an attachment, properly nested with the top level being multipart/mixed and the first part being a multipart/alternative containing the plain text and HTML email messages.