Feature Spotlight: AI‑Powered Content Drafting and Publishing

The Problem

Indie developers and small teams ship code faster than they write about it. Every commit that adds a feature, fixes a bug, or refactors a module could be a hook for a blog post or a tweet, but the effort to draft, approve, and schedule that content is a bottleneck. Without a dedicated marketer, teams waste hours on copywriting, formatting, and publishing, and often miss the window when a release is most relevant.

Architecture Overview

Herald solves this by combining

  • FastAPI for a lightweight, async API surface,
  • SQLAlchemy to persist repository metadata and draft drafts,
  • Celery with a Redis broker to run background jobs for content generation and publication,
  • React + Vite + Tailwind CSS for a responsive UI that shows the content calendar and draft status.

The core workflow is:

  1. A GitHub webhook pushes a push event.
  2. FastAPI receives the payload, stores it, and enqueues a Celery task.
  3. The task uses a LLM provider (OpenAI, Anthropic, etc.) to generate draft copy.
  4. The draft is stored in PostgreSQL and surfaced to the developer.
  5. The developer reviews and edits the draft in the browser.
  6. Once approved, another Celery task publishes to the selected platforms via their APIs (Dev.to, Medium, etc.).
  7. Analytics are collected to feed back into the content calendar.

Detailed Workflow

1. Webhook Receiver

# app/main.py from fastapi import FastAPI, Request from models import Repository, Commit from tasks import enqueue_content_generation app = FastAPI() @app.post("/webhook/github") async def github_webhook(req: Request): payload = await req.json() repo = await Repository.get_or_create(payload["repository"]["full_name"]) commit = Commit.create(repo_id=repo.id, sha=payload["after"], message=payload["head_commit"]["message"]) enqueue_content_generation.delay(commit.id) return {"status": "queued"} 

The endpoint validates the payload, persists the commit, and triggers a background job.

2. Content Generation Task

# app/tasks.py from celery import shared_task from llm import generate_draft from models import Commit, Draft @shared_task def enqueue_content_generation(commit_id): commit = Commit.get(id=commit_id) prompt = f"Write a concise blog intro for the commit: {commit.message}\n\n---\n" + commit.message content = generate_draft(prompt) Draft.create(commit_id=commit.id, body=content, status="draft") 

generate_draft calls the chosen LLM via a provider‑agnostic wrapper. The prompt is tuned to produce a short, engaging introduction that can be extended into a full post.

3. Frontend Draft Review

// src/components/DraftEditor.tsx import React, { useState, useEffect } from "react" import axios from "axios" export default function DraftEditor({ draftId }: { draftId: number }) { const [body, setBody] = useState<string>('') useEffect(() => { axios.get(`/api/drafts/${draftId}`).then(r => setBody(r.data.body)) }, [draftId]) const save = () => { axios.put(`/api/drafts/${draftId}`, { body }) } return ( <textarea className="w-full h-64 p-2 border rounded" value={body} onChange={e => setBody(e.target.value)} /> ) } 

The UI is built with React and styled using Tailwind CSS. A simple textarea lets the developer tweak the LLM output before publishing.

4. Publishing Task

# app/tasks.py @shared_task def publish_to_devto(draft_id): draft = Draft.get(id=draft_id) headers = {"api-key": os.getenv("DEVTO_API_KEY")} payload = { "article": { "title": draft.title, "body_markdown": draft.body, "published": True, "tags": ["devops", "automation"] } } resp = requests.post("https://dev.to/api/articles", json=payload, headers=headers) if resp.status_code == 201: draft.status = "published" draft.published_at = datetime.utcnow() draft.save() 

The task uses the Dev.to API to publish the article. Similar tasks exist for Medium and other platforms.

Performance & Reliability

  • Composite Index: A composite index on (commit_id, status) speeds up queries for the latest draft per commit.
  • Query Projection: The overview() endpoint now selects only the columns needed for the content calendar, reducing payload size.
  • Error Handling: Improved error messages now include depth and size limits, guiding developers to fix payloads before resubmission.
  • Retry Guard: retry_publication is protected against re‑running when a publication is already in the PUBLISHING state, preventing duplicate posts.

User Experience

On the dashboard, the content calendar shows each commit as a card with status badges: Draft, Approved, Publishing, Published. Clicking a card opens the DraftEditor component. After approval, the user clicks “Publish”, which triggers the Celery task. Once published, a link to the live article appears, and analytics counters update in real time.

Conclusion

Herald’s AI‑powered drafting feature turns every meaningful commit into a publishable piece without manual copywriting. By leveraging FastAPI, Celery, and the Dev.to API, it delivers a seamless marketing automation flow that fits naturally into a developer’s workflow.

This feature spotlight is based on the latest commit history of the Herald repository.