Developers · Go

A meeting bot in Go: join a meeting with coder/websocket

A Go program that joins a meeting and reports who is in it, with what it printed when it was run.

Ran on 10 November 2026Language GoJob Join a meeting

What it does

A Go program that dials the meeting's socket, introduces itself with its key, and prints who is in the room. It uses github.com/coder/websocket and its wsjson helpers.

It needs a key and a meeting code in MEETFUL_KEY and MEETING. Two curl requests make both. MEETFUL_URL is optional and defaults to https://meetful.com.

Go 1.22 or later. The socket programs use github.com/coder/websocket (go get github.com/coder/websocket@v1.8.12); the transcript program uses only the standard library.

The code

// Join a Meetful meeting as a bot from Go. go get github.com/coder/websocket
// MEETFUL_KEY=tmk_… MEETING=abc-defg-hij go run .
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"github.com/coder/websocket"
	"github.com/coder/websocket/wsjson"
)

type member struct{ Name, Kind string }
type message struct {
	Type    string
	Meeting string
	Why     string
	You     struct{ Name, Emoji string }
	Members []member
	Can     []string
}

func main() {
	base := os.Getenv("MEETFUL_URL")
	if base == "" {
		base = "https://meetful.com"
	}
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	conn, _, err := websocket.Dial(ctx, "ws"+strings.TrimPrefix(base, "http"), nil)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close(websocket.StatusNormalClosure, "done")
	conn.SetReadLimit(4 << 20) // the room's state can be larger than the 32 KB default

	hello := map[string]string{"type": "agent-hello", "key": os.Getenv("MEETFUL_KEY"), "meeting": os.Getenv("MEETING")}
	if err := wsjson.Write(ctx, conn, hello); err != nil {
		log.Fatal(err)
	}
	for {
		var m message
		if err := wsjson.Read(ctx, conn, &m); err != nil {
			log.Fatal(err)
		}
		switch m.Type {
		case "agent-ready":
			var people []string
			for _, x := range m.Members {
				if x.Kind == "person" {
					people = append(people, x.Name)
				}
			}
			here := strings.Join(people, ", ")
			if here == "" {
				here = "nobody yet"
			}
			fmt.Printf("In %s as %s %s. People here: %s.\n", m.Meeting, m.You.Emoji, m.You.Name, here)
			fmt.Printf("It may: %s\n", strings.Join(m.Can, ", "))
			return
		case "agent-refused":
			log.Fatal("Refused: " + m.Why)
		}
	}
}

Run it

MEETFUL_KEY=tmk_… MEETING=abc-defg-hij go run .

What it printed

In gik-fhvv-dcu as 🤖 go bot. People here: Dana, Sam.
It may: say, chat, draw, note

Run on 10 November 2026 against a Meetful server built from the same code as meetful.com, with two people — Dana and Sam — in the meeting saying three lines. Nothing above was edited.

How it works

coder/websocket refuses any message bigger than 32 KB unless you raise the limit. The room sends its whole state as one message, and that grows with a long meeting, so the program raises the limit to 4 MB right after dialling.

Decoding into a small struct with only the fields you use is enough: encoding/json ignores the rest, and matches field names without caring about case, so Type reads "type".

What can go wrong

Refused: no such key — mint one with POST /api/keys

The key is wrong, or was never minted on this server. A key from one Meetful server does not work on another.

Refused: no such meeting

The meeting code is not one that is open. A meeting that has ended, or a code with a typo, gives this.

read limited at … bytes

The library's default limit — 32768 bytes, set in its read.go — was left in place and a message from the room was bigger. Call conn.SetReadLimit after Dial.

Next

In Go: live transcript · speak · get the transcript

The same in Node.js · Python

The whole API · Every socket message and webhook · All guides

Try Meetful

Meetings that take their own notes

Free video meetings in the browser: every line of the transcript under the name of who said it, and the summary and action items when you hang up. Nothing to install — guests just open the link.