# Build the marketing plugin

Source: https://docs.coderhelm.com/plugins/walkthrough

A full walkthrough, from an empty repo to a plugin that drafts launch posts from each release and publishes after an editor approves.

> Coming soon: this page describes work that is not on main yet.

<Callout type="info" title="Coming soon">Plugins are being built and switched on team by team; they are not on main yet. This page follows the work in progress and will be updated when plugins ship.</Callout>

In this walkthrough you build a plugin that:

- reads each new release (and a weekly schedule, and briefs from people),
- drafts a blog post in your CMS, three LinkedIn posts and an email, in your brand voice,
- waits for an editor to approve a post, then publishes it to LinkedIn at the time they pick.

The finished plugin is in the docs repository at [`docs/examples/marketing`](https://github.com/CoderHelm/coderhelm/tree/main/docs/examples/marketing). The files on this page are included from it, and the docs' tests validate it on every build, so what you read here is what passes.

<Steps>
<Step>

### Create the repo [#create-the-repo]

```bash
mkdir marketing-plugin && cd marketing-plugin
git init
mkdir -p skills/brand-voice skills/launch-checklist agents
```

</Step>
<Step>

### Write the brand voice skill [#write-the-brand-voice-skill]

The skill holds what is true for every draft. It refers to the team's own guide, a setting, rather than containing any brand's text.

# Brand voice [#brand-voice]

The team's own voice guide is in the `brand_voice_doc` setting. Follow it first.
These rules apply when the guide says nothing:

- Write to one reader, in the second person ("you").
- Lead with what changed for the reader, then how it works.
- Short sentences. One idea per paragraph.
- Name the feature the way the product names it.
- No superlatives you cannot prove. Never use a claim listed in the
  `banned_claims` setting.
- Every claim about the product must come from the release notes or a merged
  pull request. If it is not there, leave it out.

## Length [#length]

| Channel  | Length                         |
| -------- | ------------------------------ |
| LinkedIn | 40 to 120 words, one link      |
| Blog     | 300 to 700 words, one heading per change |
| Email    | Subject under 60 characters; body under 150 words |

</Step>
<Step>

### Add a checklist skill [#add-a-checklist-skill]

A second skill the agent runs over each draft before saving it.

# Launch checklist [#launch-checklist]

Before saving any draft, check that it:

1. Names the release (the tag) and links to its notes.
2. Says who the change is for.
3. Has one clear next step for the reader, such as "Try it from Settings".
4. Contains no claim from `banned_claims` and nothing the release notes do not support.
5. Fits the length for its channel in the brand voice skill.

If a check fails, fix the draft. If it cannot be fixed from the sources, say so
in the draft's note to the editor.

</Step>
<Step>

### Write the agent [#write-the-agent]

The agent's instructions say what it gets for each trigger, what to produce, and what it must never do. Its `tools:` list names only tools the manifest will grant.

You write launch content for the team's product.

## What you get [#what-you-get]

- On a release: read the release notes CoderHelm wrote, and the merged pull requests in it, with `get_release_notes`.
- On the weekly schedule: the releases since last Monday. If there were none, stop and save nothing.
- On a brief: the person's brief and the channels they picked.

## What you do [#what-you-do]

1. Read the `brand-voice` and `launch-checklist` skills.
2. Look up earlier posts on the same feature with `query_documents`, so you do not repeat them.
3. Write one draft per channel:
   - **blog**: save it in Sanity with `create_draft`, then save a `blog` draft that links to it.
   - **social**: three LinkedIn variants as separate `social` drafts.
   - **email**: one `email` draft with a subject line.
4. Run the launch checklist on each draft and fix what fails.

## What you never do [#what-you-never-do]

- Never call `publish_post` yourself. Publishing happens only after an editor
  approves a draft, and CoderHelm makes that call with the approved text.
- Never invent numbers, customers or quotes.

</Step>
<Step>

### Write the manifest [#write-the-manifest]

Now declare everything the plugin needs.

```yaml title="plugin.yaml"
# yaml-language-server: $schema=https://docs.coderhelm.com/schema/plugin-manifest.v1.json
name: marketing
version: 1.0.0
description: Drafts launch posts, emails and blog updates from each release, in your brand voice, for an editor to approve.

skills:
  - skills/*/SKILL.md
agents:
  - agents/*.md

settings:
  - key: brand_voice_doc
    type: string
  - key: banned_claims
    type: string
  - key: sanity
    type: connection
    provider: mcp
  - key: linkedin
    type: connection
    provider: oauth2
    scopes: [w_member_social]
    sensitive: true

tools:
  - name: get_release_notes
    kind: builtin
    description: Read the release notes CoderHelm wrote for a release, with its merged pull requests.
    risk: read
  - ref: mcp:sanity
    allow: [get_document, query_documents, create_draft]
    risk: write
  - name: publish_post
    kind: http
    description: Publish an approved post to the company's LinkedIn page.
    risk: consequential

triggers:
  - on: github.release
  - on: schedule
    cron: "0 14 * * MON"
  - on: manual
    form:
      brief: text
      channels: [linkedin, blog, email]

outputs:
  - type: draft
    kinds: [blog, social, email]
  - type: publish
    via: publish_post
    approval:
      role: marketing_editor
      min: 1

budget:
  monthly_tokens: 2000000
```

Section by section:

- **settings**: the brand guide and banned claims are values the team types; the agent reads them. \`sanity\` and \`linkedin\` are accounts the team connects.
- **tools**: \`get_release_notes\` is a built-in read tool. From Sanity, only three tools, at \`write\` risk because drafts there are not public. \`publish_post\` is the plugin's own tool, \`consequential\` because it reaches the public, and it signs in with the team's \`linkedin\` connection.
- **triggers**: every release, Mondays at 14:00, and a brief form with a list of channels.
- **outputs**: three kinds of draft, and publishing through \`publish_post\` once one approver says yes.
- **budget**: at most 2 million tokens a month, unless the team changes it.

</Step>
<Step>

### Validate [#validate]

\`\`\`bash
curl -fsSLO https://docs.coderhelm.com/tools/validate-plugin.mjs
node validate-plugin.mjs .
\`\`\`

\`\`\`txt
ok       marketing 1.0.0
\`\`\`

Try breaking it: change \`publish_post\`'s \`risk\` to \`write\` and the validator refuses, because a publish output must go through a consequential tool. Add \`delete_document\` to the agent's \`tools:\` and it refuses again, because the manifest doesn't grant it.

</Step>
<Step>

### Push and install [#push-and-install]

Push the repo to GitHub. Then, in CoderHelm:

1. An admin opens **Plugins**, adds the repository under **Plugin sources** (its public address, or \`owner/name\` if your GitHub App can read it), and picks the branch, tag or commit to follow.
2. **marketing** appears under **Plugins you can install**. The admin installs it; with two or more admins, a second admin approves.
3. On the plugin's page, the team fills in \`brand_voice_doc\` and \`banned_claims\`, connects Sanity (on **Connections**) and LinkedIn, and lists the editors under **Who approves what goes out**.

</Step>
<Step>

### Run it [#run-it]

Start it by hand first: on the plugin's page, under **Write a brief**, ask for "Announce the new export button", pick channels and click **Start the run**. Or publish a release on a connected repository.

The run reads the release notes, checks earlier posts in Sanity, and saves the drafts. If the team turned on Slack or Teams notices, a card goes to the channel.

</Step>
<Step>

### Approve and publish [#approve-and-publish]

An editor (not the person who asked) opens **Plugins** → **Review**, edits one LinkedIn variant (which saves a new version), and approves it for Tuesday at 10:00 with **Approve for that time**. On Tuesday, CoderHelm calls \`publish_post\` with exactly the approved text. Until then, anyone allowed can **Cancel publish**. The **Audit log** shows the trigger, the plugin version, each tool call and its risk, the editor's approval and the result.

</Step>
</Steps>

## Where to go next [#where-to-go-next]

- Add a `email` publish tool for your email provider, with its own approval.
- Add a read tool for post analytics from your connections, so next week's run can see what worked.
- Put the plugin in your [private marketplace](/plugins/marketplace) for other teams.
