Daily AI content generation, Telegram approval, and social media posting
Every morning the Content Engine wakes up and checks the content calendar and recent post history to avoid repetition. It then decides which content type to generate for today: procedure spotlight, education, seasonal promo, or brand awareness.
src/consolidator_content.js → runDailyContentPipeline() (line 11)src/consolidator_scheduler.js0 12 * * * (12:00 UTC = 7 AM EST). On each tick it calls runDailyContentPipeline(), which queries content_calendar for scheduled posts and content_submissions for recent approvals. It selects the highest-priority content type not posted in the last 48 hours and hands the decision to the generation step.content_calendar, content_pipelines, content_submissionsKaren AI writes a complete social post: caption, hashtags, and an image suggestion. Content is brand-aware — professional yet approachable, rooted in Jamaican context, and positioned for a luxury med spa audience.
src/consolidator_content.js → presentDailyContent() (line 132)presentDailyContent() builds a content-specific prompt that includes Oshun's brand voice guidelines, the selected content type, and a summary of recent posts to avoid duplication. The AI pipeline returns a structured response containing the caption text, hashtag set, and a plain-language image concept. The draft is immediately written to content_submissions with status draft.content_submissions (status: draft)The generated content is sent to the Oshun Telegram channel as a preview. The message shows the full caption, hashtags, and image concept alongside Approve and Reject inline buttons for one-tap review.
src/consolidator_content.js → presentDailyContent() sends via botsrc/telegram_callbacks_content.js → handles button callbackspresentDailyContent() formats the draft into a Telegram message and attaches an inline keyboard with content_approve and content_reject callback data. The submission ID is encoded in the callback so the handler knows which draft is being acted on. The submission status is updated to pending_approval at this point.content_submissions (status: pending_approval)Matthew or Dr. Rockhead taps Approve to send the post live, or Reject to regenerate or skip. No post goes out without human sign-off — the system never publishes autonomously.
src/telegram_callbacks_content.js → content_approve / content_reject callbackssrc/consolidator_content.js → autoApproveContent() (line 185)content_approve, the callback calls autoApproveContent(), which updates the submission to approved and queues it for publishing. On content_reject, the submission is marked rejected, rejection feedback is written to content_feedback, and the pipeline can optionally regenerate with a revised brief. The Telegram message is edited to confirm the action taken.content_submissions, content_feedbackApproved content is handed to Blotato, which publishes to Instagram, Facebook, and YouTube. Each platform receives a correctly formatted version of the post — Blotato handles the per-platform differences automatically.
src/actions/blotato/publish.jssrc/actions/blotato/config_accounts.jssrc/actions/blotato/media.jspublish.js reads the approved submission, calls config_accounts.js to resolve the tenant's connected platform accounts, then prepares media via media.js if an image is attached. The Blotato API receives the post payload for each target platform in sequence. Each successful publish response is recorded in content_distributions with the platform, post ID, and timestamp.BLOTATO_API_KEYcontent_distributionsAfter publishing, the system tracks likes, comments, shares, and saves. A scoring model evaluates each post's performance relative to baseline. High performers are weighted more heavily when the AI selects future content types and briefs.
src/actions/analytics/scoring.jssrc/actions/analytics/growth.jssrc/actions/feedback_loop/scoring.jsanalytics/scoring.js computes a normalised engagement score against the tenant's rolling average. analytics/growth.js logs follower deltas to follower_snapshots. feedback_loop/scoring.js writes the final score back to content_feedback, where it influences the content type weighting used in Step 1 of the next day's pipeline.content_feedback, marketing_analytics, follower_snapshots