---
title: Run a marketing team from Slack with eve
description: Team of five marketing agents built on eve. The lead routes work to specialists that write long-form into Notion, queue social posts in Typefully, run email in Resend, and audit for SEO, with approvals for anything irreversible.
url: /kb/guide/marketing-team-eve
canonical_url: "https://vercel.com/kb/guide/marketing-team-eve"
last_updated: 2026-07-26
authors: Ben Sabic
related:
  - /docs/connect
  - /docs/vercel-blob
  - /docs/agent-resources/vercel-plugin
  - /docs/cli
  - /docs/ai-gateway
  - /docs/cli/connect
  - /docs/sandbox
  - /docs/oidc
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

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](https://eve.dev/), a filesystem-first framework for durable backend agents from Vercel. Notion, Resend, and Slack authenticate through [Vercel Connect](https://vercel.com/docs/connect), and [Vercel Blob](https://vercel.com/docs/vercel-blob) and [AI Gateway](https://vercel.com/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.

## Quick 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:

### Vercel Plugin

The [Vercel Plugin](https://vercel.com/docs/agent-resources/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.

`npx plugins add vercel/vercel-plugin`

## Meet 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.

## Setup and deployment

### What you need before deploying

You need five things to deploy and run the team:

- A [Vercel account](https://vercel.com/signup).
  
- A Slack workspace where you can install an app.
  
- A Notion workspace for briefs and finished long-form pieces.
  
- A [Resend](https://resend.com/) account with a domain you can verify, if you want the email specialist.
  
- A [Typefully](https://typefully.com/) 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](https://vercel.com/docs/cli).

### Deploy 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.

### Connect 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.

### Authorize 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.

### Verify a Resend sending domain

Verify a sending domain and create at least one segment in the [Resend dashboard](https://resend.com/domains). 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.

### Local 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:

`git clone https://github.com/your-username/marketing-team-eve-template.git cd marketing-team-eve-template`

Then link it to the Vercel project you deployed and pull the environment variables:

`vercel link vercel env pull`

`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:

`pnpm dev`

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](https://www.ultracite.ai/), 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.

### How 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.

1. @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.
   
2. Ground in brand context and preferences. The lead calls `get_brand_context` for the team's shared picture of the product and `get_user_preferences` for 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.
   
3. 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.
   
4. 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.
   
5. Specialists find their own evidence and edit their own drafts. Each one uses `web_search` and `web_fetch` against a source budget for outside facts, runs `lint_against_style` on each draft to catch banned words, and edits in deliberate passes against the shared `writing-quality` rubric before handing work back.
   
6. 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
  

## Code 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.

### The Slack surface

The Slack channel lives in `agent/channels/slack.ts`:

`export default slackChannel({ credentials: slackCredentials, onAppMention: handleInbound, onDirectMessage: handleInbound, async onMessage(ctx, message) { if (message.author?.isBot) { return null; } if (!(await ctx.isSubscribed())) { return null; } if (!(await isSoleThreadParticipant(ctx, message))) { return null; } return handleInbound(ctx, message); }, threadContext: { since: "thread-root" }, });`

`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.

### The lead that routes, not writes

The lead's runtime configuration is minimal:

`import { defineAgent } from "eve"; export default defineAgent({ compaction: { thresholdPercent: 0.9 }, model: "anthropic/claude-opus-5", });`

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](https://vercel.com/docs/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.

### Specialists 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:

`export default defineAgent({ compaction: { thresholdPercent: 0.9 }, description: "Own email as a channel: take copy that already exists and make it work as email, then build " + "and run it in Resend. Reviews a draft for email fit (subject and preview text, one call to " + "action, scannable structure, a plain text version, link and image hygiene), then builds the " + "template or broadcast, picks the verified sending address, targets the segment, and reports " + "on what was delivered and opened. Sending a broadcast or an email pauses for approval. " + "Route long-form prose to the content marketer first and pass what came back to this agent.", model: "anthropic/claude-opus-5", });`

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.

### Gating the Resend connection

Resend is the connection with the most safeguards, because it's the one whose writes reach people directly. Three lists in `agent/subagents/email/connections/resend.ts` carry the weight:

`const SEND_TOOLS = ["send-broadcast", "send-email", "send-batch-emails"]; const DESTRUCTIVE_TOOLS = [ "remove-broadcast", "remove-template", "remove-contact", "remove-segment", "remove-topic", "update-contact-topics", ]; export default defineMcpClientConnection({ approval: ({ toolName }) => [...SEND_TOOLS, ...DESTRUCTIVE_TOOLS].some((tool) => toolName.includes(tool) ) ? "user-approval" : "not-applicable", auth: connect(resendConnector), description: "Resend: build and send email campaigns. Create, compose, and send broadcasts to a segment; " + "manage contacts, segments, topics, and topic subscriptions; read delivery status and logs; " + "and list verified sending domains with their DKIM and SPF records.", tools: { allow: ALLOWED_TOOLS }, url: "https://mcp.resend.com/mcp", });` Three details matter here: - `tools.allow` is 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_TOOLS` is the commit step. Mail cannot be recalled, so there's no unscheduled case worth letting through.    - `update-contact-topics` is 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.    ### Conditional gating on Typefully Typefully's connection in `agent/subagents/social-media-coordinator/connections/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. `export default defineMcpClientConnection({ approval: ({ toolName, toolInput }) => { if (DELETE_TOOLS.some((tool) => toolName.includes(tool))) { return "user-approval"; } if (PUBLISH_TOOLS.some((tool) => toolName.includes(tool))) { const publishAt = readPublishAt(toolInput); return typeof publishAt === "string" && publishAt.length > 0 ? "user-approval" : "not-applicable"; } return "not-applicable"; }, auth: { getToken: () => { const token = process.env.TYPEFULLY_API_KEY; if (!token) { throw new Error("TYPEFULLY_API_KEY is not set."); } return Promise.resolve({ token }); }, }, description: "Typefully: draft, schedule, and manage social posts across X, LinkedIn, Threads, " + "Bluesky, and Mastodon.", url: "https://mcp.typefully.com/mcp", });` 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. ### Marketing 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. ### Shared 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. ``export const userPreferencesKey = (principal: UserPrincipal): string | null => { if (principal?.principalType !== "user" || !principal.principalId) { return null; } const id = createHash("sha256").update(principal.principalId).digest("hex"); return `${USER_PREFERENCES_PREFIX}${id}.md`; };`` 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. ### Handoff 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. ### Tracked 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. ### No shell for the specialists Each specialist's `tools/bash.ts` disables the default-harness shell: `import { disableTool } from "eve/tools"; export default disableTool();` 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. ## Customize 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](https://vercel.com/ai-gateway/models) 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                                      |
| --------- | ------------------------------------------- |
| Email     | `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.

## Add a remote specialist

Specialists don't have to live in this repo. eve's [remote agents](https://eve.dev/docs/guides/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:

`import { defineRemoteAgent } from "eve"; import { vercelOidc } from "eve/agents/auth"; export default defineRemoteAgent({ url: () => process.env.PAID_ADS_AGENT_URL ?? "https://your-paid-ads-agent.vercel.app", description: "Plan and write paid search and social ads: audience, offer, and the copy variants to test. " + "Pass the campaign goal, the audience, the budget, and any brand constraints in the message.", auth: vercelOidc(), });`

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.

## Troubleshooting

Each item below lists a symptom, its cause, and the fix.

### @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.

### The 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](https://resend.com/domains), then ask again.

### A 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.

### Connectors 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.

### A 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.

### The 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.

## Related resources

- [Marketing team eve template](https://github.com/vercel-labs/marketing-team-eve-template)
  
- [eve documentation](https://eve.dev/docs), including [subagents](https://eve.dev/docs/subagents), [skills](https://eve.dev/docs/skills), and [human in the loop](https://eve.dev/docs/human-in-the-loop)
  
- [Vercel Connect](https://vercel.com/docs/connect) and [Vercel Connect CLI](https://vercel.com/docs/cli/connect)
  
- [Vercel Blob](https://vercel.com/docs/vercel-blob)
  
- [Vercel Sandbox](https://vercel.com/docs/sandbox)
  
- [Vercel AI Gateway](https://vercel.com/docs/ai-gateway)
  
- [Vercel OIDC](https://vercel.com/docs/oidc)
  
- [Resend MCP server](https://resend.com/docs/mcp-server) and [Typefully](https://typefully.com/)
  
- [eve Sanity Copilot template](https://github.com/vercel-labs/sanity-copilot-eve-template) and [eve Content Agent template](https://github.com/vercel-labs/eve-content-agent-template)