Run a marketing team without leaving Slack. You bring work to a team lead: a launch to plan, posts to write, a page that isn't converting. The lead holds the shared picture of your product, routes the request to the specialist who does that kind of work, and hands back what they produced. The content marketer writes long-form into Notion, the social media coordinator queues drafts in Typefully, the email specialist builds and sends campaigns in Resend, and irreversible actions like sending a broadcast pause for your approval, rendered as buttons in the thread.
The team is built on eve, a filesystem-first framework for durable backend agents from Vercel. Notion, Resend, and Slack authenticate through Vercel Connect, and Vercel Blob and AI Gateway authenticate with your project's OpenID Connect (OIDC) token. The single static credential in the project is the Typefully API key.
Deploy the template now, or read on for a deeper look at how it all works.
Marketing Team eve Template
Five marketing specialists behind one Slack lead: positioning and brand context, long-form content in Notion, social drafts in Typefully, SEO audits, and email campaigns in Resend.
Copy link to headingQuick start with an AI coding agent
If you're working with an AI coding agent like Claude Code or Cursor, you can use this prompt to have it help you with building your agent:
I want to build a team of marketing agents with the eve framework, using the marketing team template. Read the setup instructions at https://agent-resources.dev/marketing-team-eve-template.md and follow them. They will cover deploying the template, building with eve, how everything works overall, and more.
Copy link to headingVercel Plugin
The Vercel Plugin turns your AI coding agent (e.g., OpenAI Codex, Claude Code, or Cursor) into a Vercel expert. It adds skills, slash commands, and current knowledge of the tools this template uses, including Vercel Connect, Vercel Blob, and AI Gateway. The plugin is optional; it isn't required to use eve or to follow this guide.
Copy link to headingMeet the team
The lead routes each request to exactly one of five specialists.
| Specialist | Owns | Hands back |
|---|---|---|
product-marketer | Positioning, messaging, competitive alternatives, and the shared brand context document | The brand context document itself |
content-marketer | Long-form: blog posts, landing pages, case studies, newsletters, docs | A Notion page link |
social-media-coordinator | Short-form for X, LinkedIn, Threads, Bluesky, and Mastodon, plus the Typefully queue | Drafts in Typefully |
seo | Page and site audits, hierarchy and internal linking, JSON-LD schema, templated page sets | Recommendations, long audits as artifacts |
email | Reworking existing copy to survive an inbox, then building, targeting, and sending in Resend | A Resend campaign link |
The dependency runs one way. The product marketer writes the brand context; every other specialist reads it at the start of each task. That makes it the one piece of shared state whose quality bounds everything else, which is why a single specialist owns authoring it.
A newsletter is the case where two specialists chain. The content marketer authors the prose, and the email specialist reworks it for the inbox and runs Resend. The lead runs the two calls in order and passes the first specialist's output into the second's brief.
Copy link to headingSetup and deployment
Copy link to headingWhat you need before deploying
You need five things to deploy and run the team:
- A Vercel account.
- A Slack workspace where you can install an app.
- A Notion workspace for briefs and finished long-form pieces.
- A Resend account with a domain you can verify, if you want the email specialist.
- A Typefully workspace and its API key, if you want the social specialist.
For local development, you also need Node.js 24 or newer, pnpm, and the Vercel CLI.
Copy link to headingDeploy to Vercel
Deploying with the one-click flow provisions most of what the team needs:
| Provisioned | Sets |
|---|---|
| Notion connector | NOTION_CONNECTOR |
| Resend connector | RESEND_CONNECTOR |
Slack connector, with its trigger pointed at /eve/v1/slack | SLACK_CONNECTOR |
| Public Vercel Blob store for all assets | Blob credentials |
| Prompt for your Typefully API key | TYPEFULLY_API_KEY |
One step stays manual after the deploy finishes: verifying a Resend sending domain. It's covered below, and it's skippable if you don't use email.
Copy link to headingConnect Slack
The one-click flow installs the Slack app and points its event trigger at the route the agent serves (/eve/v1/slack), so there's nothing to wire up. After the deploy finishes, invite the bot to a channel and @mention it or DM it to start a thread.
The bot also auto-replies to un-mentioned messages in a thread it's already part of, but only while the original requester is the sole human participant. Once anyone else joins the thread, an explicit @mention is the way to re-engage it.
Slack is optional. The eve.ts channel serves the dev terminal UI (TUI) and your own front end without it.
You can't test the Slack surface locally, because Slack forwards events to Vercel Connect, which verifies them and delivers them to your deployed project rather than to a local URL. Test the inbound Slack path against a preview or production deployment; everything else runs in the local dev TUI.
Copy link to headingAuthorize Notion and Resend
The first time a team member asks the agent to touch Notion or Resend, Vercel Connect will prompt them to sign in to that service in the browser. That sign-in is per user, so the agent acts as the signed-in person with their own permissions. A Notion page is created by the real author, and a Resend send is attributed to the person who approved it, with no shared integration token to manage.
| Service | Authenticates as | You manage |
|---|---|---|
| Notion | Each user, OAuth via Vercel Connect | Connector UID only |
| Resend | Each user, OAuth via Vercel Connect | Connector UID only |
| Slack | Bot token brokered by Vercel Connect | Connector UID only |
| Typefully | One shared workspace key | TYPEFULLY_API_KEY |
| Vercel Blob | Project OIDC token | Nothing |
| AI Gateway | Project OIDC token | Nothing |
Per-user scoping earns its keep most on Resend. Sending is the only thing this team does that cannot be undone, so a per-user token means the approval names who agreed to it and Resend records who sent it, instead of every campaign arriving from one shared workspace key.
Typefully is the exception: its MCP server only accepts a static key, so TYPEFULLY_API_KEY is the single static credential in the project and the connection is shared across all users.
On top of user scoping, every irreversible action is gated on approval: the call pauses and renders approve/deny buttons in Slack (or an approval prompt in the TUI) before it runs.
| Action | Pauses |
|---|---|
| Send a Resend broadcast, email, or batch | Always, scheduled or not |
| Delete a Resend broadcast, template, contact, segment, or topic | Always |
| Change a contact's topic subscriptions | Always |
| Delete a Typefully draft, thread, or comment | Always |
| Create or edit a Typefully draft | Only when the call schedules or publishes it |
| Move pages or change views in Notion | Always |
| Create or update a Notion page | No, drafting is the normal flow |
delete_asset, clear_user_preferences | Always |
save_brand_context | No, by design. The tool description tells the model to agree the document with you first |
The Resend connection goes one step further than gating. It narrows what the model can discover at all with a tools.allow list, cutting roughly 85 published tools down to the 47 that make up the campaign, list, and diagnostic surface. Account administration such as API key creation and domain writes is never on the table.
Copy link to headingVerify a Resend sending domain
Verify a sending domain and create at least one segment in the Resend dashboard. The agent reads that state and picks from it, but cannot create a domain or verify DNS for you. Nothing can be sent without a verified domain. Skip this step if you're not using email.
Copy link to headingLocal development
You can run the team locally in the dev TUI.
First, clone the repository that the one-click flow created under your GitHub account:
Then link it to the Vercel project you deployed and pull the environment variables:
vercel env pull writes a short-lived OIDC token into .env.local, which authenticates Vercel Blob and the model locally. Re-run it when the token expires. Then start the dev server and link a model provider with /model in the TUI:
In the TUI, talk to the lead and watch which specialist it picks. Approval gates render there too, so it's the place to confirm a send actually stops before it goes out.
The template ships a dev-only auth shim (agent/channels/eve.ts) that presents your local session as a signed-in user, so the user-scoped Notion and Resend connections work from the TUI without affecting production.
These are the commands you'll use day to day:
| Command | Does |
|---|---|
pnpm dev | Start the eve dev TUI |
pnpm validate | Lint, typecheck, and discovery diagnostics in one |
pnpm check / pnpm fix | Check formatting and lint rules, and auto-fix what it can |
pnpm typecheck | tsc --noEmit |
npx eve info | Print every discovered tool, skill, connection, and subagent |
eve deploy | Ship to production, the same as vercel deploy --prod |
Linting and formatting come from Ultracite, a Biome preset. npx eve info is the fastest way to confirm a change landed: if a file you added doesn't appear, discovery didn't classify it as an authored slot, and .eve/discovery/diagnostics.json says why.
Copy link to headingHow the marketing team works
The lead runs one loop: ground itself in the shared brand context and the user's preferences, route the request to exactly one specialist with a complete brief, and hand back what the specialist produced.
- @mention in Slack. A team member @mentions the bot or DMs it with a marketing task: a launch to plan, a thread to draft, a page to audit, a newsletter to send. The Slack channel turns the message into a session and threads every reply.
- Ground in brand context and preferences. The lead calls
get_brand_contextfor the team's shared picture of the product andget_user_preferencesfor that person's standing notes (default platforms, a length they always want, a review step they expect). When the brand context comes back empty, the lead routes to the product marketer first to interview you and write it, then returns to the original request. - Route once, with a full brief. Each specialist starts in a fresh session and works from its brief alone: no conversation history, no parent skills, tools, or connections. So the lead's real job is packing the brief with the deliverable, the relevant brand context quoted rather than referenced, the constraints, and the destination. When a request has an order (a newsletter needs prose before it needs an inbox pass), the lead runs the calls in sequence and chains the output.
- Specialists load skills on demand. Each specialist pulls in the skill that matches the task, from per-platform style guides with banned-words lists to SEO audit checklists and email deliverability rules. Loading a skill only adds instructions to the turn; it never adds a new action the agent can take.
- Specialists find their own evidence and edit their own drafts. Each one uses
web_searchandweb_fetchagainst a source budget for outside facts, runslint_against_styleon each draft to catch banned words, and edits in deliberate passes against the sharedwriting-qualityrubric before handing work back. - Deliverables land where they belong. Long-form goes into Notion as a page, social drafts land in the Typefully queue, campaigns are built in Resend, and long audits are saved as handoff artifacts in Vercel Blob and passed by ID. The lead relays the link or ID rather than pasting the document into the thread.
Every credential in this loop is brokered at runtime:
- Notion, Resend, and Slack through Vercel Connect
- Vercel Blob and AI Gateway through the OIDC token
- Typefully through the one static workspace key
Copy link to headingCode walkthrough
The whole team is defined under agent/, and eve discovers each capability from the filesystem. A tool's name is its filename, a subagent's name is its directory name, and a connection's name is its filename, so there's no central registry to wire up. These are the pieces that matter.
Copy link to headingThe Slack surface
The Slack channel lives in agent/channels/slack.ts:
connectSlackCredentials reads the connector UID from SLACK_CONNECTOR and lets Vercel Connect handle token rotation, multi-workspace tenancy, and webhook verification, so none of that lives in your code. Because the file is slack.ts, eve registers it as the slack channel and serves its events at /eve/v1/slack.
The onMessage handler is the detail worth reading: un-mentioned messages only get an auto-reply in a thread the bot is already subscribed to, and only while the original requester is the sole human participant. The channel also attributes Slack Workflow posts (which have no human author) to the first user the message mentions besides the bot, so workflow-triggered sessions still resolve to a real principal for the user-scoped connections.
Copy link to headingThe lead that routes, not writes
The lead's runtime configuration is minimal:
Everything else about the lead is agent/instructions.md, and its core rule is that the lead is a routing layer, not a second editor. It grounds itself in the brand context and user preferences, picks one specialist by its description, writes a complete brief, and passes the result back largely as the specialist wrote it. If what comes back is wrong or thin, it re-briefs rather than patching the work itself.
The model ID routes through the Vercel AI Gateway, which authenticates through the linked project, so there's no provider API key to set. Every agent runs the same model by default, and because the lead routes rather than produces, it's the first place to try a cheaper tier if cost matters more than routing quality.
Copy link to headingSpecialists as subagents
Each specialist is a directory under agent/subagents/ with the same shape: an agent.ts whose description is all the lead sees when routing, an instructions.md, its own sandbox.ts, its own connections/, a skills/ directory, and a tools/ directory. Subagents inherit nothing from the lead: no conversation history, no skills, tools, or connections. That's why each specialist loads the brand context again through its own get_brand_context, and why the lead's brief has to be complete.
The routing description is a contract, not a label. Here's the email specialist's:
The tree is deliberately one level deep. Each specialist gathers its own evidence with web_search and web_fetch and runs its own review pass against a written rubric, so it doesn't delegate further. Each hop costs a full context handoff, so the lead delegates and specialists do not.
Two specialists carry an evidence boundary worth knowing about. seo has no crawler, rank tracker, or Search Console, and its audit checklist marks every check as verifiable by fetch or not verifiable at all. email cannot see inbox placement or domain reputation, and its deliverability checklist does the same. Both are built to say what they could not check rather than infer it.
Copy link to headingGating the Resend connection
Resend is the connection with the most safeguards, because it's the one whose writes reach people directly. Three lists in resend.ts carry the weight:
Three details matter here:
tools.allowis an allow list, not a block list. Resend's MCP server publishes around 85 tools, including API key creation, domain removal, and webhook CRUD. The template allows the 47 that make up the campaign, list, and diagnostic surface, so a tool the server adds later is invisible until someone adds it on purpose.- Sends are always gated, scheduled or not. Resend splits composing from committing into separate tools, and every tool in
SEND_TOOLSis the commit step. Mail cannot be recalled, so there's no unscheduled case worth letting through. update-contact-topicsis gated alongside the deletes because it changes what someone consented to receive, which is the person's decision to reflect rather than the model's to infer.
Copy link to headingConditional gating on Typefully
Typefully's connection in typefully.ts shows a subtler approval policy: deletes always pause, but creating or editing a draft pauses only when the call actually publishes or schedules, so saving a plain draft stays friction-free.
The approval policy receives the tool's input as well as its name, so it can gate on what the call does rather than only on what it's called: a typefully_create_draft with publish_at set commits to publishing, and the same call without it saves work with no gate.
Copy link to headingMarketing expertise as skills
Twenty-three skills sit under the specialists, each a folder with a SKILL.md and reference files:
| Specialist | Skills |
|---|---|
product-marketer | positioning, messaging, customer-research, brand-context |
content-marketer | blog-style, content-planning, content-editing, writing-quality |
social-media-coordinator | x-style, linkedin-style, threads-style, bluesky-style, mastodon-style, writing-quality |
seo | seo-audit, site-architecture, schema, programmatic-seo |
email | email-style, email-adaptation, deliverability, resend-build, writing-quality |
The description in each skill's frontmatter is a routing hint, not a label. eve exposes it to the model alongside a built-in load_skill tool, and the model loads a skill only when a request matches the description. Each <surface>-style skill carries a references/banned-words.json, which is also what the lint_against_style tool reads at runtime, so the style skill stays the single source of truth for its surface.
The writing-quality skill is shared differently. eve scopes skills to one agent, so instead of four byte-identical copied directories, the skill is a defineSkill factory in agent/lib/writing-quality/ called from a one-line skills/writing-quality.ts file in each prose agent. The factory materializes the reference files as real siblings in the sandbox, so the compiled package matches an authored directory.
Copy link to headingShared brand context and per-user memory
Two kinds of durable state live in Vercel Blob, and their key derivation is the part worth reading.
The brand context is one shared document at a constant key. Every team member and every specialist reads and writes the same file, because it describes the product rather than any one person. save_brand_context is deliberately ungated; its tool description tells the model to agree with the document first.
User preferences are the opposite: one file per person, keyed by the framework-resolved principal.
The Blob key is derived entirely from ctx.session.auth.current, never from model input, so a session can only ever read or write its own user's file, and the principal id is hashed so the stored path carries no raw identifier. The general asset tools refuse every reserved prefix, so they can't be used as a side channel to the brand context or another user's preferences. clear_user_preferences is irreversible and gated on approval.
Copy link to headingHandoff artifacts
Long documents move between specialists by id rather than by pasting text through the lead's context. save_artifact writes a Markdown document (up to 200,000 characters) to a private Blob under a reserved artifacts/ prefix and returns an id; read_artifact reads it back through the authenticated path. All five specialists hold both tools, and the lead holds only the reader.
This is the main channel from the content marketer to the email agent: the lead relays the artifact id from one brief into the next, so an SEO audit or a finished draft never passes through the thread. Artifact ids are model-supplied on read, which is why the id format is validated strictly; without that, a caller could pass a path-traversal string and read a managed document through a tool that was never meant to reach one.
Copy link to headingTracked links
build_tracked_link adds utm_* parameters to a batch of links, deriving the source and medium from a fixed surface vocabulary and normalizing the campaign name, so one campaign doesn't arrive in analytics as several spellings. The social media coordinator and the email agent hold it, and it's deliberately not used on links between pages of your own site.
Copy link to headingNo shell for the specialists
Each specialist's tools/bash.ts disables the default-harness shell:
Left with a shell, a research-capable agent reaches for curl plus inline scripts to pull raw HTML into the sandbox and take it apart, which costs several times what web_fetch does. Removing the shell leaves web_fetch, which returns converted markdown in a single call that the source budget in each specialist's instructions can actually count. The file tools stay, because they're how an agent reads its own skills' references/ files.
Copy link to headingCustomize the team
There is no registry file: a tool's name is its filename, and a subagent's or skill's name is its directory name. Add a file to add a capability, delete it to remove one. The agent picks up changes to these files as you save.
| To change | Edit | Notes |
|---|---|---|
| Who is on the team | Add a directory under agent/subagents/ | The directory name is the tool name; the description in its agent.ts is all the lead sees when choosing |
| The lead's behavior | agent/instructions.md | Grounding, routing, brief writing, handing back |
| A specialist's craft | Its instructions.md and skills/ | A skill's frontmatter description decides when it loads |
| Voice, all surfaces | agent/lib/writing-quality/config.ts | Shared by every prose agent |
| Voice and banned words, one surface | references/banned-words.json in that <surface>-style skill | Add a surface by adding the skill folder and passing its name in that agent's tools/lint_against_style.ts |
| Approval gates | APPROVAL_REQUIRED_TOOLS in connections/notion.ts, DELETE_TOOLS / PUBLISH_TOOLS in typefully.ts, SEND_TOOLS / DESTRUCTIVE_TOOLS in resend.ts | The Notion copies are identical, so change them together |
| What the email agent can reach | ALLOWED_TOOLS in agent/subagents/email/connections/resend.ts | Prefer adding a name here over loosening a gate |
| Models | agent/agent.ts and each agent/subagents/<id>/agent.ts | Any Gateway model ID works, or run /model in the dev TUI |
Not using a surface? Delete its specialist; nothing references one by name, so removing the directory is the whole job.
| Not using | Delete |
|---|---|
agent/subagents/email/ | |
| Social | agent/subagents/social-media-coordinator/ |
| SEO | agent/subagents/seo/ |
| Slack | agent/channels/slack.ts |
Keep product-marketer, since it owns the brand context everything else reads.
For the full walkthrough, with recipes for adding a specialist, skill, tool, or connection, and the silent failures worth knowing about, see CUSTOMIZING.md in the repository.
Copy link to headingAdd a remote specialist
Specialists don't have to live in this repo. eve's remote agents let the lead delegate to an agent in its own deployment, with its own skills, connections, and release cycle. The filename is the tool name, and vercelOidc() handles deployment-to-deployment auth with no shared secret:
Set PAID_ADS_AGENT_URL in the project's environment variables, and the lead picks the specialist up from its description exactly as it does the local ones. The remote agent runs in its own deployment and never sees this team's conversation history, so the lead packs everything it needs into the call message, and the result comes back as a normal tool result. This suits work needing credentials or a release cycle you would rather keep out of this repo, such as ad platform access.
Copy link to headingTroubleshooting
Each item below lists a symptom, its cause, and the fix.
Copy link to heading@mentions don't get a response
Symptom: You @mention the bot in Slack, but it doesn't reply.
Cause: The bot isn't in the channel yet, or the deployment that registered the Slack trigger hasn't finished.
Fix: Invite the bot to the channel and confirm the deployment succeeded, then @mention it again. The Deploy button points Slack's events at the route the agent serves (/eve/v1/slack), so the path is already correct.
Copy link to headingThe email specialist can't send anything
Symptom: Email work fails or the agent reports no usable sending address or audience.
Cause: The Resend workspace has no verified sending domain, or no segment to target. The agent reads that state and picks from it, but cannot create a domain or verify DNS for you.
Fix: Verify a domain and create at least one segment in the Resend dashboard, then ask again.
Copy link to headingA send or delete pauses and nothing happens
Symptom: A Resend send, a Typefully delete, or a scheduled post appears to stall.
Cause: The call is gated on approval and is waiting for a decision. In Slack it renders as approve/deny buttons; in the dev TUI it renders as an approval prompt.
Fix: Approve or deny the pending call. To change which tools are gated, edit the approval lists in resend.ts, typefully.ts, and the notion.ts copies.
Copy link to headingConnectors work locally but fail in production
Symptom: The team works in the dev TUI, but the deployed bot can't reach Notion, Resend, Slack, or Typefully.
Cause: .env.local is local-only; the deployed app reads the Vercel project's environment.
Fix: Set NOTION_CONNECTOR, RESEND_CONNECTOR, SLACK_CONNECTOR, and TYPEFULLY_API_KEY in the project's production environment. The Deploy button sets them for you.
Copy link to headingA file you added doesn't show up
Symptom: A new tool, skill, or specialist isn't discovered.
Cause: eve walks agent/ at build time and only registers files that classify as an authored slot; a naming or placement problem drops the file from discovery.
Fix: Run npx eve info to see what eve discovered, and check .eve/discovery/diagnostics.json for why a file was skipped.
Copy link to headingThe bot sits on "Working…"
Symptom: A multi-step turn shows "Working…" with no visible progress.
Cause: The dev TUI hides logs by default, so a long but legitimate turn (a delegation is a full specialist session) looks stalled.
Fix: Show logs with npx eve dev --logs all, or run /loglevel all in the session.
Copy link to headingRelated resources
- Marketing team eve template and see the source
- eve documentation, including subagents, skills, and human in the loop
- Vercel Connect and Vercel Connect CLI
- Vercel Blob
- Vercel Sandbox
- Vercel AI Gateway
- Vercel OIDC
- Resend MCP server and Typefully
- eve Sanity Copilot template and eve Content Agent template
eve Software Factory Template
Four coding stations behind one orchestrator: triage, planning against a live checkout of your repo, implementation verified with your own checks, and independent review.