The Setup
I wanted to sell digital products without:
- Building a website
- Setting up payment processing
- Paying monthly SaaS fees
So I built a Telegram bot. Here's the entire architecture.
Architecture Overview
User opens bot
→ /start command
→ Main menu (inline keyboard)
→ Browse categories
→ Select product
→ View details + price
→ Click "Buy"
→ Telegram Stars payment
→ pre_checkout_query → approve
→ successful_payment → deliver PDF
→ Save to SQLite
The Product Catalog
I store everything in a Python dictionary:
PRODUCTS = {
'swiftui_starter': {
'name': 'SwiftUI Starter Kit Pro',
'description': 'MVVM templates, networking, Core Data...',
'stars': 75,
'category': 'swiftui',
'file': 'products/swiftui_starter.pdf'
},
# ... 25 more products
}
No database needed for the catalog. Products rarely change, so a dict works fine.
Navigation System
Telegram inline keyboards make great UIs:
def category_keyboard(category):
keyboard = InlineKeyboardMarkup(row_width=1)
for pid, product in PRODUCTS.items():
if product['category'] == category:
keyboard.add(InlineKeyboardButton(
f"{product['name']} — {product['stars']}⭐",
callback_data=f"product_{pid}"
))
keyboard.add(InlineKeyboardButton(
"← Back", callback_data="main_menu"
))
return keyboard
Payment Flow
The payment code is surprisingly simple:
@bot.pre_checkout_query_handler(func=lambda q: True)
def pre_checkout(query):
bot.answer_pre_checkout_query(query.id, ok=True)
@bot.message_handler(content_types=['successful_payment'])
def on_payment(message):
product_id = message.successful_payment.invoice_payload
product = PRODUCTS[product_id]
# Send the PDF
with open(product['file'], 'rb') as f:
bot.send_document(message.chat.id, f)
# Save to database
save_purchase(message.from_user.id, product_id)
bot.send_message(
message.chat.id,
f"Thank you! Enjoy {product['name']}!"
)
Database
SQLite stores users and purchases:
import sqlite3
def init_db():
conn = sqlite3.connect('shop.db')
conn.execute('''CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY,
user_id INTEGER,
product_id TEXT,
amount INTEGER,
purchased_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
conn.commit()
conn.close()
What I'm Selling
6 categories, 26 products:
- SwiftUI Templates — production-ready code
- Notion Systems — productivity dashboards
- Career Guides — resume, interview, LinkedIn
- AI Toolkits — prompts and workflows
- Business Tools — freelancer and startup kits
- Bundles — discounted collections
Costs
| Item | Cost |
|---|---|
| VPS (optional) | $5/month |
| Domain | $0 |
| Payment processing | $0 |
| Platform fees | $0 |
| Total | $5/month |
Lessons Learned
- Keep it simple — a dict beats a CMS
- Instant delivery wins — no email, no download links
- Low prices convert — 25 stars is an impulse buy
- Telegram UX is good enough — inline keyboards work great
Try It
Bot: @SwiftUIDailyBot on Telegram
Channel: t.me/SwiftUIDaily
The full code is under 200 lines of Python. If you can write a basic bot, you can build a store.
Questions? Drop them in the comments. Happy to share more details about the implementation.
Top comments (1)
Love the simplicity here. I sell digital products too (Claude Skills on Gumroad) and the "dict beats a CMS" philosophy is spot on. My product catalog is literally a markdown file that feeds into a pipeline — no database, no admin panel.
The Telegram Stars payment flow is interesting. I hadn't considered Telegram as a storefront — zero platform fees is hard to beat. With Gumroad taking 10% per sale, that adds up fast when you're doing volume.
Curious about discovery though. With Gumroad I at least get some marketplace traffic (small, but nonzero). How are people finding your bot? Is the Telegram channel doing most of the heavy lifting, or are you driving traffic from somewhere else?
The $5/month total cost is the dream. Keep shipping.