EJS logo

How we integrated my Buttondown newsletter into my Kirby site

Article illustration for How we integrated my Buttondown newsletter into my Kirby site

If you’ve subscribed to my newsletter (and, if you like getting the latest news about type and typography, please do!), you might’ve read in the most recent issue that my old friend Keir helped me get the newsletter (which is powered by Buttondown) integrated into this website (which is powered by Kirby). So, although the newsletter itself is still sent using Buttondown, and it’s Buttondown that handles my subscribers, the archives now live on elliotjaystocks.com/newsletter and the individual issue pages are customised to look slightly different to the rest of this site. And, because the newsletter signup form lives on this site, too, I’m effectively using Buttondown as a “headless” email service — something that’s near impossible with most of its competitors.

There are a few reasons why I choose to do this. If you don’t care what they are, feel free to skip to the how-to section.

When I first decided to start a newsletter, I’d assumed it’d be just like publishing a blog, but with a different delivery method — but I was completely wrong. Although I do see blog posts as quite personal outputs, a newsletter is just different somehow. It’s hard to say exactly why, but I suspect it’s something to do with the fact that people have opted in to receive your updates. Sure, you could say the same of those who subscribe to your blog’s RSS feed, but publishing something that lands in a person’s inbox just feels inherently more intimate, and I’ve personally been surprised at how writing and publishing a newsletter has felt very different to writing and publishing a blog.

All this is to say: I’m glad I was wrong and I’m glad I’ve only integrated the two now. If I’d done it when I started the newsletter, I would’ve just been delivering blog posts across different channels. And although in some ways it might seem strange to integrate at all (didn’t I just say blogging and newsletter-ing were two different beasts?), I wanted to have more control over everything, like being able to fully customise the design of the newsletter experience, and even revisit the design of older issues if need be. Plus, now that my site’s redesign is now live — a key part of which is the new book / workshop / podcast / newsletter nav you can see up top — it felt sensible to start consolidating these various different projects. (The podcast, which currently lives on hellotypefriends.com, is next on my list.)

How we set up the Buttondown / Kirby integration

As I mentioned, Keir handled this side of things for me, and he built upon what Bastian had already done when he helped get the site up and running in the first place; i.e., I already have a “speaking” content type, so we applied the same logic to make a new “newsletter” content type. This is one of many great about Kirby: if you want a new kind of content, you just create a folder for it and ensure there’s a .txt file in there with the same name. In this case, that’s /content/newsletter/newsletter.txt, which maps to my /site/templates/newsletter.php template.

Then it’s just a case of populating the newsletter folder with sub-folders for each issue. Those are then prefixed with a number to determine their order (e.g. /content/newsletter/24_the-only-pipe) and each folder contains an issue.txt file and whatever assets I want to include.

That issue.txt file has few different fields, which can then be referenced in the issue.php template. That’s another thing I love about Kirby: you can add a field whenever you want, and then it’s instantly available for you to use. My issue.txt files look a bit like this:


In the issue.php template, among other things, I use an “intro” field so that I can style the introductory text a bit differently, and it’s wrapped in a conditional statement that includes more markup. This gives me a bit more control over the formatting if I decide not to use that field in every single issue:

<?php if ($page->intro()->isNotEmpty()): ?>
<div class="intro">
  <?= $page->intro()->kt() ?></h2>
<?php endif ?>

For the archive itself, where every issue is listed beneath the big ol’ signup form, the code looks like this:

<?php foreach ($issues as $issue): ?>
    <a href="<?= $issue->url() ?>"><span><?= $issue->issue() ?> /</span> <?= $issue->title() ?></a>
  <p><?= $issue->date()->toDate('d F Y') ?></p>
<?php endforeach ?>

One other thing Keir did was create a custom collection (in /site/collections/newsletters.php), which reverses the order of the newsletter issues to show latest first:

return function () {
    return page('newsletter')->children()->listed()->flip();

This saves having to code that in the newsletter.php template and also makes the content accessible elsewhere on the site. I’m not actually doing that — yet — but it’s a bit of future proofing, and the logic is the same as the “speaking” collection, which enables the home page to show my next speaking gig.

In terms of the custom styling for the issue pages so that they look visually distinct from the rest of the site, please feel free to dig around in the CSS — I won’t go into that here. Essentially, it’s all just based off the .newsletter.issue classes set on the body. Keir modified the header.php snippet for me to handle that:

<body class="<?= $page->slug() ?><?php if($page->parent()): ?> <?= $page->parent() ?><?php endif; ?><?php if($page->template() != $page->slug()): ?> <?= $page->template() ?><?php endif; ?>">

How I publish a new issue to the site and Buttondown

With the website tweaks live, and archives turned off at the Buttondown end, here’s how my newsletter publishing process now works:

  1. I compose a new issue of the newsletter in Markdown format (I either directly edit the issue.txt file used by Kirby with IA Writer, or I write in Notion, where I can then copy-and-paste the Markdown later).
  2. Once the issue is complete locally, I commit and push to GitHub, and — just like any other change I might make to the site — DeployHQ automatically deploys the GitHub repo to my web host. (If you subscribe to the newsletter via RSS, you’ll get it at this point.)
  3. Buttondown reads the RSS feed for my newsletter (a check that runs every 30 minutes, apparently) and automatically creates a draft email for me in the admin. Wow! Kind of magical.
  4. With the URL of the new issue live on my site, I open up the new draft email and paste that URL into Buttondown’s “canonical URL” field so that the “read this email in your browser” link will go to my site instead of the Buttondown archive. (Again, if you’re doing this, it’s sensible to disable archives in your Buttondown settings.)
  5. I then edit that email in Buttondown as required (sometimes minor copy changes are handy, like rephrasing the first two sentences of the most recent issue to make it appropriate to the medium) and send myself a couple of test emails as I do so.
  6. I hit “send”!

At that point, the email then starts arriving in my subscribers’ inboxes, and if you choose to hit “read this email in your browser”, you’ll get taken to the relevant issue page on this site.

A final note: the line between blog, newsletter, and anything else that lives collectively on a site like this is somewhat blurred when you bring RSS into the equation. You can, after all, subscribe to the newsletter using the newsletter feed, just as you can subscribe to the blog using the blog feed. In fact, you can also subscribe to the speaking feed, and indeed subscribe to the “everything” feed that smushes all three of those together. (You’re crazy, and I salute you.) But I still feel like they’re fundamentally different, and I’d personally prefer it if folks subscribed via email rather than RSS, because email is the “voice” of the newsletter. But hey, it’s up to you, and it’s important to me that the web stays open and these feeds are as useful as they can be for those who have that preference.

However you subscribe, if you do subscribe, then thank you! And I hope this was useful to folks thinking of doing something similar.

Previous post

2023 in review