Implementing Automatic Posting of Promotions and Discounts to Social Media
Promotion post must go out exactly when sale starts — not two hours later when manager remembers, and not yesterday when sale hasn't started yet. System itself monitors discount schedule and publishes announcements with correct formatting, countdown, and product links.
What Distinguishes Promotion Posts from Product Posts
Promotion post — it's not just product card. It contains:
- Validity period — "only until Friday", "3 days left"
- Discount size — in percentage or absolute value
- List of products or categories under promotion
- CTA — link to promotion page or promo code
If promotion covers 20+ products, post built by promotion template, not product template.
Triggers and Schedule
Promotions table (promotions) stores fields:
CREATE TABLE promotions (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
discount_value NUMERIC(5,2),
discount_type ENUM('percent','fixed'),
starts_at TIMESTAMP NOT NULL,
ends_at TIMESTAMP,
social_post_at TIMESTAMP, -- when to publish
social_posted BOOLEAN DEFAULT FALSE,
notify_before INTERVAL DEFAULT '0', -- e.g. '1 hour'
channels JSONB DEFAULT '[]' -- ['vk','telegram']
);
Cron task runs every minute and selects promotions where social_post_at <= NOW() and social_posted = false:
SELECT * FROM promotions
WHERE social_post_at <= NOW()
AND social_posted = FALSE
AND starts_at <= NOW() + notify_before;
Found promotions queued, after successful posting social_posted set to true.
Media Generation
Promotion posts often need banners with price and discount badge — not just product photo. Two approaches:
Approach 1 — Pre-made banner. Manager uploads banner when creating promotion. Simple, predictable result.
Approach 2 — On-the-fly generation. Use Puppeteer or wkhtmltoimage: render HTML template with promotion data to PNG.
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
await page.setViewport({ width: 1080, height: 1080 });
await page.setContent(renderTemplate('promo-banner', { promotion }));
await page.screenshot({ path: `/tmp/promo-${promotion.id}.png`, type: 'png' });
await browser.close();
Generated banner cached in S3/MinIO with key promos/{id}/banner.png.
Post Text
Template accounts for promotion type:
🔥 DISCOUNT {discount}% on {category_name}!
{description}
⏰ Promotion valid until {ends_at_formatted}
📦 {products_count} products participate in promotion
View all products: {promo_url}
{hashtags}
For promotions with promo code, block added:
🎁 Promo Code: {promo_code}
Post Deletion / Archiving
Some platforms allow post deletion via API after promotion ends. In VKontakte — wall.delete, in Telegram — deleteMessage. Function optional, enabled by setting auto_delete_after_end = true on promotion.
Implementation Timeline
Basic system with fixed templates and two channels (VK, Telegram) — 5–7 business days. Banner generation via Puppeteer, post deletion after end, CMS management — additional 3–4 days.







