Measuring Scheduled Telegram→X Posts: Delivery Receipts and Dead-Letter Triage (2026)
Your Telegram bot can say a job was accepted and still leave you unsure whether X actually published the post. This guide is about the gap between those two moments: how to record a delivery receipt, how to verify the tweet later, and how to triage dead-letter jobs without guessing. It complements our failed-publish retry guide and approval-gates checklist — those cover retry mechanics and human review; this page covers observability after send. Educational ops notes for bot owners, not a promise about any network’s uptime.
Auto X in one line: a Telegram bot that helps you schedule and publish posts to X with human-friendly controls. Useful features include scheduling from chat, approval-style gates before publish, and retry-friendly job handling. Honest comparison: native X scheduling is simpler if you only post from the web; a custom X API app gives more control if you want to own every retry rule; Auto X sits in between when your drafting workflow already lives in Telegram and you want receipts without building a console from scratch.
Bot ack is not the same as X publish
Telegram’s Bot API tells you whether your bot handled an update. X’s manage Tweets endpoint tells you whether a tweet was created. Mixing those signals is how silent failures hide for days. Treat them as two ledgers that share one job id:
- Ingress ledger: Telegram update id, schedule tick, or approval decision that started the job, plus who approved it if you use gates.
- Egress ledger: HTTP status from X, tweet id on success, error code on failure, attempt number, media upload ids, and final state (complete, retrying, dead-letter).
Mark a job “complete” only when egress shows a created tweet id (or a deliberate human cancel). An ack to the human in Telegram can say “queued” or “submitted,” but the durable status for ops should wait for X. If you tell someone “posted” based only on the bot ack, you will eventually be wrong on a bad hour for the API.
What to store on every publish attempt
Minimum fields that make later triage possible without opening raw logs:
- client request id — same idea as our idempotency guide: one key per logical post so retries do not double-post.
- HTTP status and a short error class — enough to classify rate limits versus auth versus content rejection without storing secrets.
- tweet id when creation succeeds — this is your receipt.
- attempt n of max — so dead-letter is a rule, not a feeling.
- media upload ids if the job had images — media failures need a different path than text failures (see media upload size limits).
Without a tweet id, “it posted” is a guess. With a tweet id, you can prove it to yourself next week when someone asks. Keep the payload hash or fingerprint if you also guard duplicates; that ties into our duplicate-content fingerprint notes.
Verify with lookup, not hope
X documents tweet lookup under the Tweets lookup API (for example GET /2/tweets/:id). A practical pattern that stays cheap:
- On create success, store the tweet id immediately in the egress ledger.
- Optionally re-fetch that id a short time later for jobs you care about (launches, threads, partner posts).
- If lookup says the tweet is missing or unauthorized, escalate — do not keep retrying the create blindly with a new client request id.
Re-fetch is for confirmation and audits. It is not a substitute for recording the create response. Also keep rate-limit budgets in mind; our rate-limit budget notes explain why blind polling burns credits you wanted for publishing.
Dead-letter triage that stays fast
When attempts hit the cap, move the job to a dead-letter list humans can see in Telegram or a tiny admin view. Split causes before anyone rewrites the post:
- Retry-safe: 429, 5xx, timeouts — back off, then retry with the same client request id.
- Stop-and-alert: 401/403 (token or app issue — see OAuth refresh rotation), duplicate-content fingerprints, rejected media, invalid payload shapes.
Humans should not re-approve every transient blip. They should approve once (if you use gates), then only revisit stop-and-alert items. That keeps the loop fast without hiding real failures. Pair this page with the retry guide rather than copying its backoff tables here.
A small weekly ops habit
Once a week, skim four counts: jobs still retrying, dead-letter open items, jobs with create success but missing tweet id (should be zero), and auth errors clustered after a token rotate. Fifteen minutes is enough if logging is structured. If you also edit or delete posts from Telegram, keep edit/delete sync on a separate receipt path — different API calls, different success criteria. Thread splitting and quiet-hours scheduling are upstream of receipts; measure publish after those policies have already decided when to send.
When something looks wrong, start from the job id, not from “the channel felt quiet.” The job id should open ingress, egress, and the human approval row in one glance.
Building a receipt view humans will actually use
Logs only help if someone opens them. For a Telegram-first workflow, surface a compact receipt card on demand: job id, final state, tweet id (as a t.co or x.com link when present), last error class, and attempt count. Keep secrets out of the card. Operators should be able to paste a job id into chat and get that card back without SSHing anywhere.
Separate “user-visible status” from “ops-visible status.” Users may see Scheduled → Publishing → Published. Ops need Queued → Attempting → Succeeded/Dead-letter with the underlying codes. When a post is part of a thread, attach the parent tweet id so reply-chain failures are obvious without re-reading the whole pipeline. Quiet-hours delays should show as a scheduled state with the next eligible send time, not as a mysterious pending forever.
Finally, define retention: keep successful receipts long enough for monthly audits, keep dead-letter payloads until a human resolves or discards them, and scrub raw OAuth tokens from any export. Observability that leaks credentials is worse than no observability.
Connecting receipts to cadence and credits
Delivery receipts also protect your posting cadence. If quiet-hours logic delays a send, the receipt should show the delay reason so you do not “fix” a healthy queue. If rate-limit budgets are nearly exhausted, prefer finishing in-flight jobs with clear receipts over accepting new bursts that will dead-letter. Credits spent on confused retries are credits you cannot spend on real posts. A weekly roll-up of succeeded vs dead-lettered jobs is enough for most indie operators; enterprises may want dashboards, but the underlying fields stay the same.
FAQ
Is a Telegram “sent” message enough proof?
No. It only proves your bot handled the chat side. Proof of publish is an X create response with a tweet id, ideally confirmed later with lookup when the post matters.
Should every job re-fetch the tweet?
Not necessarily. Store the id on create for every job; re-fetch when the post matters or when something looks wrong.
Where do approval gates fit?
Before create. Receipts and dead-letter triage come after. Link the same job id across both so humans see one story.
What if create succeeds twice?
That is an idempotency bug. Use a stable client request id and treat unexpected duplicate creates as a stop-and-alert event.
Sources
- X API: manage Tweets (checked 2026-10-12)
- X API: Tweets lookup (checked 2026-10-12)
- Telegram Bot API: making requests (checked 2026-10-12)
More guides on the Auto X blog.