ActiveMailer PDF Email Attachments with Paperclip
Jason Dugdale
Let’s say we have a Report model, which has PDFs as Paperclip attachments:
/app/models/report.rb
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> Report</span><span style="color:#E1E4E8"> < </span><span style="color:#B392F0">ActiveRecord::Base</span></span>
<span class="line"><span style="color:#E1E4E8"> belongs_to </span><span style="color:#79B8FF">:user</span></span>
<span class="line"><span style="color:#E1E4E8"> has_attached_file </span><span style="color:#79B8FF">:pdf</span></span>
<span class="line"><span style="color:#E1E4E8"> validates_attachment </span><span style="color:#79B8FF">:pdf</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">content_type:</span><span style="color:#E1E4E8"> { </span><span style="color:#79B8FF">content_type:</span><span style="color:#9ECBFF"> "application/pdf"</span><span style="color:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#F97583">end</span></span></code></pre>
We want to automate sending out a report by email each week to the user who created the report (this post won’t cover the automation; use something to automate that, like a CRON job, or the Clockwork gem).
For this example, I’ve set up an ActionMailer mailer to send the email, and an ActiveJob job to generate the PDF from values in the Report object.
Creating the ActiveJob job
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#E1E4E8">rails g job generateReportPdf</span></span></code></pre>
/app/jobs/generate_report_pdf_job.rb
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> GenerateReportPdfJob</span><span style="color:#E1E4E8"> < </span><span style="color:#B392F0">ActiveJob::Base</span></span>
<span class="line"><span style="color:#E1E4E8"> queue_as </span><span style="color:#79B8FF">:default</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583"> def</span><span style="color:#B392F0"> perform</span><span style="color:#E1E4E8">(args)</span></span>
<span class="line"><span style="color:#E1E4E8"> @report </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> Report</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">find</span><span style="color:#E1E4E8"> args[</span><span style="color:#79B8FF">:report_id</span><span style="color:#E1E4E8">]</span></span>
<span class="line"><span style="color:#6A737D"> # create an instance of ActionView, so we can use the render method outside of a controller</span></span>
<span class="line"><span style="color:#FFAB70"> av</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> ActionView</span><span style="color:#E1E4E8">::</span><span style="color:#79B8FF">Base</span><span style="color:#E1E4E8">.</span><span style="color:#F97583">new</span><span style="color:#E1E4E8">()</span></span>
<span class="line"><span style="color:#E1E4E8"> av.</span><span style="color:#B392F0">view_paths</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> ActionController</span><span style="color:#E1E4E8">::</span><span style="color:#79B8FF">Base</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">view_paths</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> # need these in case your view constructs any links or references any helper methods.</span></span>
<span class="line"><span style="color:#E1E4E8"> av.</span><span style="color:#B392F0">class_eval</span><span style="color:#F97583"> do</span></span>
<span class="line"><span style="color:#F97583"> include</span><span style="color:#79B8FF"> Rails</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">application</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">routes</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">url_helpers</span></span>
<span class="line"><span style="color:#F97583"> include</span><span style="color:#79B8FF"> ApplicationHelper</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFAB70"> pdf_html</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> av.</span><span style="color:#B392F0">render</span><span style="color:#79B8FF"> template:</span><span style="color:#9ECBFF"> 'reports/generate_pdf'</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">layout:</span><span style="color:#9ECBFF"> 'layouts/pdf'</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">locals:</span><span style="color:#E1E4E8"> {</span><span style="color:#79B8FF">report:</span><span style="color:#E1E4E8"> @report}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> # use wicked_pdf gem to create PDF from the doc HTML</span></span>
<span class="line"><span style="color:#FFAB70"> doc_pdf</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> WickedPdf</span><span style="color:#E1E4E8">.</span><span style="color:#F97583">new</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">pdf_from_string</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#E1E4E8"> pdf_html,</span></span>
<span class="line"><span style="color:#79B8FF"> page_size:</span><span style="color:#9ECBFF"> 'Letter'</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> javascript_delay:</span><span style="color:#79B8FF"> 6000</span></span>
<span class="line"><span style="color:#E1E4E8"> )</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> # save PDF to disk</span></span>
<span class="line"><span style="color:#FFAB70"> pdf_path</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> Rails</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">root</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">join</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">'tmp/reports'</span><span style="color:#E1E4E8">, </span><span style="color:#9ECBFF">"</span><span style="color:#9ECBFF">#{</span><span style="color:#E1E4E8">@report</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">id</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">_</span><span style="color:#9ECBFF">#{</span><span style="color:#79B8FF">Date</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">today</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">iso8601</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">.pdf"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#79B8FF"> File</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">open</span><span style="color:#E1E4E8">(pdf_path, </span><span style="color:#9ECBFF">'wb'</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">do</span><span style="color:#E1E4E8"> |file|</span></span>
<span class="line"><span style="color:#E1E4E8"> file </span><span style="color:#F97583"><<</span><span style="color:#E1E4E8"> doc_pdf</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8"> @report.</span><span style="color:#B392F0">pdf</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> File</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">open</span><span style="color:#E1E4E8"> pdf_path</span></span>
<span class="line"><span style="color:#E1E4E8"> @report.</span><span style="color:#B392F0">save!</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"> # The report has now been saved elsewhere using Paperclip; we don't need to store it locally</span></span>
<span class="line"><span style="color:#79B8FF"> File</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">delete</span><span style="color:#E1E4E8">(pdf_path) </span><span style="color:#F97583">if</span><span style="color:#79B8FF"> File</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">exist?</span><span style="color:#E1E4E8">(pdf_path)</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"><span style="color:#F97583">end</span></span></code></pre>
Creating the ActionMailer mailer
Now we create the mailer which will call out to this ActiveJob job to generate the PDF:
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#E1E4E8">rails g mailer reportPdf</span></span></code></pre>
/app/mailers/report_pdf.rb
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#F97583">class</span><span style="color:#B392F0"> ReportPdf</span><span style="color:#E1E4E8"> < </span><span style="color:#B392F0">ApplicationMailer</span></span>
<span class="line"><span style="color:#F97583"> def</span><span style="color:#B392F0"> send_mail</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">report_id:</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8"> @report </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> Report</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">find</span><span style="color:#E1E4E8"> report_id</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF"> GeneratePdfJob</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">perform_now</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">report_id:</span><span style="color:#E1E4E8"> @report.</span><span style="color:#B392F0">id</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8"> @report.</span><span style="color:#B392F0">reload</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8"> attachments[</span><span style="color:#9ECBFF">"report_</span><span style="color:#9ECBFF">#{</span><span style="color:#79B8FF">Date</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">today</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">iso8601</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">.pdf"</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#79B8FF"> mime_type:</span><span style="color:#9ECBFF"> 'application/pdf'</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> content:</span><span style="color:#79B8FF"> open</span><span style="color:#E1E4E8">(@report.</span><span style="color:#B392F0">pdf</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">expiring_url</span><span style="color:#E1E4E8">).</span><span style="color:#B392F0">read</span></span>
<span class="line"><span style="color:#E1E4E8"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0"> mail</span><span style="color:#E1E4E8">(</span></span>
<span class="line"><span style="color:#79B8FF"> to:</span><span style="color:#E1E4E8"> @report.</span><span style="color:#B392F0">user</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">email</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF"> subject:</span><span style="color:#9ECBFF"> "Weekly PDF Email: </span><span style="color:#9ECBFF">#{</span><span style="color:#79B8FF">Date</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">today</span><span style="color:#9ECBFF">.</span><span style="color:#B392F0">iso8601</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8"> )</span></span>
<span class="line"><span style="color:#F97583"> end</span></span>
<span class="line"><span style="color:#F97583">end</span></span></code></pre>
So now all we need to do is call ReportPdf.send_mail(report_id: @report.id) to generate and send an email with a PDF attachment, powered by Paperclip. The PDF generation is done via the wicked_pdf gem.