Make yourself a bot
Telegram's rule is that software speaks through bots, and bots are minted by another bot. Open @BotFather in Telegram, send it /newbot, pick a display name and a username ending in bot. It hands back a token — that string is the bot's identity, so treat it like a password.
/newbot
Site messages
mysitemessages_bot /newbot Site messages mysitemessages_bot
Find your chat id
A bot can't message you until you've messaged it once — Telegram's politeness rule. Open your new bot, send it anything, then ask the API who wrote in. The number at chat.id is your chat id; for a group it's negative, and the bot needs to be a member.
curl "https://api.telegram.org/bot<your-bot-token>/getUpdates" curl "https://api.telegram.org/bot<your-bot-token>/getUpdates"
Tell Postbag about it
Create a Telegram Destination with the token and chat id. Postbag holds the token, formats each submission into a short readable note — not a wall of JSON on a phone screen — and owns the sending from here on.
form_id="fm_YOUR_FORM_ID" # Copy yours from: npx postbag forms list
destination_json="$(curl --fail --silent --show-error -X POST https://postbag.dev/v1/destinations \
-H "Authorization: Bearer pb_live_…" \
-H "content-type: application/json" \
-d '{
"type": "telegram",
"name": "My phone",
"config": { "bot_token": "<your-bot-token>", "chat_id": "<your-chat-id>" }
}')"
destination_id="$(printf '%s\n' "$destination_json" | jq -er '.id')"
printf 'Destination ID: %s\n' "$destination_id" form_id="fm_YOUR_FORM_ID" # Copy yours from: npx postbag forms list
destination_json="$(curl --fail --silent --show-error -X POST https://postbag.dev/v1/destinations \
-H "Authorization: Bearer pb_live_…" \
-H "content-type: application/json" \
-d '{
"type": "telegram",
"name": "My phone",
"config": { "bot_token": "<your-bot-token>", "chat_id": "<your-chat-id>" }
}')"
destination_id="$(printf '%s\n' "$destination_json" | jq -er '.id')"
printf 'Destination ID: %s\n' "$destination_id" Prove it before it matters
Every Destination can be tested before a real message depends on it. This sends a test note through the actual bot to the actual chat — your phone should buzz within a breath or two.
curl --fail --silent --show-error -X POST https://postbag.dev/v1/destinations/"$destination_id"/test \
-H "Authorization: Bearer pb_live_…" curl --fail --silent --show-error -X POST https://postbag.dev/v1/destinations/"$destination_id"/test \ -H "Authorization: Bearer pb_live_…"
Route your Form to it
One Route ties the Form to the chat. From now on each new submission is stored first, then delivered to Telegram — and if Telegram has a moment, the message waits safely while Postbag retries. Prefer calm to buzzing? Switch the Route to a daily digest and get one tidy note instead.
route_body="$(jq -n --arg form_id "$form_id" --arg destination_id "$destination_id" \
'{ form_id: $form_id, destination_id: $destination_id }')"
curl --fail --silent --show-error -X POST https://postbag.dev/v1/routes \
-H "Authorization: Bearer pb_live_…" \
-H "content-type: application/json" \
-d "$route_body" route_body="$(jq -n --arg form_id "$form_id" --arg destination_id "$destination_id" \
'{ form_id: $form_id, destination_id: $destination_id }')"
curl --fail --silent --show-error -X POST https://postbag.dev/v1/routes \
-H "Authorization: Bearer pb_live_…" \
-H "content-type: application/json" \
-d "$route_body"