Systems Library / Marketing Automation / How to Build an AI Blog Post Generator
Marketing Automation content marketing

How to Build an AI Blog Post Generator

Generate SEO-optimized blog posts using AI that match your brand voice.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Most AI blog post generators produce generic garbage that sounds like every other site in your niche. The problem is not the AI. It's the lack of constraints. Without a brand voice file, a target keyword strategy, and a structure that reflects how your audience actually reads, you get mediocre content that ranks for nothing and converts nobody. This ai blog post generator business system produces posts that sound like you, target real search intent, and are actually useful enough to share.

I use this system to produce first drafts for SEO content at about 80% quality. A human takes it from 80% to 100% in 15-20 minutes instead of 90+ minutes of writing from scratch. That's the right use of AI in a content workflow.

What You Need Before Starting

Step 1: Build Your Brand Voice Configuration

The voice config is what separates generic AI content from content that sounds like you.

# brand_voice.py

BRAND_VOICE = {
    "author_name": "Jay Banlasan",
    "persona":     "AI systems operator and paid ads specialist with 7+ years running campaigns for agencies and direct clients",
    
    "tone": [
        "Direct and practical — no fluff, lead with the answer",
        "First person — 'I built this', 'I noticed', 'I use'",
        "Conversational but technical — Grade 5-6 reading level",
        "Confident without being arrogant — state what works without overpromising",
        "Honest timelines and results — specific numbers over vague claims"
    ],
    
    "avoid": [
        "Hedge words: might, could potentially, it's possible",
        "Filler openers: Great question, That's a good point",
        "Corporate speak: leverage, synergy, robust, seamless",
        "Em dashes and en dashes",
        "Passive voice where active is possible",
        "Lists of options when the answer is obvious"
    ],
    
    "structure": {
        "intro":    "Open with a specific real scenario or result. No preamble.",
        "headings": "Action-oriented H2s using How/What/Why verbs",
        "sections": "One main idea per section. Short paragraphs.",
        "closing":  "Practical next step. No motivational sign-off."
    },
    
    "audience": "Business owners and marketing managers who run or oversee paid ads and want to use AI to work more efficiently"
}

Step 2: Build the Research and Outline Generator

The outline is the foundation. Bad outline = bad post no matter how good the writing is.

import anthropic
import json

_client = anthropic.Anthropic()

def generate_outline(
    target_keyword: str,
    audience_pain_point: str,
    word_count_target: int = 800,
    voice: dict = BRAND_VOICE
) -> dict:
    prompt = f"""Create a detailed blog post outline. Return JSON only.

Author persona: {voice['persona']}
Target keyword: {target_keyword}
Reader's pain point: {audience_pain_point}
Target length: {word_count_target} words
Tone: {', '.join(voice['tone'][:3])}

Requirements:
- Title must include the target keyword naturally
- H2 headings should be specific and action-oriented
- Each section note should describe what the section proves or demonstrates
- The post should be a tutorial or case study, not a listicle

Return:
{{
  "title": "exact post title",
  "meta_description": "155-char SEO description",
  "target_keyword": "{target_keyword}",
  "secondary_keywords": ["related term 1", "related term 2"],
  "intro_hook": "first sentence that grabs attention",
  "sections": [
    {{
      "heading": "H2 heading text",
      "subheadings": ["optional H3 1", "optional H3 2"],
      "content_note": "what this section covers and proves",
      "word_count": 120
    }}
  ],
  "conclusion_approach": "how to close the post"
}}"""
    
    response = _client.messages.create(
        model="claude-sonnet-4-5", max_tokens=1000,
        messages=[{"role": "user", "content": prompt}]
    )
    
    return json.loads(response.content[0].text.strip())

Step 3: Build the Section Writer

Write each section individually. This produces better quality than asking the AI to write 800 words in one shot.

def write_section(
    section: dict,
    post_context: str,
    voice: dict = BRAND_VOICE,
    include_code: bool = False
) -> str:
    avoid_list = "\n".join(f"- {item}" for item in voice["avoid"][:4])
    
    code_instruction = (
        "Include a practical code example if relevant to the concept. "
        "Code should be Python and actually runnable."
    ) if include_code else "No code blocks needed."
    
    prompt = f"""Write this section of a blog post. First person as {voice['author_name']}.

Post topic: {post_context}
Section heading: {section['heading']}
What to cover: {section['content_note']}
Target word count: {section.get('word_count', 150)}

Voice requirements:
- {chr(10).join(f'- {t}' for t in voice['tone'][:3])}

Avoid:
{avoid_list}

{code_instruction}

Write only the section body — no heading, no intro to the section.
Be specific. Use concrete examples over abstract descriptions."""

    response = _client.messages.create(
        model="claude-sonnet-4-5", max_tokens=600,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.content[0].text.strip()

Step 4: Build the Introduction Writer

The intro is the most important part. It determines whether someone reads or bounces.

def write_intro(outline: dict, voice: dict = BRAND_VOICE) -> str:
    prompt = f"""Write the opening two paragraphs of a blog post. First person as {voice['author_name']}.

Title: {outline['title']}
Keyword: {outline['target_keyword']}
Hook: {outline['intro_hook']}
Persona: {voice['persona']}
Audience: {voice['audience']}

Rules:
- First sentence matches the hook above or improves it
- Include the target keyword naturally in the first 100 words
- Second paragraph explains why this matters for the reader's business
- No preamble, no "In this post I will..."
- Grade 5-6 reading level
- Under 120 words total

Write only the two paragraphs. No heading."""

    response = _client.messages.create(
        model="claude-sonnet-4-5", max_tokens=200,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.content[0].text.strip()

Step 5: Assemble the Full Post

Combine all pieces into a formatted Markdown document.

def generate_full_post(
    target_keyword: str,
    audience_pain_point: str,
    word_count: int = 800,
    include_code_sections: bool = True
) -> str:
    print("Generating outline...")
    outline = generate_outline(target_keyword, audience_pain_point, word_count)
    
    print("Writing intro...")
    intro = write_intro(outline)
    
    sections = []
    for i, section in enumerate(outline["sections"]):
        print(f"Writing section {i+1}/{len(outline['sections'])}: {section['heading']}")
        body = write_section(
            section,
            post_context=outline["title"],
            include_code=include_code_sections and i < 3  # code in first 3 sections
        )
        sections.append(f"## {section['heading']}\n\n{body}")
    
    # Assemble
    post = f"""# {outline['title']}

{intro}

{"".join(chr(10)*2 + s for s in sections)}

## What to Do Next

{outline['conclusion_approach']}

---
*{BRAND_VOICE['author_name']} is an {BRAND_VOICE['persona']}.*
"""
    
    return post, outline

# Usage
post_text, outline = generate_full_post(
    target_keyword="ai request priority queue management",
    audience_pain_point="AI batch jobs blocking live customer requests",
    word_count=800
)

# Save
with open("draft_post.md", "w") as f:
    f.write(post_text)
    
print(f"\nPost generated. {len(post_text.split())} words.")
print(f"Title: {outline['title']}")
print(f"Meta: {outline['meta_description']}")

Step 6: Run a Self-Check Before Human Review

Have the AI check its own output against your standards before it reaches a human editor.

def self_check_post(post_content: str) -> dict:
    prompt = f"""Review this blog post draft against these standards:

1. Does it lead with a specific scenario or result? (no vague openers)
2. Does it avoid hedge words (might, could potentially)?
3. Does it avoid em dashes and en dashes?
4. Is it Grade 5-6 reading level throughout?
5. Are all claims specific (numbers, timeframes) not vague?
6. Does each section cover exactly what the heading promises?

Post:
{post_content[:3000]}

Return JSON: {{"passes": [list of passing checks], "fails": [list of failing checks], "overall": "pass|needs_revision"}}"""

    response = _client.messages.create(
        model="claude-haiku-3", max_tokens=400,
        messages=[{"role": "user", "content": prompt}]
    )
    try:
        return json.loads(response.content[0].text.strip())
    except Exception:
        return {"overall": "review manually"}

What to Build Next

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems