If you manage half a dozen client blogs, three newsletters, and a smattering of LinkedIn pages, you already know the busy-work tax. Draft shuffling, approval pings, copy-paste into five CMSes—it burns hours nobody bills. This tutorial shows how we cut that overhead with OpenClaw on ClawCloud. The stack is opinionated but reproducible: one agent per client, one shared calendar, and a pipeline that goes from topic idea to published post with audit logs in between. All code below runs on OpenClaw v0.38.2 (Node 22+) and the hosted ClawCloud gateway as of June 2024.
Why agencies plug OpenClaw into content ops
Three reasons keep coming up in GitHub discussions:
- Multi-channel connectors out of the box. Slack, Gmail, Notion, Webflow, WordPress, social APIs—all ship via Composio integrations. No Zapier fees.
- Memory + scheduled tasks. An agent remembers brand guidelines and wakes itself up on Mondays to draft next week’s pieces.
- Approval workflow hooks. Because it’s code, you can force human sign-off before anything hits production.
The alternative is juggling five SaaS tools and praying webhooks fire. We replaced CoSchedule + Airtable + Buffer with ~150 lines of YAML and two slash commands.
Project layout: one repository, many clients
I keep a single monorepo called agency-content-ops. Each client lives under /clients/<slug> with its own config, prompt library, and memory store. Shared templates (SEO briefs, social snippets) sit in /common/templates.
- /clients/acme/ – voice profile, calendar, publishing rules
- /clients/helix/ – same structure, different parameters
- /scripts/ – CLI helpers for batch tasks
Push the repo to GitHub and give your ClawCloud agent read-only deploy keys. Versioning content ops like code feels weird at first but means PRs double as approvals.
Spinning up agents on ClawCloud
Create one agent per client so memory and audit logs stay isolated. From the ClawCloud dashboard:
- "New Agent" → name it
acme-content. - Select Node 22 runtime, drop in your GitHub repo URL.
- Add environment variables:
COMPOSIO_KEY=sk_live_... WP_ENDPOINT=https://blog.acme.com/xmlrpc.php HELIX_LINKEDIN_TOKEN=... - Hit Deploy. The gateway spins up in ~60 s; the daemon keeps it alive.
Repeat for every client. ClawCloud bills per agent-hour, so idle ones cost pennies.
Defining a client voice profile
Drop a voice.md file in the client folder. Example snippet:
# Acme Robotics – Voice Guide
Tone: knowledgeable but sarcastic, 6th-grade readability.
Third-person plural POV.
Forbidden phrases:
- revolutionary
- cutting-edge
Brand facts:
- Founded 2014; HQ Austin, TX.
- Key product: SwiftBot 3.2.
OpenClaw ingests that file into persistent memory on first boot:
openclaw memory import clients/acme/voice.md --agent acme-content
Now every prompt can rely on {{memory.voice.guide}} without repeating itself.
Automating the content calendar
Google Sheets still wins for stakeholders
Clients want visibility, not another login. We keep a single Content Calendar Google Sheet with one tab per client:
- Column A: Publish date
- Column B: Status (Idea / Draft / Ready / Scheduled / Live)
- Column C: Title
- Column D: SME assigned
- Column E: URL once live
OpenClaw polls the sheet every six hours using the Composio google.sheets.read tool. When it sees Status=Idea for a future date, it moves the row to Draft and kicks off generation.
# /clients/acme/flows/calendar.yaml
trigger:
schedule: "0 */6 * * *" # every 6h
steps:
- google.sheets.read:
sheet_id: 1hH6...
range: "Acme!A2:E100"
- foreach: "rows where column_B == 'Idea'"
do:
- set column_B to 'Draft'
- save
- call: generate_draft
The generate_draft sub-flow lives next.
Generating on-brand first drafts
# /clients/acme/flows/generate_draft.yaml
params: [row]
steps:
- prompt:
template: ../../common/templates/blog_draft.md
vars:
title: "{{row.C}}"
tone: "{{memory.voice.tone}}"
product_facts: "{{memory.voice.brand_facts}}"
- save as: draft.md
- github.createPR:
repo: agency-content-ops
branch: "draft/{{row.C | slugify}}"
files:
- path: clients/acme/drafts/{{row.C | slugify}}.md
content: "{{draft.md}}"
reviewers: ["@acme-marketing", "@agency-editor"]
We draft in Markdown, open a Pull Request, and tag humans for eyeballs. Nothing ships without a green merge button.
Human-in-the-loop approvals
Two layers protect brand safety:
- GitHub PR review. Writers or SMEs leave comments inline. Merge = approval.
- Slack slash command. After merge, an editor runs
/claw schedule post-42. The agent verifies the post passes ourlint-content.mjs(spelling, SEO length) before setting calendar status to Scheduled.
# /scripts/lint-content.mjs
import { readFileSync } from "fs";
import spellcheck from "@agency/spell";
const md = readFileSync(process.argv[2], "utf8");
if (spellcheck(md).errors > 0) throw new Error("typos detected");
if (md.length < 1500) throw new Error("content too thin");
If lint fails, the Slack thread shows the error and the calendar stays in Draft. No silent failures.
Publishing automation to CMS & social
The final hop happens at midnight on publish day. We use a cron inside ClawCloud (they run on the same server, so latency is nil):
# /clients/acme/flows/publish.yaml
trigger:
schedule: "0 0 * * *" # 00:00 UTC daily
steps:
- google.sheets.read: { ... }
- foreach: "rows where column_B == 'Scheduled' and column_A == today"
do:
- call: wordpress.post
- call: linkedin.share
- set column_B to 'Live'
- set column_E to "{{wordpress.post.url}}"
- save
WordPress call uses XML-RPC today because REST API lacks an image upload we need. Not pretty, works. For clients on Webflow or Ghost, swap in other Composio tools; the flow file stays the same.
Edge case: regional publish time
Acme wants 9 AM EST. We set an env var PUBLISH_TZ=America/New_York and use Luxon inside a custom Node tool. Couldn’t find an off-the-shelf integration that handled daylight savings cleanly.
Performance tracking across clients
Reporting is usually where automation pipelines fall apart. We wired three data sources:
- Google Analytics 4 for organic pageviews
- Ahrefs API for backlink growth
- LinkedIn stats for post impressions
Every Monday 02:00 UTC, a global agent (not client-specific) pulls numbers, dumps them into BigQuery, and triggers metabase.sendPulse emails to account managers.
# /global/flows/weekly_report.yaml
trigger: { schedule: "0 2 * * MON" }
steps:
- foreach: clients
do:
- google.analytics.get:
property_id: "{{client.ga4_property}}"
date_range: last_7_days
- ahrefs.domain_metrics: { domain: "{{client.domain}}" }
- linkedin.stats: { account_id: "{{client.li_account}}" }
- bigquery.insert: { table: content_metrics }
- metabase.sendPulse:
pulse_id: 42
Account managers forward the Metabase PDF to clients—lazy but effective. Eventually we’ll pipe the numbers back into the Google Sheet so status and metrics live in one doc.
Putting it all together: you own the pipeline
This setup replaced eight separate SaaS subscriptions and about 25 hours/month of manual process. It took two weekends to wire because OpenClaw is just Node + YAML. Hard parts:
- OAuth for fifteen connectors. Composio saved time, but each client domain still needed its own consent screen. Expect a morning lost to Google Cloud Console.
- Rate limits. LinkedIn capped us at 100 calls/day. We had to batch stats pulls.
- Brand voice hallucinations. The agent sometimes drifted. We fixed it by shoving the forbidden phrase list into every prompt header, not only memory.
If you bill monthly retainers, the math is easy: less keyboard time, higher margin. The next obvious step is letting clients open tickets ("write about X") straight from Slack and feeding that into the Idea column—same pattern, new trigger. When that lands, content ops finally feels like code, not chaos.
Next step: clone the repo and try one client this week
Grab the starter repository at github.com/clawcloud/examples/agency-content-ops, fork it, and spin up your first agent on the ClawCloud free tier. If it doesn’t cut at least five Slack pings by Friday, file a GitHub issue—I owe you a coffee.