Syndicating Content to Twitter

One of the core principles of the IndieWeb is that people should own their own content. Controlling how and where they publish makes users more independent from big content silos.

However, the main reason why people publish on Twitter / Medium or other platforms is that they can reach a much bigger audience there - everyone’s on them, so you have to be too. Publishing on a personal site can cut you off from those readers. That’s why it might be a good idea to automatically post copies of your content on these sites whenever you publish something new.

This practice is known as “POSSE” (Publish on your Own Site, Syndicate Elsewhere). It enables authors to reach people on other platforms while still keeping control of the original content source.

For the recent relaunch of my personal website, I wanted to embrace some of these ideas. I included a section called notes featuring small, random pieces of content - much like tweets. These notes are perfect candidates for syndication to Twitter.

Syndication on Static Sites

My site is built with Eleventy, a static site generator based on node, and hosted on Netlify. Static sites are awesome for a variety of reasons, but interacting with other platforms typically requires some serverside code - which they don’t have.

Luckily though, Netlify provides a service called “Functions”, which lets you write custom AWS lambda functions without the hassle of dealing with AWS directly. Perfect! 🤘

A content feed

The first step is to publish a machine-readable feed of the content we want to syndicate. That’s exactly what RSS-Feeds are for - but they’re usually in XML format, which is not ideal in this case.

For my own site, I chose to provide notes as a simple JSON object. I already have an atom feed for content readers, and JSON makes the note processing easier later on.

My feed looks something like this:

// notes.json
"id": 1,
"date": "2018-12-02T14:20:17",
"url": "",
"content": "Here's my first note!",
"syndicate": true

All entries also include a custom syndicate flag that overrides the auto-publishing behaviour if necessary.

Event-Triggered Functions

Now for the tricky part: we need to write a lambda function to push new notes to Twitter. I won’t go into detail on how to build lambda functions on Netlify, there are already some great tutorials about this:

Be sure to also check out the netlify-lambda cli, a very handy tool to test and build your functions in development.

To trigger our custom function everytime a new version of the site was successfully deployed, we just need to name it deploy-succeeded.js. Netlify will then automatically fire it after each new build, while also making sure it’s not executable from the outside.

Whenever that function is invoked, it should fetch the list of published notes from the JSON feed. It then needs to check if any new notes were published, and whether they should be syndicated to Twitter.

// deploy-succeeded.js
exports.handler = async () => {
return fetch('')
.then(response => response.json())
.catch(err => ({
statusCode: 422,
body: String(err)

Since we will have to interact with the Twitter API, it’s a good idea to use a dedicated helper class to take some of that complexity off our hands. The twitter package on npm does just that. We will have to register for a developer account on Twitter first though, to get the necessary API keys and tokens. Store those in your project’s .env file.


Use these keys to initialize your personal Twitter client, which will handle the posting for your account.

// Configure Twitter API Client
const twitter = new Twitter({
consumer_key: process.env.TWITTER_CONSUMER_KEY,
consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
access_token_key: process.env.TWITTER_ACCESS_TOKEN_KEY,
access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET

Right. Now we need to look at the notes array and figure out what to do. To keep it simple, let’s assume the latest note is a new one we just pushed. Since the JSON feed lists notes in descending date order, that would be the first item in the array.

We can then search twitter for tweets containing the latest note’s URL (we will include that in every syndicated tweet to link back to the original source). If we find anything, then it’s already been published and we don’t need to do anything. If not, we’ll go ahead.

const processNotes = async notes => {
// assume the last note was not yet syndicated
const latestNote = notes[0]

// check if the override flag for this note is set
if (!latestNote.syndicate) {
return {
statusCode: 400,
body: 'Latest note has disabled syndication.'

// check twitter for any tweets containing note URL.
// if there are none, publish it.
const search = await twitter.get('search/tweets', { q: latestNote.url })
if (search.statuses && search.statuses.length === 0) {
return publishNote(latestNote)
} else {
return {
statusCode: 400,
body: 'Latest note was already syndicated.'

Next, we need to prepare the tweet we want to send. Since our self-published note does not have the same restrictions that twitter has, we should format its content first.

My implementation simply strips all HTML tags from the content, makes sure it is not too long for Twitter’s limit, and includes the source url at the end. It’s also worth noting that Eleventy will escape the output in the JSON feed, so characters like " will be encoded to " entities. We need to reverse that before posting.

// Prepare the content string for tweet format
const prepareStatusText = note => {
const maxLength = 200

// strip html tags and decode entities
let text = note.content.trim().replace(/<[^>]+>/g, '')
text = entities.decode(text)

// truncate note text if its too long for a tweet.
if (text.length > maxLength) {
text = text.substring(0, maxLength) + '...'

// include the note url at the end.
text = text + ' ' + note.url
return text

When everything is done, we just need to send our note off to Twitter:

// Push a new note to Twitter
const publishNote = async note => {
const statusText = prepareStatusText(note)
const tweet = await'statuses/update', {
status: statusText
if (tweet) {
return {
statusCode: 200,
body: `Note ${} successfully posted to Twitter.`

Hopefully that all worked, and you should end up with something like this in your timeline:

🎉 You can find the finished lambda function along with the rest of the source code for this site on Github.

Further Resources


What’s this?
  1. Sukil Etxenike
    Reading a use case by @mxbck for implementing #Indieweb syndication. Some questions: does all that magic happen in 10 seconds? And where do you store the .env file for Netlify to pick it? 😕 Thanks!…
  2. IndieWeb.Life
    Static Indieweb pt1: Syndicating Content | Max Böck - Frontend Web Developer #indieweblife…
  3. Nicolas Hoizey
    I've been able to replace my own Node script with yours (and a little improvement for images), with 3 times less code, and more robust! 👍 Still run manually, this site is not (yet?) on Netlify… 😉
  4. Andy Bell
    Yeh I used this great resource for my site. I ended up actually removing them because I managed to attract a bit of spam with them, unfortunately.
Show All Webmentions (20)
  1. Kevin Marks
    Spam from twitter? We haven't had much native webmention spam, though we have been looking out for it.
  2. Andy Bell
    Bit of both unfortunately. I'm not overly fond of having comments etc on my site, so I already had a pretty dim view.
  3. fluffy 💜 🎂
    You could always receive webmentions and not display them. There are several sites which do that to gauge reactions around the web without making those reactions public.
  4. Andy Bell
    Yeh for sure. Making my own handler for them would be cool!
  5. Awesome thanks
  6. admin
  7. admin
  8. Max Böck
    oh nice! might have to steal that local cache thing from you, I'm hitting that 7day API limit too
  9. Nicolas Hoizey
    I didn't make it yet, but really planning to, I'll let you know. I should write about it, anyway.
  10. Chris Collins
    This is ideal. Cheers!
  11. Nicolas Hoizey
    You're welcome! 🙏