DEV Community

loading...

Generating PDFs from React for Emails using Material-UI, Mailgun, and PDFShift

slruh profile image Ryan Hurley Updated on ・4 min read

Three years ago my partner and I moved to NYC so she could pursue a career as a stage manager. As a freelancer, she is constantly working on new productions. Though the shows can be very different, parts of her job can be very repetitive.

In my spare time, I'm slowly building a website for her complete with a suite of features to help her run productions.

Emails

To start, I wanted to focus on the emails she sends. Throughout a production, she sends a lot of emails including:

  • Rehearsal reports - status updates after each rehearsal
  • Production reports - status updates after a show
  • Daily calls - indicates when specific actors and crew should show up for a rehearsal.

For all of these, she manually fills in a template. She then copies the filled in template into an email while also saving and attaching it as a PDF. It's very tedious for her and I want it to be the first feature of the website.

Success Criteria

I had a few goal for this project:

  1. The HTML and CSS used to preview the email client side must be used as the body of the email and as the contents of the PDF without alterations. This will make it much easier to make changes to email templates without introducing bugs.
  2. The text in the generated PDF should be selectable.
  3. The solution should eventually be able to run entirely server side (for security reasons).

Existing Tech Stack

I wanted to continue to use React but would be focusing on newer versions so I can play with hooks (I'm a little behind the curve). I also opted to use Material-UI and was able to use it in the emails as well.

Possible but Discarded Solutions

I knew the PDF generation would be the trickiest problem and I did not want to roll my own PDF writing software so I did some research.

First, I found a way to convert HTML into a SVG, then into a png, and eventually into a PDF. You can find this approach here. Unfortunately, the generated PDFs no longer have selectable text which can be a problem since that would prevent copy and paste and may have other accessibility issues.

The article also had a link to another blog where the author used KendoReact's pdf generation library to create a PDF from HTML. This was very promising until I saw the license cost for KendoReact ($899). That was a hard pass for a website that was only going to be used by my partner.

I also researched other libraries like React-pdf but it forces you to write using special React components and not normal HTML. This would not work for me since I need to write my email templates using tables for compatibility with email clients.

The Winner, PDFShift

I finally found something that would check all the boxes. PDFShift is a service that can be called over the internet but also has a node library.

When doing PDF conversions, it can either be given a string containing HTML or a link to a website. It then returns the generated PDF via callback, Promises, or webhooks.

In addition, it supports a sandbox mode for development that is entirely free but adds a ReactPDF watermark. Even with sandbox mode off, PDFShift gives you 50 free credits per month. After that, the cheapest plan is $10 a month for 500 credits.

Implementation

The idea is relatively simple:

  1. Create a React component that represents the body of the email. This will be used to generate the HTML for the email itself. It can also be used as a way to preview the email.
  2. Use renderToString of react-dom/server and ServerStyleSheets from material-ui to generate the HTML and CSS respectively for the email.
  3. Construct a full HTML doc by adding the generated CSS to the head and the HTML to the body.
  4. Convert this doc to a PDF using PDFShift
  5. Use mailgun-js to send an email with the HTML doc as the body along with the PDF as an attachment.

Example code

This code is spread throughout my website and is not guaranteed to work but it should give you a rough idea of how the whole thing works. PerformanceReportEmail contains the HTML table for the email itself.

  import {
    ServerStyleSheets,
    ThemeProvider, 
  } from "@material-ui/core/styles";
  import Mailgun = from "mailgun-js";
  import PdfShift from "pdfshift";
  import PerformanceReportEmail from "../PerformanceReportEmail";
  import theme from "../../theme"; // My custom material-ui theme colors

  const mailgunClient = new Mailgun({apiKey: "YOUR MAILGUN KEY", domain: "YOUR MAILGUN DOMAIN"});
  const pdfShift = new PdfShift("YOUR PDF SHIFT KEY HERE");
  const sheets = new ServerStyleSheets();
  const generatedHtml = ReactDOMServer.renderToString(
    sheets.collect(
      <ThemeProvider theme={theme}>
        <PerformanceReportEmail production={production} report={report} />
      </ThemeProvider>
    )
  );

  const cssString = sheets.toString();
  const email = `<!DOCTYPE html>
    <html>
      <head>
        <style>${cssString}</style>
        <style type="text/css"> 
          @media screen and (max-width: 630px) {}
        </style>  
      </head>
      <body style="padding:0; margin:0">${generatedHtml}</body>
    </html>
  `;

  const pdfOptions = {
    margin: "20px",
    sandbox: true,
  };

  pdfShift.convert(email, pdfOptions)
    .then((pdfBuffer) => {
      const attachment = new mailgunClient.Attachment({
        data: pdfBuffer,
        filename: "report.pdf"
      });
      const email = {
        from: 'info@YOUR MAILGUN DOMAIN.com',
        subject: 'Report',
        html: email,
        to: "john@doe.com",
        attachment: attachment
      }; 
      return mailgunClient.messages().send(email);
    });

Discussion (0)

pic
Editor guide