Skip to main content

Post from Telegram to WordPress Bot

Publishing quick updates, micro-blog posts, or photo logs to a WordPress website from a mobile device often involves navigating a heavy web admin dashboard. To eliminate mobile blogging friction, I built post-from-telegram-to-wordpress-an automated Python server tool that receives messages from a Telegram channel or chat and publishes them directly to WordPress via the official REST API.

This article details the system architecture, media pipeline, WordPress REST API authentication, and deployment strategies for building a reliable Telegram-to-WordPress publishing bridge.

Post from Telegram to WordPress


Architectural Workflow and System Design

The application operates as an event-driven daemon running on a cloud server or container. When an authorized user posts text, photos, or formatted content into a dedicated Telegram channel or chat, the bridge captures the event, transforms the payload, handles media uploads, and invokes WordPress endpoints.

[ Telegram Channel / Chat ]

│ (Telegram Bot API / Event Listener)

[ Python Listener Service ]

├── 1. Extract Text & Parse HTML/Markdown
├── 2. Download Telegram Media Attachments
├── 3. Upload Media to WordPress (/wp/v2/media)

▼ (WordPress Application Password Auth)
[ WordPress REST API (/wp/v2/posts) ]


[ Live WordPress Blog Post ]

Core System Components

1. WordPress Authentication Strategy

Rather than storing administrative session cookies or global passwords, I configured authentication using WordPress Application Passwords. Introduced natively in WordPress 5.6+, Application Passwords generate unique, revokable tokens specifically designed for REST API integrations.

Header formulation:

import base64

def get_wp_auth_header(username: str, app_password: str) -> dict:
credentials = f"{username}:{app_password}"
token = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
return {"Authorization": f"Basic {token}"}

2. Telegram Event Listener

The bot registers handlers to process incoming text and photo updates. To ensure only authorized users or channel administrators can publish to the blog, incoming message events are filtered against designated Telegram chat_id allowlists.

from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, MessageHandler, filters

ALLOWED_CHAT_IDS = {-100123456789} # Target Telegram Channel / Admin ID

async def handle_incoming_post(update: Update, context: ContextTypes.DEFAULT_TYPE):
message = update.effective_message
if message.chat.id not in ALLOWED_CHAT_IDS:
return

text_content = message.text or message.caption or ""
photo_attachments = message.photo

# Process and push to WordPress
await process_and_publish(text_content, photo_attachments, context.bot)

Media Upload Pipeline (Handling Telegram Photos)

Publishing posts with attached images requires a multi-step media pipeline:

  1. Telegram File Retrieval: Fetch the highest-resolution photo object from Telegram servers.
  2. WordPress Media Library Upload: Send a POST request with image bytes to /wp/v2/media with proper Content-Type headers.
  3. Featured Image Association: Pass the generated WordPress media ID (featured_media) in the main post creation payload.
import requests

def upload_photo_to_wordpress(wp_site_url: str, auth_headers: dict, photo_bytes: bytes, filename: str) -> int:
media_url = f"{wp_site_url}/wp-json/wp/v2/media"
headers = {
**auth_headers,
"Content-Disposition": f'attachment; filename="{filename}"',
"Content-Type": "image/jpeg",
}

response = requests.post(media_url, headers=headers, data=photo_bytes)
response.raise_for_status()

# Return created media ID
return response.json()["id"]

Post Creation and HTML Publishing

Once media processing completes, the service constructs the WordPress post payload. Telegram HTML formatting tags (<b>, <i>, <a>, <code>) map directly into WordPress content blocks.

def create_wordpress_post(wp_site_url: str, auth_headers: dict, title: str, html_content: str, media_id: int | None = None) -> dict:
posts_url = f"{wp_site_url}/wp-json/wp/v2/posts"
payload = {
"title": title,
"content": html_content,
"status": "publish", # 'draft' or 'publish'
}

if media_id:
payload["featured_media"] = media_id

response = requests.post(posts_url, headers=auth_headers, json=payload)
response.raise_for_status()
return response.json()

Deployment with Docker

To ensure uninterrupted 24/7 background execution, I package the bridge as a lightweight container managed via Docker or docker-compose.

Dockerfile

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "main.py"]

Environment Configuration (.env)

TELEGRAM_BOT_TOKEN=123456789:XXXXXXXXXX
ALLOWED_CHAT_ID=-100123456789
WP_SITE_URL=https://myblog.com
WP_USER=admin
WP_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx

Production Recommendations

  1. Webhook vs Long-Polling: For high-frequency Telegram channels, switch from long-polling (run_polling()) to Webhook mode behind an Nginx reverse proxy.
  2. Post Status Controls: Set "status": "draft" in configuration if you prefer reviewing posts inside the WordPress editor prior to public indexing.
  3. Security Boundaries: Scope WordPress Application Password capabilities and restrict IP access if running on static cloud infrastructure.