DEV Community

Cover image for I Automated My Daily Blog with GitHub Actions, Gemini and the Blogger API
Ritika Rajput
Ritika Rajput

Posted on AI-assisted

I Automated My Daily Blog with GitHub Actions, Gemini and the Blogger API

I wanted my blog to publish a new post every morning without me touching it. So I built a small pipeline: GitHub Actions runs a Python script on a schedule, Gemini writes the post, and the Blogger API publishes it.

It worked, but only after I hit three errors that the tutorials never mention. Here is the setup and the fixes.

The architecture

  1. GitHub Actions triggers the workflow every day on a cron schedule.
  2. generate_post.py asks the Gemini API to write one post about a topic from a skills.txt list.
  3. post_to_blogger.py publishes it through the Blogger API.
  4. The workflow commits the saved post file so topics don't repeat.

All credentials live in GitHub Secrets, never in the code:

BLOGGER_CLIENT_ID
BLOGGER_CLIENT_SECRET
BLOGGER_REFRESH_TOKEN
BLOGGER_BLOG_ID
GEMINI_API_KEY
Enter fullscreen mode Exit fullscreen mode

The workflow

name: Daily Blogger Auto-Post

on:
  schedule:
    - cron: "30 4 * * *"   # 10:00 AM IST (cron runs in UTC)
  workflow_dispatch:

permissions:
  contents: write

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - name: Generate post
        run: python scripts/generate_post.py
        env:
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
      - name: Post to Blogger
        run: python scripts/post_to_blogger.py
        env:
          BLOGGER_CLIENT_ID: ${{ secrets.BLOGGER_CLIENT_ID }}
          BLOGGER_CLIENT_SECRET: ${{ secrets.BLOGGER_CLIENT_SECRET }}
          BLOGGER_REFRESH_TOKEN: ${{ secrets.BLOGGER_REFRESH_TOKEN }}
          BLOGGER_BLOG_ID: ${{ secrets.BLOGGER_BLOG_ID }}
Enter fullscreen mode Exit fullscreen mode

Remember that GitHub cron uses UTC. For 10:00 AM in India (UTC+5:30), the cron is 30 4 * * *. Scheduled runs can also start 10 to 30 minutes late, so I schedule a little earlier than I need.

Error 1: GEMINI_API_KEY is not set

My first run failed in 13 seconds with exactly that message. The key was in my local .env, but GitHub never sees your .env file.

Fix: add it under Settings → Secrets and variables → Actions → New repository secret.

Secret names can only contain letters, numbers and underscores. If GitHub rejects the name, you probably pasted the whole NAME=value line into the name field.

Error 2: the model was retired (HTTP 404)

The next run failed with:

This model models/gemini-2.0-flash is no longer available.
Enter fullscreen mode Exit fullscreen mode

Model names get retired. Hard-coding one means your automation silently dies one day.

Fix: read the model from an environment variable, and if the API returns 404, ask the ListModels endpoint for a current "flash" model that supports generateContent, then retry.

import os, requests

def pick_model(api_key):
    url = "https://generativelanguage.googleapis.com/v1beta/models"
    r = requests.get(url, params={"key": api_key}, timeout=30)
    r.raise_for_status()
    models = [
        m["name"] for m in r.json().get("models", [])
        if "flash" in m["name"]
        and "generateContent" in m.get("supportedGenerationMethods", [])
    ]
    if not models:
        raise RuntimeError("No usable Gemini flash model found")
    return sorted(models)[-1]

model = os.getenv("GEMINI_MODEL") or pick_model(os.environ["GEMINI_API_KEY"])
Enter fullscreen mode Exit fullscreen mode

I also retry 3 times and validate the output so a half-written post never gets published.

Error 3: the refresh token that expires

If your Google OAuth app is in Testing mode, refresh tokens expire after about 7 days. Your automation works all week, then dies.

Fix: in Google Cloud Console, open the OAuth consent screen and move the app to In production. Then generate a new refresh token and update it in both .env and the GitHub Secret.

Making it safe to run daily

  • Idempotency: save each post as posts/<date>-<slug>.md and skip if today's post already exists, so a manual re-run does not create duplicates.
  • Quality guardrails: reject drafts that invent personal claims like fake clients or made-up percentages.
  • Fail soft on images: if the image fetch fails, publish without one instead of failing the whole run.
  • Test as a draft first: run once manually with drafts enabled, check Blogger, then switch to publishing.
  • Never print secrets in logs.

A note on quality

Automation should not mean spam. I keep topics tied to real skills, ask for practical guides instead of invented stories, and read the output before trusting it. Search engines care about useful content, not volume.

Wrapping up

The pipeline is small, but the lessons are general: secrets live outside your repo, model names expire, and OAuth apps in testing mode expire too.

If you want to see where my posts end up, check my portfolio at ritika-rajput-portfolio.vercel.app, and my videos on RitikaMoments.

Have you automated something similar? What broke first for you?

Top comments (0)