Vlad's Roam Garden

Powered by 🌱Roam Garden

How to create a serverless Matrix Chat bot


I have a strong preference for not running stateful infrastructure and so when I was getting into the world of Matrix development a natural question for me was - how does one create a serverless bot for Matrix.

It's unfortunately not as straightforward as one might wish though - there are no native support for that & no-one (that I can find) have discovered and written up a similar guide that adopts existing Matrix facilities for these purposes.

What follows is a result of my explorations and experiments codified into an easily replicable recipe


Goal: We'll be building a simple "echo" bot, that automatically joins any room you invite it to & when you send a message starting with !echo it'll repeat the content of the message back to you.

You can test an existing version of this bot running on Val.town (and fork it) here: https://www.val.town/v/vlad.serverlessMatrixEchoBot

Short version

Create a new bot account

Register webhook (val.town express endpoint for your val) using https://matrix-serverless.netlify.app/ (log in with the bot account)

The bot should be good to go - it'll auto-join rooms it's invited into and reply to echo messages.

Create a new Matrix account

There are no explicit "bot" facilities in Matrix, so we'll just use a new Matrix user as our bot account.

Create a handler function

You can see (and run) a basic "echo" bot implemented on Val.town:

Val.town makes it really easy to create/run/fork serverless functions.

To get you own version of the bot - you can fork the Val below (you'd need to

for bot to be able to send you replies)

Your HTTP endpoint would be called with the content of all the events the bot would "see". This includes both messages from the rooms the bot is in and DM's (as well as invites to join new rooms).

For the rooms with encryption enabled - you'll get the message in an encrypted form.

You will not receive notifications for the messages the bot user sends itself.

The handler function does three main things of note:

It accepts all room invitations bot receives

This is necessary if you want to create a bot that anyone can interact with (as is the case for serverless-echo@matrix.org)

You may skip this step if you want to have a private bot (in case of my transcription bot I just manually accepted the room invite from the bot side)

An invitation event would look like:

  if (event.membership === "invite") {
    await @me.joinMatrixRoom(matrixToken, event.room_id);
    return res.json({ rejected: [] });
  }

For messages starting with !echo - it'll send back the content of the message

You average unencrypted message notification would look like

In our echo bot we'll just read out the content (notification.content.body) and send it back tho the room of origin (notification.room_id) if it starts with !echo

  const text = event.content.body;
  const roomId = event.room_id;
  
  const echoText = "!echo ";
  if (text.startsWith(echoText)) {
    await @me.sendMatrixChatRoomTextMessage(
      matrixToken,
      roomId,
      text.slice(echoText.length),
    );
  }

In both cases it'll send an HTTP response telling Matrix that event was successfully processed

If this is not done - Matrix will keep re-sending the same event again and again

return res.json({ rejected: [] });

Emulate webhook mechanism through Push notifications

There are no explicit facilities to create serverless bots in Matrix

But there is a push notifications delivery mechanism that can, for any new event that matches filter rules, you've specified, send a notification to email or an HTTP endpoint

Which is most of what we want to be able to create a serverless bot.

See https://spec.matrix.org/v1.7/client-server-api/push-notifications for details on how push notifications work.

The basic setup required for our purposes is to create a new Pusher

Key fields to pay attention to:

data

url

The endpoint URL for your bot.

MUST be an HTTPS URL with a path of /_matrix/push/v1/notify

For example in case of our echo bot the URL would be: https://vlad-serverlessMatrixEchoBot.express.val.run/_matrix/push/v1/notify

kind

needs to be http

pushkey

unique id of the pusher

I've built a small tool that simplifies a setup for this at https://matrix-serverless.netlify.app/

It allows you to log in with Matrix (make sure to use the newly created "bot" account) and create a pusher by specifying the fields I mentioned above.

It's very bare-bones right now, you're welcome to contribute to make it nicer at https://github.com/Stvad/matrix-serverless-setup

(this is actually the main "trick" from here you'd proceed as if you're building a serverless bot for any other platform)

After you forked the handler above, set the matrix token secret and registered the webhook - your bot should be ready to go!

Caveats

Encrypted messages

For rooms that are encrypted - you'll receive messages in the encrypted form.

It should be possible to share the keys with the bot and decrypt the messages - I haven't really looked into how to do that yet though.

You won't receive notifications for the events your user sends

This is a reasonable default behavior, but it also limits your ability to create self-bots in an unfortunate way.

For example: I dearly want to build a bot that would allow me to "snooze" messages similar to Slack. Ideally I'd do it by listening to any "react" messages in all my rooms and DMs and snoozing messages that get a particular react from me. But this is impossible right now.

Another thing I want to have is automatic transcription for all audio messages I send (Transcribing Matrix Chat voice messages with OpenAI Whisper), but right now it'd only be possible to configure transcription for messages I receive.

This method does not allow you to "respond" to webhooks synchronously - you must send message reply asynchronously and as a consequence the bot needs your Matrix token.

I think it should be possible to configure a more precise filtering on what kinds of events your handler gets notified on (e.g. text content, message type)

I haven't quite figured it out yet, but I think specifying a pushrule scoped to a device that you have for your pusher and setting appropriate conditions should work.

I imagine you'd also need to disable standard global push rules

Would be curious to hear from someone who figures this out in more detail.

Get in touch

Subscribe to future updates from my garden:

Talk to me on Twitter

Comments

You can add comments by using Matrix Highlight or by joining #build-serverless-bot:matrix.org room using Matrix Chat