Hacking Notifications into Anytype
An unexpected fight with its API.
Anytype is an awesome piece of software. It’s one of my main self-hosted services and has become my go-to for knowledge hoarding and managing small personal projects. I’ve also been using it to jot down everyday tasks unrelated to actual projects. The only thing missing for me were notifications for tasks I noted down for the future, as I found myself forgetting them.
So with the help of ✨Claude Code✨ in a free afternoon, I created a little service that periodically fetches task objects, saves them to a DB, and then notifies me (currently only supporting Telegram bot notifications, but keeping different notification channels in mind for possible future expansion).
To note here, I am aware that Anytype is open source and this feature could have been a contribution, but this was undeniably the faster path to a working solution.
So far so good, everything was working.
I used the API exposed by Anytype’s Desktop application (running on localhost) as it was the easiest way to develop on a local machine.
However, when attempting to deploy the service and use the API from my self-hosted Anytype backend (using grishy/any-sync-bundle), I made a very interesting discovery:
The API is not actually exposed from the backend, but rather only from the clients, which is an interesting design decision to say the least.
I suppose this makes sense when using Anytype’s cloud instance to save resources, but for a self-hosted service, I was sort of expecting the API to be there out of the box.
This is by no means meant as a jab at the creator and maintainer of the all-in-one bundle Anytype setup (which is actually really amazing!), there is actually already an open issue to add the API to the stack. It’s more of a weird decision on Anytype’s side in my opinion.
My solution was to wrap anyproto/anytype-cli in a docker container and add it to my Anytype stack. I had to jump through some hoops to get it connected to my self hosted instance (see repo for more details on how to set it up), but in the end it worked really nicely.
Full Docker Example
services:
anytype-cli:
build:
context: ./anytype-cli-docker
args:
ANYTYPE_CLI_VERSION: v0.1.9
ports:
- "31012:31012"
volumes:
- ./anytype-cli-docker/data:/data
- ./anytype-cli-docker/anytype-home:/root/.anytype
- ./client-config.yml:/root/.config/anytype/network.yml:ro
command: ["serve", "--listen-address", "0.0.0.0:31012"]
restart: unless-stopped
anytype-notifications:
build: ./anytype-notifications
ports:
- "8080:8080"
environment:
TZ: ${TZ}
ANYTYPE_BASE_URL: http://anytype-cli:31012
ANYTYPE_API_KEY: ${ANYTYPE_API_KEY}
SYNC_INTERVAL_MINUTES: "60"
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID}
volumes:
- ./anytype-notifications/data:/app/data
depends_on:
- anytype-cli
restart: unless-stopped
Just add the all-in-one solution (and a Tailscale sidecar for accessing it privately and securely) and you’re golden!