DEV Community

Cover image for I Built a Free Screenshot Bot for Telegram — Here's How It Works
Adrian Vega
Adrian Vega

Posted on • Edited on

I Built a Free Screenshot Bot for Telegram — Here's How It Works

The Problem

As a developer, I constantly need to:

  • Capture screenshots of websites for documentation
  • Generate PDFs from web pages for reports
  • Create Open Graph images for blog posts and social media

There are paid services for this (ScreenshotOne, ApiFlash, etc.), but I wanted something free and accessible from my phone without needing a browser or terminal.

The Solution: @SnapForgeBot

I built a Telegram bot that does all three things. Just send it a URL and it captures a screenshot instantly.

Try it: t.me/SnapForgeBot

Features

  • Auto-screenshot — Send any URL, get a screenshot
  • PDF generation/pdf example.com
  • OG image creation/ogimage My Blog Post | A subtitle here
  • Dark mode/screenshot example.com dark
  • Full-page capture/screenshot example.com full
  • Inline mode — Type @SnapForgeBot example.com in any chat

Free Tier

Every user gets 10 free requests per day. Invite friends with /invite to get +3 bonus requests per friend. Need more? Purchase request packs with Telegram Stars.

Technical Architecture

Here's what's under the hood:

Telegram ──> grammY Bot ──> Express API ──> Puppeteer (Chromium)
                                   │
                              Browser Pool
                          (2 persistent instances)
Enter fullscreen mode Exit fullscreen mode

The Stack

  • Runtime: Node.js 22
  • Bot Framework: grammY — fast, TypeScript-first Telegram bot framework
  • Browser Engine: Puppeteer with Chromium
  • Web Server: Express.js
  • Deployment: Docker Compose on a VPS

Browser Pool

The most interesting engineering challenge was managing Chromium instances efficiently. Instead of launching a new browser for each request (slow + memory-hungry), I maintain a pool of persistent browser instances:

class BrowserPool {
  constructor(size = 2) {
    this.browsers = [];
    this.size = size;
    this.current = 0;
  }

  async getPage() {
    const browser = this.browsers[this.current];
    this.current = (this.current + 1) % this.size;
    return browser.newPage();
  }
}
Enter fullscreen mode Exit fullscreen mode

Each request gets a fresh page (not browser), which is much faster to create and destroy. The pool rotates through browsers round-robin style.

Handling Binary Responses

One gotcha I hit: Puppeteer's page.screenshot() returns a Uint8Array in Node.js 22, not a Buffer. If you send it through Express with res.send(), it gets serialized as JSON ({"0":137,"1":80,...}) instead of binary PNG:

// Wrong — sends JSON
const screenshot = await page.screenshot();
res.send(screenshot);

// Correct — sends binary PNG
const screenshot = await page.screenshot();
const buffer = Buffer.from(screenshot);
res.set('Content-Type', 'image/png');
res.end(buffer);
Enter fullscreen mode Exit fullscreen mode

Telegram Stars Payments

Monetization uses Telegram's built-in Stars payment system. No Stripe, no payment forms — users pay directly in the Telegram UI:

await ctx.replyWithInvoice(
  'Extra 50 Requests',
  'Get 50 additional screenshot/PDF requests',
  'pack_50',
  '',          // provider_token empty for Stars
  'XTR',       // Telegram Stars currency
  [{ label: '50 Requests', amount: 25 }]
);
Enter fullscreen mode Exit fullscreen mode

The pre_checkout_query must be answered within 10 seconds or the payment fails.

Referral System

Viral growth is built-in. Each user gets a unique referral link:

https://t.me/SnapForgeBot?start=ref_123456
Enter fullscreen mode Exit fullscreen mode

When someone joins through this link, both users get +3 extra requests per day. It's a win-win that encourages organic sharing.

REST API

The same engine powers a REST API for developers:

# Screenshot
curl -X POST http://51.75.255.155/v1/screenshot \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sf_demo_public" \
  -d '{"url": "https://example.com", "width": 1280, "height": 800}' \
  -o screenshot.png

# PDF
curl -X POST http://51.75.255.155/v1/pdf \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sf_demo_public" \
  -d '{"url": "https://example.com"}' \
  -o document.pdf
Enter fullscreen mode Exit fullscreen mode

Demo key: sf_demo_public (50 requests/day, no signup required)

GET requests also supported:

http://51.75.255.155/v1/screenshot?api_key=sf_demo_public&url=https://example.com
Enter fullscreen mode Exit fullscreen mode

Deployment

Everything runs in Docker Compose:

services:
  snapforge-api:
    build: ./screenshot-api
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '2'
    healthcheck:
      test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3100/health')...\""]

  snapforge-bot:
    build: ./telegram-bot
    depends_on:
      snapforge-api:
        condition: service_healthy
Enter fullscreen mode Exit fullscreen mode

The API container is limited to 2GB RAM (Chromium is hungry) and the bot waits for the API to be healthy before starting.

What's Next

  • Custom domain with SSL
  • More OG image templates
  • HTML-to-image endpoint
  • RapidAPI marketplace listing
  • Open-sourcing the screenshot API

Try It


I'd love to hear feedback! What features would you add? Drop a comment below or reach out via the bot.

Top comments (0)