Bridge Nostr long-form notes (kind 30023) into a Telegram channel. Fetch authors from a static list or a follow list (kind 3), format posts, and publish. Supports dry-run for safe testing.
Environment variables:
N2TG_TG_BOT_TOKEN(required): Telegram bot tokenN2TG_TG_CHAT_ID(required): Target chat/channel ID (e.g.-100xxxxxxxxxx)N2TG_MODE:authorsorfollowlist(default:authors)N2TG_AUTHORS: comma-separated author pubkeys (whenauthorsmode)N2TG_FOLLOWLIST_PUBKEY: pubkey to read follow list from (whenfollowlistmode)N2TG_NOSTR_RELAYS: comma-separated relay URLsN2TG_TG_PREFIX: prefix line for Telegram posts (default:New Nostr article:)N2TG_LINK_NADDR_BASE: base URL for naddr links (default:https://njump.me/)N2TG_LINK_NIP05_BASE: base URL for NIP-05 linksN2TG_SYNC_INTERVAL_MS: sync interval in ms (default:3600000)N2TG_TG_POLL_TIMEOUT_MS: Telegram long-poll timeout in ms (default:10000)N2TG_DRY_RUN: set to1to simulate sends (no Telegram posts)N2TG_SYNC_ALL_ON_EMPTY: when no pinned message exists, set1to backfill from baseline0(epoch). If unset/0, publishing halts until a pinned message is set.
mix deps.get
mix compile
MIX_ENV=dev mix dialyzer
Run with env vars set. For dry-run:
export N2TG_TG_BOT_TOKEN=xxx
export N2TG_TG_CHAT_ID=-100xxxxxxxxxx
export N2TG_DRY_RUN=1
iex -S mix
Build and run locally:
docker build -t nostr2tg:local .
docker run --rm \
-e N2TG_TG_BOT_TOKEN=xxx \
-e N2TG_TG_CHAT_ID=-100xxxxxxxxxx \
-e N2TG_DRY_RUN=1 \
nostr2tg:local
This repo includes render.yaml and a multi-stage Dockerfile.
- Push the repo to GitHub.
- In Render, create a new service → Worker → “Use existing render.yaml”.
- Configure environment variables:
N2TG_TG_BOT_TOKEN(secret)N2TG_TG_CHAT_ID(secret)
- Optional:
N2TG_MODE,N2TG_AUTHORS,N2TG_FOLLOWLIST_PUBKEY,N2TG_NOSTR_RELAYS,N2TG_TG_PREFIX,N2TG_LINK_NADDR_BASE,N2TG_LINK_NIP05_BASE,N2TG_SYNC_INTERVAL_MS,N2TG_DRY_RUN.
- Deploy. The worker process runs
/app/start.shwhich launches the Erlang release.
- Dry-run logs the full flow and skips Telegram network calls.
- Baseline initialization and pinning:
- On startup, baseline is derived from the Telegram channel's pinned message.
- If the pinned text ends with a line like
Published: YYYY-MM-DD HH:MM:SS(UTC), that timestamp is used. - Otherwise, the pinned message's own
dateis used as baseline.
- If the pinned text ends with a line like
- If there is no pinned message:
- With
N2TG_SYNC_ALL_ON_EMPTY=1, baseline is0(backfill everything). - Otherwise, publishing halts until a message is pinned.
- With
- After each batch, the last sent message is pinned to keep the baseline current.
- On startup, baseline is derived from the Telegram channel's pinned message.