DEV Community

Subhendu Das
Subhendu Das

Posted on

Marketing Automation via AI Content Calendar | DoAide Pulse

Problem

Indie developers and small teams often ship code but forget to keep a steady stream of content that explains, promotes, and drives traffic to their projects. Manually drafting blog posts, social‑copy, and scheduling them across platforms is a repetitive task that consumes time better spent on code.

Feature Overview

DoAide Pulse’s AI‑powered content calendar solves this by automatically watching a project’s GitHub repository, generating draft articles and social snippets, and scheduling their publication to destinations like Dev.to and Bluesky. The calendar is fully integrated into the Pulse UI, giving teams a single view of what will be posted, when, and in which language.

Technical Architecture

The feature is built on a stack that keeps the system responsive and fault‑tolerant:

  • FastAPI exposes a webhook endpoint that receives GitHub push events. The endpoint validates the payload, stores a minimal record in PostgreSQL (via SQLAlchemy), and triggers a Celery task.
  • Celery workers, backed by Redis, perform CPU‑heavy AI generation and schedule the posts. A beat scheduler ensures that the content calendar is refreshed every hour.
  • The React frontend (bundled with Vite and styled with Tailwind CSS) renders a calendar view that pulls schedule data from a FastAPI endpoint.

Data Flow

  1. Repo Watcher – GitHub sends a push event to /webhooks/github. FastAPI validates the signature, extracts the commit list, and persists a repo_update record.
  2. Draft Generation – A Celery task generate_draft loads the latest commit messages, feeds them to an AI model (via a generic ai_client.generate call), and stores the resulting markdown and social copy in a drafts table.
  3. Scheduling – The task calculates a publish window based on project settings and inserts a content_schedule row. The schedule includes target platform, language, and a status flag.
  4. Publishing – At the scheduled time, another Celery task publish_content reads the row, calls the Dev.to API (https://dev.to/api/articles) or the Bluesky endpoint, and updates the status.

Implementation Details

# fastapi_app.py
from fastapi import FastAPI, Request, Header, HTTPException
from ai_client import generate
from models import RepoUpdate, Draft, Schedule
from database import session
from celery_app import celery

app = FastAPI()

@app.post("/webhooks/github")
async def github_webhook(req: Request, x_hub_signature: str = Header(...)):
    payload = await req.json()
    if not validate_signature(payload, x_hub_signature):
        raise HTTPException(status_code=401)
    record = RepoUpdate(repo_id=payload["repository"]["id"], commit_sha=payload["after"])
    session.add(record)
    session.commit()
    celery.send_task("tasks.generate_draft", args=[record.id])
    return {"status": "queued"}

# tasks.py
@celery.task(name="tasks.generate_draft")
def generate_draft(update_id):
    update = session.get(RepoUpdate, update_id)
    commits = get_commits(update.repo_id, update.commit_sha)
    draft_text = generate("blog", commits)
    draft = Draft(update_id=update_id, content=draft_text)
    session.add(draft)
    session.commit()
    schedule_id = schedule_publishing(draft.id)
    return schedule_id

@celery.task(name="tasks.publish_content")
def publish_content(schedule_id):
    schedule = session.get(Schedule, schedule_id)
    try:
        if schedule.platform == "devto":
            resp = requests.post(
                "https://dev.to/api/articles",
                json={"article": {"body_markdown": schedule.content, "tags": schedule.tags}},
                headers={"api-key": DEVTO_KEY}
            )
        else:
            # Bluesky or other platform
            pass
        schedule.status = "published"
    except Exception as exc:
        schedule.retry_count += 1
        if schedule.retry_count > 3:
            schedule.status = "failed"
    session.commit()
Enter fullscreen mode Exit fullscreen mode

The snippet demonstrates how FastAPI, Celery, and SQLAlchemy work together to move a commit through drafting, scheduling, and publishing.

Reliability Enhancements

Recent commits reinforce the robustness of this workflow:

  • M5 fixes the retry logic for SEO jobs and the database connection checker, ensuring that a dropped PostgreSQL link does not halt scheduling.
  • M2 in the publish tests guarantees that a single publish action can target two destinations in two languages, each receiving the correct localized text.
  • M11 logs non‑count metrics, allowing operators to monitor dropped events without a 32‑bit overflow.
  • M12 prevents a partial deploy that could delete uploads or leave the code base in an inconsistent state. These changes mean that the content calendar can operate continuously, even under intermittent network or database failures.

User Experience

The React component ContentCalendar renders a monthly grid. Each cell shows a preview of the article title, status (queued, drafted, published, failed), and a small icon indicating the target platform. Clicking a cell opens a modal that displays the full draft and allows the user to edit the text or reschedule. The UI fetches schedule data from /api/schedule every minute, so the view is always up to date.

The calendar also exposes a “Publish Now” button that bypasses the scheduler and triggers publish_content immediately. This is useful for last‑minute announcements. This is where marketing automation pays off in practice. Getting marketing automation right early saves rework later. Good marketing automation keeps the rest of the workflow predictable.

Conclusion

By automating the drafting and scheduling of content, DoAide Pulse’s AI‑powered content calendar lets indie developers focus on shipping code while maintaining a consistent marketing cadence. The stack—FastAPI, Celery, PostgreSQL, Redis, React, Vite, and Tailwind CSS—provides a solid foundation for reliable, developer‑friendly marketing automation.

Further Reading

Related reading

Top comments (0)