DEV Community

Subhendu Das
Subhendu Das

Posted on

Marketing Automation: The Content Calendar Feature

Feature Spotlight: Content Calendar

DoAide Pulse is built around the idea that developers should be able to ship code and let the platform handle promotion. The core of that promise is the content calendar – a declarative schedule that tells the system when and where to publish AI‑generated copy. The calendar solves a common problem: keeping a consistent publishing cadence across multiple channels without manual intervention. This is where marketing automation pays off in practice.

The Problem

Indie developers and small teams often produce a new feature, a bug fix, or a documentation update every few weeks. Translating that work into a marketing asset usually requires a separate workflow: writing a blog post, drafting social copy, and posting it to Dev.to, Bluesky, or a newsletter. This process is time‑consuming, error‑prone, and hard to keep in sync with the code repository. The content calendar centralises that workflow, allowing a single source of truth for all scheduled posts.

How It Works

The calendar is a combination of a database model, a FastAPI endpoint, a Celery worker, and a React UI. The system watches a GitHub repository via webhook events; each push triggers an AI model that drafts a blog post and social snippets. Those drafts are persisted in a ScheduledPost table and appear in the calendar UI.

Database Model (SQLAlchemy)

# models.py
from sqlalchemy import Column, Integer, String, Text, DateTime
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class ScheduledPost(Base):
    __tablename__ = "scheduled_posts"

    id = Column(Integer, primary_key=True)
    repo_id = Column(String, index=True)
    title = Column(String, nullable=False)
    content = Column(Text, nullable=False)
    publish_at = Column(DateTime, nullable=False)
    platform = Column(String, nullable=False)  # e.g., "devto", "bluesky"
    status = Column(String, default="queued")  # queued, published, failed
Enter fullscreen mode Exit fullscreen mode

The publish_at field is the heart of the calendar – it defines the exact moment the worker will attempt to publish. Getting marketing automation right early saves rework later.

FastAPI Endpoint

# api.py
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from datetime import datetime
from sqlalchemy.orm import Session

router = APIRouter()

class SchedulePostRequest(BaseModel):
    repo_id: str
    title: str
    content: str
    publish_at: datetime
    platform: str

@router.post("/schedule")
async def schedule_post(
    req: SchedulePostRequest,
    db: Session = Depends(get_db),
):
    post = ScheduledPost(**req.dict())
    db.add(post)
    db.commit()
    # Fire a Celery task to enqueue the publish job
    publish_post.delay(post.id)
    return {"id": post.id, "status": post.status}
Enter fullscreen mode Exit fullscreen mode

The endpoint accepts the draft and desired publish time, stores it, and immediately queues a Celery task.

Celery Worker

# tasks.py
from celery import Celery
from sqlalchemy.orm import Session
from models import ScheduledPost
from devto_api import publish as devto_publish

celery = Celery("pulse", broker="redis://localhost:6379/0")

@celery.task
def publish_post(post_id: int):
    db: Session = get_session()
    post = db.query(ScheduledPost).get(post_id)
    if not post:
        return

    try:
        if post.platform == "devto":
            devto_publish(post.title, post.content)
        # Additional platform logic can be added here
        post.status = "published"
    except Exception as exc:
        post.status = "failed"
    finally:
        db.commit()
Enter fullscreen mode Exit fullscreen mode

The worker pulls the scheduled post from the queue, calls the platform‑specific API, and updates the status.

Dev.to API Wrapper

# devto_api.py
import requests

API_URL = "https://dev.to/api/articles"
TOKEN = "<devto-token>"

headers = {"api-key": TOKEN}

def publish(title: str, body: str):
    payload = {
        "article": {
            "title": title,
            "published": True,
            "body_markdown": body,
        }
    }
    r = requests.post(API_URL, json=payload, headers=headers)
    r.raise_for_status()
Enter fullscreen mode Exit fullscreen mode

The wrapper abstracts the HTTP call and ensures a consistent interface for the worker. Good marketing automation keeps the rest of the workflow predictable.

React UI (Vite + Tailwind)

// ContentCalendar.tsx
import { useState } from "react";

export default function ContentCalendar() {
  const [publishAt, setPublishAt] = useState<string>('');

  return (
    <div className="p-4">
      <label className="block mb-2">Publish At</label>
      <input
        type="datetime-local"
        value={publishAt}
        onChange={e => setPublishAt(e.target.value)}
        className="border rounded p-2 w-full"
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The UI allows developers to pick a date‑time and see all queued posts in a grid. The component is lightweight and can be embedded in any dashboard.

What It Looks Like to Use

  1. Push to GitHub – A commit triggers a webhook that runs the LLM pipeline.
  2. Drafts Appear – The AI outputs a markdown article and social snippets, stored as ScheduledPost records.
  3. Schedule – In the dashboard, the developer selects a publish time and platform.
  4. Publish – At the scheduled time, the Celery worker posts to Dev.to via the wrapper.
  5. Audit Trail – The status field shows queued, published, or failed, allowing quick review.

The entire flow is visible from the UI: a calendar view shows upcoming posts, a table lists past publications, and a log pane displays any worker errors. Because the system uses Redis as a broker and PostgreSQL for persistence, scaling is straightforward – add more workers or spin up a read replica without touching the code.

Why It Matters for Developer Marketing

  • Consistency – A single schedule keeps all channels on the same cadence.
  • Automation – No manual copy‑paste; the worker handles the API call.
  • Transparency – The UI shows real‑time status and logs.
  • Extensibility – Adding a new platform is a matter of adding a branch in the Celery task and a new API wrapper.

The content calendar is a concrete example of how DoAide Pulse turns repository activity into a repeatable marketing pipeline, giving indie developers the tools of a full‑blown marketing automation stack without the overhead.

Related reading

Top comments (0)