Help
How Plinth works
Plinth sits between your apps (and your agents) and the email providers you already pay for — Brevo, Resend, your in-house SMTP. Below is the shortest possible explanation of how that works. No jargon. Real examples you can copy. If something is still confusing, that's a bug — tell us and we'll fix the page.
The pieces
Four things to keep straight. Once you've got these, the rest is just URLs.
- Workspace
- Your team's account on Plinth. Holds your real provider keys, sealed in a vault.
- Provider
- An email rail you already pay for: Brevo, Resend, your in-house SMTP. Connect it once on /providers and Plinth seals the credential. The raw key never leaves the vault. Connect a second provider and Plinth treats them as a pool.
- App
- Anything that calls Plinth. Your website's transactional sender. Your marketing-newsletter cron. Your AI assistant in Cursor. A Python script. Each app gets its own API key, its own brand identity, and its own routing strategy — so marketing and transactional can ride different rails without forking your code.
- API key
- The bearer token an app uses to prove who it is. Looks like
plinth_production_xyz…. Shown once when you create the app, then only as a hash in our database.
What happens when an app sends a single email
Here's every step, in order, for one emails.send_template call:
- 1Your app says “send the
welcometemplate to[email protected]with{ firstName: "Ada" }.” - 2Plinth checks the API key — yes, this app belongs to your workspace, yes it's allowed to call
emails.*. - 3Plinth looks at the app's routing strategy (single, round-robin, or random) and picks a provider from your pool — say, Resend.
- 4Plinth checks the regulation buckets for Resend on this workspace. Daily quota OK? API rate limit OK? If not, fail over to the next provider in the pool. If everything's exhausted, the call fails with a clear error before any traffic leaves us.
- 5Plinth opens the safe, reads your Resend key, renders the template against the app's brand identity, and calls Resend. Gets a message ID back.
- 6Plinth writes a tamper-evident audit row (App X sent template “welcome” via Resend at 10:32) and a usage event. Returns the result to your app.
Why a pool, instead of just one provider
One email provider is fine until the day it isn't. The most common ways “the day it isn't” arrives:
- Free-tier daily quota exhausted. Brevo free is 300 sends/day. Resend free is 100 sends/day. Your one-provider setup silently drops everything for the rest of the day.
- API rate limit tripped. Resend caps at 10 req/s. A burst from a backfill kills your transactional flow with no backoff.
- Reputation drop on one IP / domain. You don't want to find out the morning of a launch.
- The provider has an incident. Status pages exist for a reason.
Plinth's answer is a per-app provider pool with a routing strategy. single is the default (one provider, like the products you're used to). round_robin rotates evenly so reputation builds across two rails. random uniformly samples — useful when you're A/B-testing inboxing across providers.
On every call, Plinth checks the regulation buckets before it dispatches. If the chosen provider would exceed its quota, the next one in the pool gets the send. The decision shows up in the audit row so you can review “why did this go through Brevo and not Resend” later.
Four ways your apps can use Plinth
Same key works on all four. Pick whichever fits the job.
From inside Cursor or Claude Desktop
When to use: composing templates, debugging deliverability, one-off resends from your laptop.
Paste this in ~/.cursor/mcp.json:
{
"mcpServers": {
"plinth": {
"url": "https://plinth.tools/mcp",
"headers": { "Authorization": "Bearer ${PLINTH_API_KEY}" }
}
}
}Restart Cursor. Now you can say “set the marketing app to round-robin between Brevo and Resend, and resend the welcome template to the addresses that bounced yesterday” and Cursor actually does it. It sees the email tools (send, compose, set_routing, ingest, etc.) and picks the right ones.
From your backend
When to use: your customer-facing product. Replaces the Brevo SDK + Resend SDK + your retry logic with one bearer token.
One HTTP call, no SDK install:
await fetch("https://plinth.tools/api/v1/call", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PLINTH_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "emails.send_template",
arguments: {
template: "welcome",
to: "[email protected]",
vars: { firstName: "Ada" },
},
}),
});That's the entire integration. The provider gets picked by the app's routing strategy, the regulation buckets get checked, the brand gets substituted, the audit row gets written — none of which is your code's problem.
From an AI agent in the cloud
When to use: customer support agents, lifecycle bots, anything that drafts and sends mail on behalf of your team.
A support bot that does real work:
# Customer support agent (Python)
template = await call("emails.compose_template", {
"goal": "Apologize for the outage on 2026-05-09 and explain credits.",
"audience": "Pro tier customers in EU",
})
await call("emails.send_template", {
"template": template["key"],
"to": "[email protected]",
"vars": { "credits_usd": 12 },
})The AI decides what to draft and who to mail. Plinth makes sure it stays inside its sandbox: only the tools you allow, only within the regulation buckets you set, every action recorded in the audit chain.
From a script or GitHub Action
When to use: CI jobs, “email everyone whose trial expires today” cron, “announce on release tag” workflows.
Pure curl, no dependencies:
curl -X POST https://plinth.tools/api/v1/call \
-H "Authorization: Bearer $PLINTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"emails.send_template","arguments":{"template":"trial_expiring","to":"$RECIPIENT"}}'A real example with four apps, all sending email
You connect Brevo and Resend once on /providers. Then you create four apps — each with its own routing strategy and brand identity:
App A — Transactional backend
Who: Your customer-facing product. Does: Sends welcome / receipt / password-reset emails. Strategy: round_robin across Brevo + Resend so reputation builds on both.
App B — Marketing newsletter cron
Who: A scheduled job. Does: Sends the weekly newsletter. Strategy: single (Brevo only — its higher daily quota fits the volume), separate brand identity from App A so unsubscribe links land on a different domain.
App C — Support agent in the cloud
Who: An AI that answers customer mail. Does: Composes apology / credit / clarification templates and sends them. Strategy: single (Resend) — separate provider so a hot bug here doesn't degrade transactional reputation.
App D — Cursor on my laptop
Who: You, the developer. Does: Composing templates, replaying yesterday's bounces, exploring the audit log. Strategy: single (Resend dev). Its allowedTools is locked down to emails.compose_template + emails.send_template + governance.audit_chain.
All four use the same Brevo and Resend keys. None of them sees the plaintext. Each gets the brand it should — App B's newsletter looks different from App A's receipts even though they live in one workspace.
A morning in the audit log might look like:
10:01 App A emails.send_template (welcome, via resend) 10:01 App A emails.send_template (receipt, via brevo) ← round_robin 10:03 App C emails.compose_template (apology, en-US) 10:03 App C emails.send_template (apology, via resend) 10:05 App B emails.send_template (newsletter, via brevo) ← single 10:05 App D emails.compose_template (welcome v2 draft)
You can revoke App D's key in one click without touching the others. App A keeps sending receipts. App B keeps shipping the newsletter. That's why you split apps even when they all do “send email” — different routing, different brand, different blast radius on revocation.
MCP vs REST — when to use which
You have two URLs. Same key works on both. Same things happen behind the scenes. Pick by what your client speaks:
https://plinth.tools/mcp
MCP
The protocol AI editors and agent frameworks understand.
Use from: Cursor, Claude Desktop, Cline, your custom agent loop, anywhere that asks for an “MCP server URL.”
POST https://plinth.tools/api/v1/call
REST
Plain HTTP and JSON. No protocol, no SDK.
Use from: your backend, scripts, GitHub Actions, edge functions, Cloudflare Workers, anything that can do fetch or curl.
They're literally the same dispatcher behind two envelopes. That's why one app key works on both — pick whichever your client speaks and don't overthink it.
Building an agent?
The full playbook — bootstrap JSON, Cursor/Claude config, self-registration steps, copy-paste curl, and FAQ — lives on /agents. Start with GET https://plinth.tools/api/v1/agent/bootstrap.
What's protected automatically
You don't configure any of this. Every send gets it for free:
Audited
Every send is written to a tamper-evident log you can export. Each row is hash-chained to the previous one — you'll spot any rewrite immediately.
Regulated
Token-bucket rate / quota gates per workspace × app × provider. TOS presets auto-applied on connect (Brevo 300/day, Resend 100/day + 10/s, …). Strictest rule wins.
Budget-checked
Set monthly caps per provider. Plinth blocks calls that would exceed them with a clear error before any money moves.
Scoped
Each app key only works in its workspace and only for the tools you allow. Defaults are sane; tighten any time.
Sealed credentials
Provider keys are encrypted with a per-workspace subkey (XChaCha20-Poly1305 + HKDF). The raw key leaves the system exactly once — at create time.
Idempotent
Retries on flaky networks won't double-send an email. Plinth dedupes by idempotency key, with provider-side message IDs surfaced back to you.
Frequently asked
Do my apps need to be deployed somewhere special?
Why route across multiple providers if I'm happy with one?
What if my API key leaks?
Can two apps share an API key?
allowedTools, brand, and revoking one integration without touching others.Can I limit what one app can do?
allowedTools list. Defaults to * (everything your workspace can reach). Set it to ["emails.send_template", "emails.compose_template"] if that's all the app should be able to do.