Announcing CIMD support for MCP Client registration
Learn more

Should you use Resend MCP or Resend API for building AI Agents?

Nityashree Yadunath
Product Marketing Manager

TL;DR

  • Resend is the rare case where the auth model is identical on both paths. The hosted MCP server and the REST API share one authorization server at api.resend.com, the same two scopes (emails:send and full_access), and both accept a re_ API key as a Bearer token. Picking MCP does not force you into OAuth, and picking the API does not force you into static keys.
  • Capability coverage is close, not identical. The MCP server exposes the dashboard visual editor through compose-broadcast, compose-template, and get-tiptap-json-content, which have no REST equivalent. The REST API exposes Stop Automation and segment metrics, which the MCP server does not.
  • Resend's rate limit is 10 requests per second per team, shared across every API key on that team. Minting a key per tenant buys you isolation and revocation, not throughput.
  • Resend has no API versioning today. Neither surface gives you a pinned contract, so "the REST API is more stable" is a weaker argument here than it is for Salesforce or Slack.
  • Scalekit's Resend and Resend MCP connectors both authenticate with a Bearer token held in the token vault and resolved per connected account at call time, so the MCP versus API decision does not change your credential infrastructure, your tenant isolation, or your tool-call audit trail.

Your agent needs to send email through Resend. Maybe it is a trial-expiry notice, a support reply drafted from an inbound message, or a September product update going to one segment. Resend ships a hosted MCP server and a REST API that cover almost the same ground, built by the same team, backed by the same authorization server. The usual MCP-versus-API tiebreakers do not apply cleanly here, which is exactly why the choice is confusing. Here is what actually separates them.

What Resend MCP and the Resend API actually are

Both surfaces reach the same Resend platform. The difference is what sits between your agent and api.resend.com, and what shape the tool contract arrives in.

The Resend MCP server

Resend hosts its official MCP server at mcp.resend.com/mcp over Streamable HTTP. There is nothing to install and no local process to run. Connecting a client opens a browser window to log in to Resend and approve access over OAuth.

For clients that cannot run a browser login, such as a server, CI job, or headless agent, Resend documents passing a re_ API key as a Bearer token instead. The same open-source server also runs locally through npx -y resend-mcp in either stdio or HTTP mode.

The tool surface is organised into groups covering emails, received emails, templates, contacts, broadcasts, automations, events, domains, segments, suppressions, topics, contact properties, API keys, webhooks, logs, and the dashboard editor. Full details are in Resend's MCP server documentation.

The Resend REST API

The REST API is served from https://api.resend.com and authenticates with Authorization: Bearer re_xxxxxxxxx. It covers emails, batch sends, received mail, broadcasts, automations, events, templates, contacts, contact properties and imports, segments, topics, domains, suppressions, webhooks, logs, and OAuth grant management.

Two operational details matter for agents before you write a line of code. Every request must carry a User-Agent header; requests without one are rejected with a 403 and error code 1010. And there is no versioning system in place today, with calendar-based headers noted as a future plan. The full endpoint list lives in Resend's API reference.

Comparing them where it matters for agents

The four dimensions below are the ones that change your architecture. Capability coverage, the auth path, what you own in production, and where each surface wins.

What your agent can actually do

Coverage overlaps heavily because the MCP server is a first-party wrapper over the same API. The divergences are small, specific, and worth knowing before you commit.

Capability
Resend MCP
Resend REST API
Send transactional email with an idempotency key
Yes: send-email
Yes: POST /emails
Batch send up to 100 emails in one call
Yes: send-batch-emails
Yes: POST /emails/batch
Schedule, reschedule, and cancel an email
Yes
Yes
Read inbound email and download attachments
Yes
Yes
Create, send, and cancel broadcasts
Yes
Yes
Compose content in the dashboard visual editor
Yes: compose-broadcast, compose-template
No
Show agent presence in the editor
Yes: connect-to-editor
No
Create, update, and duplicate automations
Yes
Yes
Stop a running automation
No
Yes: Stop Automation
Retrieve segment metrics
No
Yes: Retrieve Metrics
Manage event definitions
Yes: one manage-events tool
Yes: six separate endpoints
Create, verify, and claim sending domains
Yes
Yes
Manage the suppression list, single and batch
Yes
Yes

Where the two surfaces genuinely diverge

The editor tools are the real MCP-only capability. get-tiptap-json-content reads a broadcast or template's current document, compose-broadcast and compose-template write structured content back, and the result is editable by a human in the Resend dashboard. There is no REST endpoint for this.

Going the other way, Stop Automation and segment metrics exist as REST endpoints with no matching MCP tool. If your agent's job is to halt a misfiring drip campaign or report contact counts per segment, that gap is a blocker rather than an inconvenience.

Tool shape is the difference you will feel every day

The MCP server is written for a model to read. manage-events collapses create, list, get, update, and remove behind a single action parameter. Several tools accept a Resend dashboard URL wherever an ID is expected. Destructive tools carry instructions in their descriptions telling the model to confirm with the user first.

The REST surface is written for a program to call. One endpoint, one operation, one predictable response shape, no model-facing prose to drift. For a deterministic pipeline that is an advantage. For a chat agent it is work you now have to do in your own prompt.

The auth path each one puts you on

This is where Resend breaks the pattern that holds for most tools in this series. There is no MCP-means-OAuth, API-means-keys split.

Resend implements OAuth 2.0 and 2.1 with PKCE required on every authorization code exchange. A client_id comes from pre-registration, a Client ID Metadata Document (CIMD), or Dynamic Client Registration (RFC 7591) at POST /oauth/register. Resend recommends CIMD over DCR, because one document gives a client a single identity across every install. Two scopes exist: emails:send for send-only routes and full_access for everything else. Omitting scope grants both, so pass it explicitly.

The same credentials work on both surfaces

The hosted MCP server is an OAuth-protected resource sitting in front of that same authorization server, and it also accepts a re_ API key as a Bearer token. The REST API accepts either an OAuth access token or an API key. Whichever surface you pick, the credential your infrastructure has to store, rotate, and revoke is the same object.

One caveat worth knowing before you design around it: Resend does not support RFC 8707 resource indicators yet. A resource parameter is accepted and ignored, so you cannot audience-bind a token to one surface.

The refresh-token trap that catches multi-worker agents

Resend OAuth access tokens are JWTs with a 900-second lifetime. Refresh tokens are opaque, rotate on every refresh, and carry a 60-day lifetime that resets on each use.

The failure mode is reuse detection. Replaying an old refresh token fails with invalid_grant and revokes the entire grant unless the rotation happened within the last minute. Two background workers refreshing the same tenant's grant concurrently is enough to disconnect that tenant. Resend's guidance: serialize refreshes per grant, and persist the new refresh token atomically with the rest of the response.

Access tokens are JWTs and cannot be revoked individually; revoking the refresh token revokes the grant. If your compromise story needs faster than 15 minutes, plan around that ceiling. More in handling token refresh for AI agents.

API keys, and why per-tenant keys are still worth it

Resend API keys are created with either full_access or sending_access permission, and a sending_access key can be pinned to a single verified domain. The token value is displayed exactly once at creation.

That makes a key per tenant a reasonable posture: least privilege by permission level, domain pinning where it applies, and clean revocation when a customer offboards. What it does not buy you is throughput. Resend's rate limit is 10 requests per second per team, applied across every API key on that team, so a noisy tenant still degrades a quiet one.

What you own in production

On the MCP path, Resend owns hosting, transport, and the tool schemas. You own the credential per tenant, the retry policy, and the fact that the tool surface can change under you when Resend ships. Scalekit's Resend MCP connector lists 94 tools; the upstream open-source server registers 103 at the time of writing. That drift is normal, and it is yours to absorb.

On the REST path you own the schema mapping, pagination, and error handling as well. What you do not gain, unlike most tools in this series, is a pinned contract. Resend has no versioning today, so the REST surface is stable by convention rather than by guarantee.

Rate limits, quotas, and retries

Every response carries ratelimit-limit, ratelimit-remaining, ratelimit-reset, and retry-after. Quota headers x-resend-daily-quota and x-resend-monthly-quota track sending volume, and exceeding either returns a 429 with daily_quota_exceeded or monthly_quota_exceeded.

Retries are safe on exactly two routes. Idempotency keys are supported on POST /emails and POST /emails/batch, held for 24 hours, and capped at 256 characters. The MCP send-email and send-batch-emails tools accept an idempotencyKey too, so this is not an MCP gap. Everything else, including broadcast sends and domain mutations, is not idempotent. An agent that retries a broadcast send on a timeout will send it twice.

When to use MCP, when to use the API

Both paths are viable for most Resend agents. These are the cases where the choice is not a coin flip.

Use Resend MCP when:

  • Your agent drafts marketing content that a human will review, because compose-broadcast and compose-template put the draft in the dashboard editor where a marketer can finish it
  • You are building an interactive assistant where the model benefits from tool descriptions that already encode workflow order and confirmation prompts
  • You want the destructive-action guardrails Resend wrote into the tool descriptions rather than writing them into your own system prompt
  • You are prototyping and want the full email platform behind one integration without mapping fifteen endpoint groups yourself

Use the Resend REST API when:

  • Your agent is a deterministic pipeline: a nightly trial-expiry job, a billing dunning sequence, a signup confirmation path where the same input must produce the same call every time
  • You need Stop Automation or segment metrics, neither of which the MCP server exposes
  • You are sending at volume and want to control batching against the 10-requests-per-second team limit yourself
  • You need response shapes that will not shift when the vendor rewrites a tool description, because a schema change in a fixed pipeline is an incident

The credential problem that exists on both paths

Both paths hand your agent a credential and stop there. Neither gives you a vault, rotation logic, revocation flow, or per-tenant isolation. That work is identical whichever surface you picked.

The shared API key failure mode

One full_access key in an environment variable works on day one. It also means every tenant's agent run shares one identity. Resend's request logs record endpoint, method, response status, user agent, and the request and response bodies; they do not carry your application's end-user identity, because the key is the identity.

So when a customer asks who sent the broadcast that went to the wrong segment, the answer from Resend's side is "your API key." The mapping back to a specific tenant and a specific agent run has to exist somewhere in your own infrastructure.

The N-credential problem

In a multi-tenant B2B agent, which is the norm rather than the exception, each customer brings their own Resend account, their own verified sending domain, and their own key or OAuth grant. Forty tenants means forty credentials to store encrypted, refresh before a 900-second access token expires, and revoke on offboarding.

Miss the revocation and a key issued eight months ago still sends mail from a customer you no longer serve. The agent does not decide to keep using it. It just does. Our breakdown of access control for multi-tenant AI agents walks through the isolation boundaries this requires.

Where Scalekit fits

Scalekit's Resend connector and Resend MCP connector both take a Bearer token, hold it in an AES-256 token vault, and resolve the right credential per connected account on every call. Credentials never enter the agent runtime or the model context.

The connected account is the unit of isolation. One connection defined once per environment, one connected account per tenant, and identifier selects which credential a tool call runs with. That model is the same on both connectors, which is the point: the MCP versus API decision stops being an auth decision.

Wiring Resend into an agent with Scalekit

The two paths differ only by which connection you point at. Everything below the connection name is shared.

Set up the connection

Create a Resend API key at the permission level your agent needs, then add it in the Scalekit dashboard under AgentKit, Connections, Create Connection. The connection name you choose is what you pass in code, and it must match the dashboard string exactly. This is the single most common integration error.

pip install scalekit-sdk-python langchain-openai
import os from scalekit.client import ScalekitClient scalekit_client = ScalekitClient( env_url=os.getenv("SCALEKIT_ENV_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit_client.actions # "resend" must match the connection name configured in the Scalekit dashboard account = actions.get_or_create_connected_account( connection_name="resend", identifier="acct_northwind", ) print(account.connected_account.status)

Retrieve the tools this tenant is authorized to call

Before the agent sees a single tool, it loads the surface that this connected account is authorized for. This is not a flat catalogue of everything the Resend connector can do; it is the subset bound to acct_northwind. list_scoped_tools returns the raw schemas if you are writing your own adapter, and actions.langchain.get_tools returns the same scoped list as LangChain StructuredTool objects.

tools = actions.langchain.get_tools( identifier="acct_northwind", connection_names=["resend"], page_size=100, # the Resend connector exposes more tools than the default page ) print([t.name for t in tools])

Run the agent loop

With the scoped tools bound to the model, the loop is ordinary LangChain. The credential resolution happens inside each tool invocation, keyed on the identifier you passed at discovery time. For a deeper look at how LangChain tool calling works under the hood, see our dedicated guide.

from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, ToolMessage tool_map = {t.name: t for t in tools} llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ HumanMessage( "Send the trial-expiry notice to ops@northwind.test from " "billing@acme.dev, then list the last 5 emails we sent so I can confirm." ) ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: result = tool_map[tc["name"]].invoke(tc["args"]) messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Call a tool deterministically instead

For the pipeline case, skip the model. execute_tool runs one named tool with one input payload and returns the result plus an execution_id you can correlate against your own logs. Note the idempotency_key, which makes this call safe to retry inside Resend's 24-hour window.

result = actions.execute_tool( tool_name="resend_email_send", tool_input={ "from": "Acme Billing ", "to": ["ops@northwind.test"], "subject": "Your Northwind trial expires in 3 days", "html": "

Renew before Friday to keep your workspace active.

", "idempotency_key": "trial-expiry/northwind-2026-09-08", }, identifier="acct_northwind", ) print(result.execution_id, result.data)

Take the MCP path with Mastra

Switching to Resend's MCP server changes the connection name and the tool prefix, nothing else. Tool names become resendmcp_send_email, resendmcp_list_broadcasts, and so on. Mastra has native MCP support, so the cleanest route in TypeScript is a Scalekit-generated MCP URL minted per tenant on your backend.

npm install @scalekit-sdk/node @mastra/core @mastra/mcp @ai-sdk/openai
# Backend (Python): mint a URL for one tenant, once per session inst_response = actions.mcp.ensure_instance( config_name="resend-broadcast-agent", user_identifier="acct_northwind", ) mcp_url = inst_response.instance.url

Each URL is pre-authenticated for a single tenant. A process-wide URL is safe only in a single-user demo; sharing it runs every request as that one account.

import { Agent } from '@mastra/core/agent'; import { MCPClient } from '@mastra/mcp'; import { openai } from '@ai-sdk/openai'; // Fetched from your backend for the authenticated tenant, never a shared constant const mcpUrl = await getMcpUrlForTenant(currentTenantId); const mcp = new MCPClient({ servers: { scalekit: { url: new URL(mcpUrl) }, }, }); const tools = await mcp.getTools(); const agent = new Agent({ name: 'resend_broadcast_agent', instructions: 'You draft Resend broadcasts. Always confirm the segment before sending anything.', model: openai('gpt-4o'), tools, }); const result = await agent.generate( 'Draft the September product update for the active-customers segment and leave it as a draft.', ); console.log(result.text); await mcp.disconnect();

Why a virtual MCP server matters for Resend agents

Resend is a wide connector. Handing all of it to every agent role is both a security problem and a cost problem, and virtual MCP servers exist to solve exactly that. As we explored in our post on why MCP is up to 32× more expensive than CLI, token overhead from wide tool surfaces is a real production cost.

The context tax of a wide connector

Scalekit's Resend connector lists 101 tools. Using the roughly 200-tokens-per-tool figure in Scalekit's own virtual MCP documentation, that is on the order of 20,000 tokens of tool definitions loaded before your agent does any work.

A drip-campaign agent needs perhaps six of those tools. A support-reply agent needs four. Scoping a server to the tools one agent role actually calls cuts that overhead sharply and removes the possibility of the model reaching for resend_domain_delete because it was in scope.

One definition, one session token per tenant

You define the virtual server once per agent role: which connections it draws from and which tools from each. That yields a static mcp_server_url. At runtime you mint a short-lived session token bound to one tenant and pass both to the agent as bearer auth.

For a multi-tenant email agent this collapses a lot of plumbing. One definition serves every customer, each customer connects once, and every run resolves to that customer's own Resend credential. Blast radius from a misbehaving run stays inside one tenant. Rate limiting virtual MCP servers is worth reading alongside this, given Resend's team-wide 10-requests-per-second ceiling.

Tool-call logs an auditor will accept

Every call through Scalekit is logged with full attribution: who authorized it, which agent ran it, and what came back. Failures are separated by source, so a Resend 429 is distinguishable from an expired credential, and logs export to your SIEM.

That is the layer Resend's own request logs cannot provide, because from Resend's side the credential is the identity. When a customer asks why a broadcast went out at 3am, you want the log row that names the tenant, the agent, and the execution_id. More on the shape of that trail in our post on agent tool observability.

Which one to build against

If your agent produces content a human will review, particularly broadcasts and templates, build against Resend MCP. The editor tools are a capability the REST API does not have, and the workflow hints in the tool descriptions are work you would otherwise write yourself.

If your agent is a deterministic sender, or it needs Stop Automation or segment metrics, build against the REST API. Fixed schemas and explicit control over batching beat model ergonomics in a pipeline that runs unattended.

Most production Resend agents will run both; the marketing assistant and the billing dunning job are not one system. The credential layer does not change across that split: one credential per tenant, resolved at call time, revocable, attributable. Build that once. Understanding the credential ownership patterns across agent tool-calling architectures will help you design this layer correctly from the start.

Build your Resend agent

Start with the Resend connector on Scalekit, or browse the full connector catalogue if your agent touches more than email. Related patterns are already scaffolded in the outbound prospecting agent, the support ticket automation agent, and the email to calendar agent templates. AgentKit tool calling and audit are free to start; see pricing for the details.

Building something with Resend and want another pair of eyes on the auth design? Join the Scalekit Slack community, or talk to us if you need an answer today.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.