The Problem
Salon networks running multiple directory pages need a constant stream of social media content -- before/after photos, service spotlights, seasonal promotions, customer testimonials, and booking reminders. Managing this manually across Facebook, Instagram, and TikTok takes 10-15 hours per week per salon. Most salon owners simply give up after a month, leaving their social profiles dormant with the last post from six months ago. Dormant social profiles actively harm local SEO and reduce booking inquiries by up to 30%.
The Solution
A serverless content calendar engine that generates, schedules, and publishes social media posts automatically. AiSalonHub's Marketing Engine runs on Cloudflare Workers cron triggers with D1 storing the content queue, KV managing scheduling locks, and a simple webhook-based publishing layer that connects to each social platform's API. The system is fully autonomous -- salon owners configure it once and get daily social media posts without lifting a finger.
Architecture Overview
The system has four layers:
- **Content Generator (LLM-powered):** A Worker endpoint that takes a salon's profile (services, location, upcoming promotions) and generates 7 days worth of post drafts -- one morning post, one evening post. Uses OpenRouter API for LLM calls with caching in KV to avoid re-generation if content hasn't changed. Each post includes platform-specific formatting (hashtags for Instagram, shorter copy for TikTok, link preview for Facebook).
- **Scheduling Queue (D1):** Each generated post is stored in a D1 table with scheduled_at timestamp, salon_id, platform_target (Instagram, Facebook, TikTok), and status (pending/published/failed). D1's SQL capabilities enable complex queries: 'find all posts due in the next hour where status=pending' runs in under 5ms even with 10,000 posts in the table. The queue is timezone-aware, using each salon's configured timezone offset.
- **Publisher Cron (Workers):** A cron job running every 15 minutes queries D1 for pending posts where scheduled_at <= now. It picks up to 10 posts per run, publishes them via platform API webhooks, and marks them as published in D1. KV stores a processing lock to prevent double-publishing during concurrency. Failed posts get up to 3 retries with exponential backoff.
- **Analytics Collector (D1 Aggregation):** After each post publishes, the system tracks engagement (likes, shares, comments) via periodic webhook pull and stores it in D1 with per-post granularity. This feeds back into the Content Generator to bias future posts toward formats and topics that historically perform better. The analytics also power a salon dashboard showing content performance trends.
Implementation Details
Content Generation Prompt Structure
The LLM prompt combines four data sources:
- 1. Salon profile: "{salon_name} offers {services} in {city}. Target audience: {demographic}."
- 2. Seasonal context: "Current month: {month}. Upcoming holidays: {holidays}. Local events: {events}."
- 3. Historical performance: "Top-performing post types last week: {top_formats}. Worst-performing: {bottom_formats}."
- 4. Active campaigns: "Active promotion: {campaign_name}. Discount code: {code}."
Publishing Flow
1. Cron fires at HH:00, HH:15, HH:30, HH:45 (configurable per salon)
2. Acquire KV lock: `IF NOT EXISTS scheduler_lock SET TTL 600` -- prevents concurrent Workers from double-publishing
3. Query D1: `SELECT * FROM social_posts WHERE status='pending' AND scheduled_at <= datetime('now') AND salon_id = ? ORDER BY scheduled_at LIMIT 10`
4. For each post: call the appropriate platform API (Facebook Graph API, Instagram Content Publishing API, TikTok Business API)
5. On success: `UPDATE social_posts SET status='published', published_at=datetime('now'), platform_post_id=? WHERE id=?`
6. On failure (network error, auth error): `UPDATE social_posts SET status='failed', retry_count=retry_count+1, last_error=? WHERE id=?` -- retry up to 3 times at 15-minute intervals
7. Release KV lock and log metrics to D1 analytics table
Seasonal Campaign Integration
The Content Generator checks D1 for active seasonal campaigns on each generation run. If a salon has a Halloween promotion configured (via the seasonal campaign scheduler we described in a previous post), the posts for that week auto-include Halloween-themed copy and imagery references. The campaign scheduler creates an entry in the seasonal_campaigns D1 table, and the content generator queries this table during prompt building. This integration means a Valentine's Day campaign automatically generates 14 romantic-themed posts without any manual intervention.
Results
After 8 weeks of automated social media posting across 15 AiSalonHub salon directories:
- **Average 11 posts/week** per salon (up from 2 posts/week manually)
- **3.4x increase** in social media engagement (likes + comments + shares)
- **18% increase** in 'booking via social media' attribution (tracked via UTM parameters)
- **Zero manual effort** from salon owners after initial setup
- **$0 infrastructure cost** (entirely within Cloudflare Workers free tier for 15 salons)
- **100% publishing success rate** after the first week (initial auth token setup was the only failure mode)
Key Takeaways
- The LLM-generated content approach means each post is unique to the salon -- no generic templates that readers ignore, no duplicate content penalties
- D1 as a scheduling queue is surprisingly effective: Workers + D1 replace what would normally require a dedicated message queue (SQS, RabbitMQ) at zero additional cost
- The feedback loop (engagement data -> better prompts -> better content) creates compounding improvement over time -- posts in week 8 averaged 40% more engagement than week 1
- Cron-based publishing at 15-minute intervals avoids platform rate limits while ensuring timely delivery
- The seasonal campaign integration turns one-time promotions into weeks of automatically themed content