Run your team's social presence without leaving Slack. You @mention the bot, and it drafts posts and threads for X, LinkedIn, Threads, Bluesky, and Mastodon through Typefully, schedules and manages the publishing queue, uploads media, reads post and follower analytics, and pulls briefs from Notion. Drafting flows freely, but anything with consequences pauses for your approval before it runs, rendered as buttons in the thread: scheduling or publishing a post, and deleting drafts, comments, or threads.
The bot is built on eve, a filesystem-first framework for durable backend agents from Vercel. Slack and Notion authenticate through Vercel Connect, and Vercel Blob and AI Gateway authenticate with your project's OpenID Connect (OIDC) token. The one credential you supply is your Typefully API key, set as the TYPEFULLY_API_KEY environment variable.
Deploy the template now, or read on for a deeper look at how it all works.
Typefully Social Media Agent eve Template
A Slack bot that runs your social posts through Typefully: drafts, threads, scheduling, analytics, and Notion drafting, with approvals for publishing and deletes.
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 Slack agent with the eve framework, using the Typefully social media agent template. Read the setup instructions at https://agent-resources.dev/typefully-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 headingSetup and deployment
Copy link to headingWhat you need before deploying
You need four accounts to deploy and run the agent:
- A Vercel account.
- A Slack workspace where you can install an app.
- A Typefully account with an API key (find it under Settings > API).
- A Notion workspace for briefs, source material, and long-form drafts.
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 everything the agent needs:
- A Slack connector, with its event trigger pointed at the route eve serves (
/eve/v1/slack) and the connector UID stored inSLACK_CONNECTOR. - A prompt for your Typefully API key, stored in
TYPEFULLY_API_KEY. - A Notion connector, with its UID stored in
NOTION_CONNECTOR. - A public Vercel Blob store for the asset and user-preference tools.
When the deployment finishes, your agent is live and wired up. All that's left is to invite it to a Slack channel and have team members sign in to Notion on first use.
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 in your workspace and @mention it to start a thread.
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 terminal UI.
Copy link to headingAuthorize Typefully and Notion
The two connections authenticate differently, and the difference matters.
Typefully authenticates with the static API key in TYPEFULLY_API_KEY. The connection is app-scoped: one shared Typefully workspace credential across all Slack users. The key is resolved on each connection attempt and never exposed to the model.
Notion is user-scoped. The first time a team member asks the agent to touch Notion, Vercel Connect prompts them to sign in in the browser. That sign-in is per user, so the agent reads and writes Notion as the signed-in person with their own permissions, and there's no shared integration token to manage.
On top of the auth model, consequential operations are gated on approval: the call pauses and renders approve/deny buttons in Slack before it runs.
| Connection | Operations that require approval |
|---|---|
| Typefully | Deleting drafts, comments, or threads (always); creating or editing a draft only when it schedules or publishes (publish_at is set) |
| Notion | Updating pages, moving pages, updating data sources, updating views |
| Vercel Blob | Deleting an asset, clearing a user's preferences |
Plain drafting, edits to copy, reads, queue listing, and analytics flow freely. Creating a new Notion page is also not gated, because drafting into Notion is the normal flow.
Copy link to headingLocal development
You can run the agent locally in a terminal UI (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, along with TYPEFULLY_API_KEY for the Typefully connection. 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 you can exercise the Typefully, Notion, Blob, and preference flows directly.
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 connection works from the TUI without affecting production.
Push changes to production with eve deploy:
The eve deploy command is the same thing as running vercel deploy --prod.
The template also uses Ultracite (a Biome preset) for linting and formatting.
pnpm check checks formatting and lint rules, pnpm fix auto-fixes what it can, and pnpm validate runs lint, typecheck, and eve's discovery diagnostics together.
Copy link to headingHow the social media agent works
The agent runs one loop: load the user's preferences and the right skills, ground the work in the real accounts, work in drafts, and run anything consequential past you first.
- @mention in Slack. A team member @mentions the bot or DMs it with a social task, like a thread for X, a LinkedIn version of a launch post, or a queue change. The Slack channel turns the message into a session and threads every reply.
- Load preferences and the matching skills. The agent calls
get_user_preferencesto apply that user's standing notes (a default social set, tone, or workflow), then loads the skills that match the task:writing-qualityfor any prose meant for humans, plus the style skill for each target platform (x-style,linkedin-style,threads-style,bluesky-style, ormastodon-style). Skills load on demand, so a skill enters the agent's context only when the task calls for it. - Ground everything in the real accounts. The agent lists the user's social sets and their connected accounts and reads existing drafts before creating or editing anything, so it never invents draft IDs, account names, or content. It pulls briefs and source material from Notion when you point it there. When a piece needs an outside fact, it delegates to the
researchersubagent, which runs web research in a fresh session and returns cited findings. - Work in drafts, schedule only on approval. Typefully drafts are created and edited freely; that's the agent's normal mode. It never sets
publish_at(which schedules or publishes a post) unless you explicitly ask, and the scheduling call itself still pauses for an approval button. - Draft long pieces into Notion. When the destination is Notion, or the piece is long-form, the agent creates it as a new Notion page and replies with the link. A page is easier to read than a long in-thread message.
- Check the draft before proposing it. Before proposing any social draft, the agent runs
lint_against_style, a deterministic check against the target platform's banned-words list, and fixes what it flags. On the final draft, it delegates to thereviewersubagent, which judges the draft against a writing-quality rubric in a fresh session and returns a verdict with concrete issues. The agent addresses them before proposing the draft in the thread.
Along the way, the Vercel Blob tools store any durable files (e.g., exported threads or images) and return URLs that the agent can reuse, and the preference tools persist each user's standing preferences across sessions. Once a week, a scheduled run pulls Typefully analytics and posts a digest to a Slack channel you choose.
Every credential in this loop is handled outside your code:
- Slack and Notion through Vercel Connect
- Vercel Blob and AI Gateway through the OIDC token
- Typefully through the
TYPEFULLY_API_KEYenvironment variable
No static keys live in the code.
Copy link to headingCode walkthrough
The whole agent is defined under agent/, and eve discovers each capability from the filesystem. A tool's, connection's, or channel's filename is its name, so there's no central registry to wire up. These are the pieces that matter.
Copy link to headingThe Slack surface
The Slack surface is one file:
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. Each human Slack user becomes a per-user principal, allowing the agent to act in Notion as that specific person and to link their preferences to them.
Copy link to headingDrafting and scheduling through Typefully
Typefully is a Model Context Protocol (MCP) connection. This connection is the agent's core: social sets, drafts, the publishing queue, media uploads, tags, comment threads, and post and follower analytics.
Two details carry the weight here:
getTokenmakes the connection app-scoped: it reads your API key fromTYPEFULLY_API_KEYon each connection attempt, eve sends it as a Bearer token, and the model never sees the URL or the key. One shared workspace credential covers every Slack user.- The
approvalpolicy gates by consequence, not by tool. Deletes always pause for an approve/deny decision, rendered as a Slack button. Creating or editing a draft pauses only when the call actually schedules or publishes, that is, whenpublish_atis set. The policy readspublish_atdefensively through areadPublishAthelper that never trusts the shape of the model-supplied input. Plain drafting and copy edits stay friction-free; scheduling, publishing, and deleting wait for a human.
The model discovers the available Typefully tools at runtime through the built-in connection__search and calls them by their qualified name, such as typefully__typefully_create_draft. The description field is the hint the model uses to decide when to reach for Typefully.
Copy link to headingReading and writing Notion
Notion follows the connection pattern, but with user-scoped auth:
connect() makes it user-scoped: each person signs in once, the per-user token is resolved before every call, and the model never sees it. Page updates, moves, and data-source changes are gated; page creation (notion-create-pages) is left ungated on purpose, because drafting a new page into Notion is the agent's normal flow. To gate creation too, add it to the list.
Copy link to headingPlatform craft as skills
Six skills sit under agent/skills/, each a folder with a SKILL.md and reference files:
writing-quality: generic prose rules for anything written for humans, from AI-tells to avoid through plain-English alternatives. The platform skills layer on top of it.x-style,linkedin-style,threads-style,bluesky-style, andmastodon-style: one per platform, each carrying that platform's voice, hooks, thread structure, length specs, and abanned-words.jsonreference.
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. Loading a skill only adds instructions to the turn, it never adds a new action the agent can take. The instructions tell the agent to load writing-quality plus the matching platform skill before drafting, and to load each target's skill in turn when adapting a piece for several platforms.
Copy link to headingA deterministic style lint
The style skills aren't only guidance the model reads. The lint_against_style tool turns each platform's banned-words list into a deterministic check the agent runs on every draft before proposing it:
The surface input is constrained to a fixed enum, so the resolved skill and file path can never be influenced by the caller. Each banned entry is regex-escaped and matched as a literal whole word, which closes off both regex syntax errors and catastrophic backtracking against caller-supplied text. Editing a platform's banned-words.json changes both what the model reads and what this tool enforces.
Copy link to headingThe weekly analytics digest
A schedule under agent/schedules/ turns the agent into a Monday reporter:
Every Monday at 14:00 UTC (Vercel evaluates cron in UTC, so adjust the hour for your timezone), the schedule runs the agent on that prompt. It pulls follower and post analytics for the first social set, builds two tables from only the fields the tools actually return, and delivers them through the post_analytics_report tool as Slack data_table blocks, with a fixed-width text fallback when a workspace rejects the block.
The delivery tool is hardened the same way as the rest of the template: the destination channel comes from the TYPEFULLY_ANALYTICS_CHANNEL environment variable, never from model input, so a prompt can't redirect the digest. The read-only analytics tools aren't approval-gated, so the scheduled session never stalls waiting for a human.
In development, schedules never fire on their cadence; trigger a run out of band with:
Copy link to headingFresh-context subagents
Two subagents live under agent/subagents/, and each runs in its own fresh child session: it inherits none of the root's skills, connections, or tools, so the root packs everything the subagent needs into the call message, and the result comes back as a structured tool result.
The researcher handles outside facts. When a post needs a statistic, a competitor detail, or a claim verified, the root delegates rather than reaching from memory. The researcher uses the framework's web_search and web_fetch tools and returns an outputSchema-typed result: findings where every claim carries at least one real source URL and a confidence level, plus gaps for anything it couldn't verify. The root weaves in only cited findings and surfaces the gaps to you instead of papering over them.
The reviewer handles the final pass. On a finished draft, the root sends the full text plus the target platform and any voice or audience context, and the reviewer judges it against the writing-quality rubric with fresh eyes, returning a ready or revise verdict with one entry per concrete issue: the severity, the rule broken, the offending quote, and a suggested fix. A reviewer that never saw the sources or the drafting reasoning catches the AI-tells and bloated wording that self-review reads right past.
Two implementation details make the reviewer self-sufficient: it carries its own copy of the writing-quality skill under agent/subagents/reviewer/skills/, and it declares its own sandbox.ts, because a subagent's sandbox doesn't inherit from the root and it needs one to read the skill's reference files.
Copy link to headingRemembering each user, agent/tools/*_user_preferences.ts
Three tools give the agent durable, per-user memory backed by Vercel Blob: get_user_preferences, save_user_preferences, and clear_user_preferences.
When a user states a standing preference ("always draft for the X and LinkedIn set", "keep threads under 8 posts"), the agent loads the current document, merges the note in, and saves the full result, so the file stays curated rather than append-only.
The isolation model is the part worth reading:
The Blob key is derived entirely from the framework-resolved principal (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 the reserved user-preferences/ prefix, so they can't be used as a side channel to another user's preferences. clear_user_preferences is irreversible and gated on approval.
Copy link to headingStoring assets in Vercel Blob
Five tools store and manage files in Vercel Blob: upload_asset, list_assets, get_asset_info, download_asset, and delete_asset. They're the agent's file layer: export a finished thread as Markdown, save an image before uploading it to a draft, or keep anything that should be reachable by URL.
Two of the tools are hardened for an agent that runs on model output. download_asset fetches only URLs whose host ends in .blob.vercel-storage.com, which stops the model from using it to reach arbitrary internal addresses. delete_asset is irreversible, so it's gated on human approval:
always() from eve/tools/approval displays a card with approve and deny buttons in Slack before the deletion runs, so the model can never delete a file on its own.
Copy link to headingBehavior and model
instructions.md is the agent's always-on system prompt.
eve prepends it to every model call, and it encodes the loop from the section above: load preferences and the right skills, ground work in the real social sets and drafts, draft freely but schedule and delete only on approval, draft long pieces into Notion, and lint plus review before proposing a draft.
The rule worth calling out is the scheduling contract: the agent never sets publish_at unless you explicitly ask to schedule or publish, and the approval gate in the Typefully connection backs that promise up at the tool layer.
The runtime configuration adds session management on top of the model:
Conversation history is compacted once it reaches 75% of the context window, and the per-session token limits cap runaway sessions; raise them if long content work hits the caps. The model id routes through the Vercel AI Gateway, which authenticates through the linked project, so there's no provider API key to set.
To use a different model, change this string (any Gateway model ID works) or run /model in the dev TUI. Each subagent sets its own model in its agent.ts.
Copy link to headingCustomize the agent
The agent picks up changes to these files as you save:
- Change the approval gates. Edit the
DELETE_TOOLSandPUBLISH_TOOLSlists and thepublish_atcheck inagent/connections/typefully.ts, and theAPPROVAL_REQUIRED_TOOLSlist inagent/connections/notion.ts, to change which MCP tools pause for a human decision. For example, addnotion-create-pagesto gate Notion page creation too. - Change the behavior. Edit
agent/instructions.md; it describes the whole workflow the agent follows. - Edit or add skills. Each folder under
agent/skills/holds aSKILL.md(give it adescriptionthat names when to use it) plus reference files, and eve discovers new skills automatically. The five*-styleskills each carry abanned-words.jsonthat thelint_against_styletool checks drafts against, so editing a list changes both the guidance and the enforcement. The reviewer keeps its own copy ofwriting-qualityunderagent/subagents/reviewer/skills/, so keep the two in step when you edit either. - Add or change tools. Files under
agent/tools/are tools, and the filename is the tool name. - Tune the analytics digest. Set
TYPEFULLY_ANALYTICS_CHANNELto the Slack channel id it should post to, change the day or time inagent/schedules/weekly-analytics.ts(the cron is evaluated in UTC), and editagent/tools/post_analytics_report.tsto change the report format.
To add another chat platform, add a channel file under agent/channels/ and a matching Vercel Connect connector. eve discovers the channel from the file the same way it discovers slack.ts, and Vercel Connect brokers the new platform's credentials, so the workflow, skills, tools, and connections stay unchanged.
Copy link to headingPair it with the content agent template
The eve content agent template is a full content assistant: a house voice and style skills for long-form surfaces this agent doesn't cover, like blog posts, release notes, and newsletters. Instead of merging all of that into this agent, you can deploy it as its own agent and let this one delegate to it through eve's remote agents feature.
Deploy the content agent template as its own Vercel project, then add a remote subagent file to this repo. The filename is the tool name, and vercelOidc() handles deployment-to-deployment auth with no shared secret:
Set CONTENT_AGENT_URL in this project's environment variables and mention the new subagent in agent/instructions.md so the agent knows when to hand off.
The remote agent runs in its own deployment with its own skills and connections, and it never sees this agent's conversation history, so this agent packs everything the writer needs into the call message. The result comes back as a normal tool result, the same shape as the local researcher and reviewer subagents.
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 headingTypefully calls fail with a missing key error
Symptom: Any Typefully operation errors with TYPEFULLY_API_KEY is not set.
Cause: The environment variable isn't present where the agent is running. .env.local is local-only; the deployed app reads the Vercel project's environment.
Fix: Set TYPEFULLY_API_KEY in the project's production environment (the Deploy button prompts for it), and run vercel env pull for local development. Find your key in Typefully under Settings > API.
Copy link to headingConnectors work locally but fail in production
Symptom: The agent works in the dev TUI, but the deployed bot can't reach Slack or Notion.
Cause: .env.local is local-only; the deployed app reads the Vercel project's environment.
Fix: Set SLACK_CONNECTOR and NOTION_CONNECTOR in the project's production environment. The Deploy button sets them for you.
Copy link to headingA schedule or delete pauses and nothing happens
Symptom: Scheduling a post, or deleting a draft, comment, or thread, 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 policy in agent/connections/typefully.ts and the APPROVAL_REQUIRED_TOOLS list in agent/connections/notion.ts.
Copy link to headingThe weekly digest never posts
Symptom: Monday comes and goes with no analytics digest in Slack.
Cause: TYPEFULLY_ANALYTICS_CHANNEL isn't set to a channel id, or you're expecting the schedule to fire in local development, where schedules never run on their cadence.
Fix: Set TYPEFULLY_ANALYTICS_CHANNEL in the project's environment and invite the bot to that channel. In dev, trigger a run manually with curl -X POST http://localhost:3000/eve/v1/dev/schedules/weekly-analytics. Note the cron is evaluated in UTC, so 14:00 UTC may not be the local time you expect.
Copy link to headingRelated resources
- Run a marketing team from Slack with eve
- Typefully social media agent eve template
- eve documentation
- eve content agent template
- Vercel Connect and Vercel Connect CLI
- Typefully
- Vercel Blob
- Vercel Sandbox
- Vercel AI Gateway
- Vercel OIDC
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.