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:

  1. "New Agent" → name it acme-content.
  2. Select Node 22 runtime, drop in your GitHub repo URL.
  3. Add environment variables:
    COMPOSIO_KEY=sk_live_... WP_ENDPOINT=https://blog.acme.com/xmlrpc.php HELIX_LINKEDIN_TOKEN=...
  4. 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:

  1. GitHub PR review. Writers or SMEs leave comments inline. Merge = approval.
  2. Slack slash command. After merge, an editor runs /claw schedule post-42. The agent verifies the post passes our lint-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.