Skip to main content
Home / Articles /

A Gutenburg-Powered Newsletter

I like Gutenberg, the new WordPress editor. I'm not oblivious to all the conversation around accessibility, UX, and readiness, but I know how hard it is to ship software and I'm glad WordPress got it out the door. Now it can evolve for the better.

I see a lot of benefit to block-based editors. Some of my favorite editors that I use every day, Notion and Dropbox Paper, are block-based in their own ways and I find it effective. In the CMS context, even moreso. Add the fact that these aren't just souped-up text blocks, but can be anything! Every block is it's own little configurable world, outputting anything it needs to.

I'm using Gutenberg on a number of sites, including my personal site and my rambling email site, where the content is pretty basic. On a decade+ old website like CSS-Tricks though, we need to baby step it. One of our first steps was moving our newsletter authoring into a Gutenberg setup. Here's how we're doing that.

Gutenberg Ramp

Gutenberg Ramp is a plugin with the purpose of turning Gutenberg on for some areas and not for others. In our case, I wanted to turn on Gutenberg just for newsletters, which is a Custom Post Type on our site. With the plugin installed and activated, I can do this now in our functions.php:

if (function_exists('gutenberg_ramp_load_gutenberg')) {
  gutenberg_ramp_load_gutenberg(['post_types' => [ 'newsletters' ]]);
}

Which works great:

Classic editor for posts, Gutenberg for the Newsletters Custom Post Type

We already have 100+ newsletters in there, so I was hoping to only flip on Gutenberg over a certain date or ID, but I haven't quite gotten there yet. I did open an issue.

What we were doing before: pasting in HTML email gibberish

We ultimately send out the email from MailChimp. So when we first started hand-crafting our email newsletter, we made a template in MailChimp and did our authoring right in MailChimp:

The MailChimp Editor

Nothing terrible about that, I just much prefer when we keep the clean, authored content in our own database. Even the old way, we ultimately did get it into our database, but we did it in a rather janky way. After sending out the email, we'd take the HTML output from MailChimp and copy-paste dump it into our Newsletter Custom Post Type.

That's good in a way: we have the content! But the content is so junked up we can basically never do anything with it other than display it in an <iframe> as the content is 100% bundled up in HTML email gibberish.

Now we can author cleanly in Gutenberg

I'd argue that the writing experience here is similar (MailChimp is kind of a block editor too), but nicer than doing it directly in MailChimp. It's so fast to make headers, lists, blockquotes, separators, drag and drop images... blocks that are the staple of our newsletter.

Displaying the newsletter

I like having a permanent URL for each edition of the newsletter. I like that the delivery mechanism is email primarily, but ultimately these are written words that I'd like to be a part of the site. That means if people don't like email, they can still read it. There is SEO value. I can link to them as needed. It just feels right for a site like ours that is a publication.

Now that we're authoring right on the site, I can output <?php the_content() ?> in a WordPress loop just like any other post or page and get clean output.

But... we have that "old" vs. "new" problem in that old newsletters are HTML dumps, and new newsletters are Gutenberg. Fortunately this wasn't too big of a problem, as I know exactly when the switch happened, so I can display them in different ways according to the ID. In my `single-newsletters.php`:

<?php if (get_the_ID() > 283082) { ?>

  <main class="single-newsletter on-light">
    <article class="article-content">
      <h1>CSS-Tricks Newsletter #<?php the_title(); ?></h1>
      <?php the_content() ?>
    </article>
  </main>
  
<?php } else { // Classic Mailchimp HTML dump ?>

  <div class="newsletter-iframe-wrap">
    <iframe class="newsletter-iframe" srcdoc="<?php echo htmlspecialchars(get_the_content()); ?>"></iframe>
  </div>

<?php } ?>

At the moment, the primary way we display the newsletters is in a little faux phone UI on the newsletters page, and it handles both just fine:

Old and new newsletters display equally well, it's just the old newsletters need to be iframed and I don't have as much design control.

So how do they actually get sent out?

Since we aren't creating the newsletters inside MailChimp anymore, did we have to find another way to send them out? Nope! MailChimp can send out a newsletter based on an RSS feed.

And WordPress is great at coughing up RSS feeds for Custom Post Yypes. You can do...

/feed/?post_type=your-custom-post-type

But... for us, I wanted to make sure that any of those old HTML dump emails never ended up in this RSS feed, so that the new MailChimp RSS feed would never see them an accidentally send them. So I ended up making a special Page Template that outputs a custom RSS feed. I figured that would give us ultimate control over it if we ever need it for even more things.

<?php
/*
Template Name: RSS Newsletterss
*/

the_post();
$id = get_post_meta($post->ID, 'parent_page_feed_id', true);

$args = array(
  'showposts' => 5,
  'post_type'  => 'newsletters',
  'post_status' => 'publish',
  'date_query' => array(
     array(
      'after'     => 'February 19th, 2019'
    )
  )
);

$posts = query_posts($args);

header('Content-Type: '.feed_content_type('rss-http').'; charset='.get_option('blog_charset'), true);
echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>';
?>

<rss version="2.0"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  <?php do_action('rss2_ns'); ?>>

<channel>
  <title>CSS-Tricks Newsletters RSS Feed</title>
  <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
  <link><?php bloginfo_rss('url') ?></link>
  <description><?php bloginfo_rss("description") ?></description>
  <lastBuildDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_lastpostmodified('GMT'), false); ?></lastBuildDate>
  <language><?php echo get_option('rss_language'); ?></language>
  <sy:updatePeriod><?php echo apply_filters( 'rss_update_period', 'hourly' ); ?></sy:updatePeriod>
  <sy:updateFrequency><?php echo apply_filters( 'rss_update_frequency', '1' ); ?></sy:updateFrequency>

  <?php do_action('rss2_head'); ?>

  <?php while( have_posts()) : the_post(); ?>

    <item>
      <title><?php the_title_rss(); ?></title>
      <link><?php the_permalink_rss(); ?></link>
      <comments><?php comments_link(); ?></comments>
      <pubDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_post_time('Y-m-d H:i:s', true), false); ?></pubDate>
      <dc:creator><?php the_author(); ?></dc:creator>
  <?php the_category_rss(); ?>
      <guid isPermaLink="false"><?php the_guid(); ?></guid>

      <description><![CDATA[<?php the_excerpt_rss(); ?>]]></description>

      <content:encoded><![CDATA[<?php the_content(); ?>]]></content:encoded>

      <wfw:commentRss><?php echo get_post_comments_feed_link(); ?></wfw:commentRss>
      <slash:comments><?php echo get_comments_number(); ?></slash:comments>

      <?php rss_enclosure(); ?>
      <?php do_action('rss2_item'); ?>

    </item>

  <?php endwhile; ?>

</channel>

</rss>

Styling...

With a MailChimp RSS campaign, you still have control over the outside template like any other campaign:

But then content from the feed just kinda gets dumped in there. Fortunately, their preview tool does go grab content for you so you can actually see what it will look like:

And then you can style that by injecting a <style> block into the editor area yourself.

That gives us all the design control we need over the email, and it's nicely independent of how we might choose to style it on the site itself.

icon-link icon-logo-star icon-search icon-star