{"slug": "building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it", "title": "Building an outbound sales agent as a Claude Code plugin (and open-sourcing it)", "summary": "A developer in Tokyo built LeadAce, an outbound sales agent that runs as a Claude Code plugin and is open-sourced. The agent finds companies matching an ideal customer profile, writes personalized emails, sends them via Gmail, and categorizes rejections by reason. The architecture separates model decisions from code, with validation and data access enforced server-side, and supports both cloud and self-hosted editions.", "body_md": "I run a small software company in Tokyo. I am an engineer, not a salesperson. Cold outbound was the part of my job I kept avoiding. So I built an agent that does it for me. It runs as a Claude Code plugin, and the whole stack is open source. This post is about the architecture decisions that mattered.\n\nThe product is LeadAce ([leadace.ai](https://leadace.ai), [source](https://github.com/aitit-inc/leadace)). It is in Public Beta. It finds companies that match your ICP, reads each company's site, writes one email per company, and sends from your own Gmail. Then it collects the replies. And it turns every rejection into a structured reason: budget, timing, wrong buyer, or missing feature. That data changes the next round's targeting.\n\nThe honest reason: I already spend all day in Claude Code. I did not want to build one more web app with a chat box in it.\n\nLater I found a better reason. A Claude Code plugin runs on the user's own Anthropic plan, on their machine. I do not proxy the LLM calls. So I do not have to charge for them. The price only covers send volume and infrastructure. That is why the entry plan can be $29/mo. My users are developers who already pay for Claude Pro or Max. For them, \"bring your own agent runtime\" is a feature, not a limitation.\n\nThe plugin is only the UX layer. Everything stateful is a normal SaaS backend behind it.\n\nIf you have not built one: a plugin is a directory with skills (markdown instructions that become slash commands), an optional MCP server declaration, and scripts. Distribution is a git repo:\n\n```\nclaude plugin marketplace add aitit-inc/leadace\nclaude plugin install leadace@leadace\n```\n\nLeadAce ships skills like `/leadace`\n\n(onboarding: point it at your homepage), `daily-cycle`\n\n, `build-list`\n\n, `outbound`\n\n, `check-responses`\n\n, and `evaluate`\n\n. Each skill is a prompt that works like a program. It contains instructions, guardrails, and references to MCP tools and local scripts. `${CLAUDE_PLUGIN_ROOT}`\n\nresolves file paths inside the installed plugin. That sounds like a small thing. But you need it the first time a helper script inside your plugin has to run on someone else's machine.\n\nThe main design question for any agent product is this: what does the model decide, and what is code?\n\nOur split:\n\n`record_outreach`\n\n. It does not see the twelve validation rules behind it. The validation runs on the server. So a prompt injection hidden in some company's website cannot turn it off.The rule I ended up with: if a mistake causes a compliance or data problem, it must be code. If a mistake causes a quality problem, the model can do it, with review gates around it.\n\nThere is one more point I understood too late. This split is also the trust boundary. The agent reads random websites all day. A prospect's site is untrusted input by definition. So anything that touches the database or sends email has to be a fixed contract in code. It must not be something the model can be talked into.\n\nEvery table has a `tenant_id`\n\n. Every request runs in a Postgres transaction that starts with:\n\n```\nSET LOCAL ROLE app_rls\nSET LOCAL app.tenant_id = '<tenant>'\n```\n\n`app_rls`\n\nis a role with row-level security enforced. The policies filter on `app.tenant_id`\n\n. Route handlers still write normal filtered queries. RLS is not the query mechanism here. It is the thing that saves you on the day a handler forgets a `where`\n\n. Only the auth middleware bypasses RLS, because it has to resolve user → tenant before the tenant is known.\n\nThe whole setup is about a hundred lines. This product stores other people's prospect lists and email threads. For that risk, a hundred lines is very cheap insurance.\n\nThe backend has one switch, `LEADACE_EDITION`\n\n. It has two values: `cloud`\n\nand `self-hosted`\n\n.\n\nIn self-hosted mode, the Stripe endpoints (`/me/checkout`\n\n, `/me/portal`\n\n, `/stripe/webhook`\n\n) return 404, and every tenant resolves to an unlimited plan. The default is self-hosted. So a fresh deploy fails closed into the free mode. It cannot accidentally run billing without keys.\n\nThis is what makes the open-source story real. The hosted service and the self-hosted deploy are the same code. Hosted is just the edition where Stripe is on. The deploy target is Cloudflare Workers + Supabase. The free tiers of both cover solo and small-team volume. So self-hosting costs nothing.\n\nWhy give away an unlimited edition? Because my buyer is a developer. A developer asks \"what happens if this company disappears?\" before wiring their outbound pipeline to a SaaS. My answer: you deploy the same repo and keep going. That answer closes the objection better than any uptime page. (The license is a modified Apache 2.0.)\n\n`/leadace <your-homepage>`\n\nbecame the one command that sets up everything else.Cold outbound reply rates are in the single digits. The pitch is not \"10x replies\". The pitch is that the nos come back as structured reasons instead of silence. Sending is capped at a human pace per mailbox, on purpose. Recipients are limited to the US, Canada and Japan until per-country compliance rules ship. The plugin is verified on macOS with Claude Code today. Other runtimes are unverified.\n\nWe use LeadAce to sell LeadAce. So most of the rejection reasons in my dashboard are about my own product. Reading them is not fun. But it is the most useful screen in the product.\n\nCode: [github.com/aitit-inc/leadace](https://github.com/aitit-inc/leadace) · Product: [leadace.ai](https://leadace.ai) (Free tier, no card)\n\nIf you have questions about the plugin model, the RLS setup, or the edition switch, I am happy to go deeper in the comments.", "url": "https://wpnews.pro/news/building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it", "canonical_source": "https://dev.to/leadace/building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it-17ae", "published_at": "2026-08-13 11:45:10+00:00", "updated_at": "2026-08-13 12:17:50.631623+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-products", "ai-infrastructure"], "entities": ["LeadAce", "Claude Code", "Anthropic", "Gmail", "Postgres", "Stripe", "aitit-inc"], "alternates": {"html": "https://wpnews.pro/news/building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it", "markdown": "https://wpnews.pro/news/building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it.md", "text": "https://wpnews.pro/news/building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it.txt", "jsonld": "https://wpnews.pro/news/building-an-outbound-sales-agent-as-a-claude-code-plugin-and-open-sourcing-it.jsonld"}}