The UX- and UI of most (micro)blogging platforms suck. I hate having to adjust to a “new” way to writing and sharing what I want to share. I always wanted an easy way to “write once and deploy everywhere” but with social media and content. With Duifje, I’ve achieved that!

In this article I quickly go through some of the ideas, code and implementation of this little project to maybe inspire you to do something similar with your Notion setup.

Where it all started

I tend to write from time to time and a thing I really hate about having a website or a place to post my thoughts is the UI or the workflow I have to go through in order to post something. Most editors that I’ve used on platforms like Wordpress or Ghost don’t come close to a good user experience. When I started pre-writing my articles on Notion however, I really liked the UX of just writing an article in that software. When I read that the platform was gonna receive an API, I knew that I was gonna make something to centralise all the content I make once and for all! I made Duifje (which means little pigeon/dove in Dutch). A little inspiration I took from the classic musical film “Ja Zuster, Nee Zuster”.

What does it do?

It simply automates the most frustrating part of writing content: publishing. My eventual use cases include: writing/publishing blog posts, writing tweets with content to engage with my game’s audience and more! All with automatic scheduling for the posts and tweets!

How does it work exactly? It takes a Notion page in a database (regardless of view) and looks for which articles are marked “Ready to Release” in their status. Then it checks what type they are and get’s the appropriate piece of code to publish it on the appropriate platform. The prototype of this project included two content “destinations”: my personal blog & a BBCode text file for posting on forums. This was useful for creating the devlog for my game “Clarice Clairvoyage”. This way I could just write the devlog once and have the content ready for these different platforms.

It's really nice to have a good overview of all of the content in one place.

After going through the release cycle, it would automatically publish the article on my website through the Github API and upload a text file with BBCode text to Notion. Then Duifje puts the article in the “Released” status, which lets me know that it’s been released! When I refresh my website the article is live and when I look at the Notion page I can see the BBCode text uploaded!

In the new version I wrote last weekend I added support for Tweets by adding a Twitter type to my Notion database. After writing a Twitter module for Duifje, I can now tweet from my Notion workspace! From now on I can just create a backlog of content for my Twitter or Clarice Clairvoyage’s Twitter without any issues! No more early posting when I don’t have to! I’m also freed from the absolutely _horrible _Twitter tweet schedule planner.

WHY IS THE DEFAULT DATE SET TO 5 DAYS LATER ± A FEW HOURS? WHY?!

A closer look at some code (feel free to skip this)

All of the code we’re going through is somewhat simplified and most of the error catching and more defensive code pieces are omitted. With that out of the way, first I get all of the pages that I put in the “Ready to be released” column of my content. The Notion SDK makes the really easy, which I use the Deno version of. The Node version seems to be pretty good as well.

let ready_pages = await notion.databases.query({
    database_id: NOTION_DATABASE_ID,
    filter: {
        property: 'Status',
        select: {
            equals: 'Ready to be released'
        }
    }
})

Next up, we’re gonna have to do the bulk of the functionality in one go, so bear with me. We’re gonna get the page blocks that hold our content, check if the publish date is correct and see what modules we want to use to publish. All of these things are determined by properties that I just set from Notion. So I don’t need to interact with the program at all as a user, it’s all data driven!

After that we go through all of these modules and render the content (We’ll get into one of these modules soon enough). After that we simply mark the page we published as “Released” and we’re done!

for (let row of ready_pages.results) {
		// Get more detailed block information and enrich with children
    let page = await notion.blocks.retrieve({ block_id: row.id })
    let children = await utils.get_tree(notion, page)

		// Check if the article can be released based on date
    let date = row.properties['Publish Date'].date
    if (!(date && new Date(date.start).getTime() < Date.now())) {
        continue
    }

		// Use the "Type" attribute to for which module its meant for
    let modules = [blog, twitter, bbcode].filter(
            module => module.tags.filter(
                tag => row.properties.Type.multi_select.map(type => type.name).indexOf(tag) !== -1
            ).length != 0
        )

		// Go through all the available modules, render & publish
    for (let module of modules) {
        let content = await module.render(row, page, children)
        await module.publish(row, page, content)
    }

		// Put the article under the "Released" column!
    await notion.pages.update({
        page_id: row.id,
        properties: {
            Status: {
                select: {
                    name: 'Released'
                }
            }
        }
    })
}

Now for some more magic: the modules. To make sure that I can put all different kinds of content in one place and that it’ll all publish correctly I use a module pattern. These modules are just objects with a render and publish method in them. It could be shorter in one step, but I separated the two to leave some room for dry running and stuff.

In between the blocks in a page (aka the children mentioned before) get parsed and formatted to work for the platform I’m publishing in. For my blog, this means a little Notion-to-markdown conversion. For Twitter, this means a simple flat text with some checks to make sure it stays under 280 characters. It also makes it easy to make your own little modules. Just write a thing that parses the Notion page information and a small function that interfaces with another platform and you’re done! Some ideas could include:

  • A Mailchimp integration that automatically sends out a mailinglist based on a page
  • A Twilio integration that sends out a text to a group of people
  • Other social media integrations for sharing media
  • Running the content through a text-to-speech program for accessibility

Some small additions + future plans

Since I initially started on this article and release I’ve already a myriad of different modules and features. I’ve made a little markdown output for sharing my posts on Markdown supported platforms that don’t have an API. I also added a web frontend that’s only accessible through Notion.

The embedded part of the application makes it easy to use the integration from the Notion UI!

This way I can activate the program right from my Notion page. I can also see when the program last ran. This combined with an automatic execution every 15 minutes makes for the best experience.

In the future I might add more specific options such as Twitter accounts (for tweeting on behalf of my indiegame’s account) or automatic scheduling of tweets if they don’t have a date assigned.

The code is available on Github if you want to take a look! Let me know what you think!


Bram Dingelstad

Hey there! My name is Bram Dingelstad: an indiedeveloper & activist. I write articles sometimes. You can hire me as well!

If you'd like to stay up to date for a new post, or see smaller updates of games I'm working on, follow me on Twitter!