Skip to main content

Cursor + Claude Code guide

Let your coding agent
wire email, SMS and WhatsApp.

Connect the Unlimited Messaging MCP server to Cursor, Claude Code or Windsurf, paste one prompt, and the agent writes a notify module, sends a real test from your own accounts and checks that it arrived.

In a hurry? Summarize this article with an AI

The short answer

How do Cursor and Claude Code add email, SMS and WhatsApp to an app?

Give the agent two things: the Unlimited Messaging MCP server at https://mcp.unlimitedmessaging.app, so it can see your accounts and send a test message, and an API key in your .env, which the code it writes reads to call POST /message. One request then sends email, WhatsApp and SMS from your own accounts, on a free plan.

Coding agents are good at writing a fetch call and bad at guessing whether it worked. The MCP connection closes that gap: while it builds, the agent can list the mailboxes, WhatsApp numbers and Android phones you connected, send a test after you confirm, and read the delivery status, all through OAuth with no key pasted into the chat.

In your app itself, the code uses the REST API or the TypeScript and Python SDKs with an API key from the environment. Unlimited Messaging sends from accounts you already own: email from your Gmail or domain mailbox over IMAP/SMTP, WhatsApp from your number linked like WhatsApp Web, SMS from your Android phone's SIM. The free plan has no per-message fee; messages end with a short "Sent with UnlimitedMessaging.app" line.

It works the same in Cursor, Claude Code and Windsurf, and in any MCP client that supports remote servers with OAuth. None of them has an official partnership with Unlimited Messaging: they speak the open MCP protocol, and that is enough.

The MCP server in detail: MCP guide. In the Claude apps: Claude setup. Typed client for your code: the TypeScript SDK.

Illustration: an AI assistant wired to chat, email, calendar and contacts, with the headline “Your coding agent wires the messages”.

Why not hand-code it

Why is it slow to let an agent hand-code SMTP, Twilio and Meta?

The agent writes the integration in a minute. Then the real work starts: accounts, verification, platform rules and silent failures it cannot see.

  1. Email: the SMTP snippet breaks in production

    Agents love nodemailer, but Vercel blocks outgoing SMTP on port 25 and Supabase Edge Functions refuse ports 25 and 587. Switching to an email API means a verified domain and DNS records before anyone but you gets mail.

  2. SMS: a number, a registration, a fee per segment

    Twilio bills $0.0083 per segment in the US plus carrier fees, on a number you rent. Sending to US numbers from a regular long code also requires A2P 10DLC registration, with a $4.50 brand fee and vetting.

  3. WhatsApp: templates and a review queue

    Meta's Cloud API needs an approved template for any message outside the 24-hour customer service window, with review taking up to 24 hours and a price per template message. New accounts start at 250 unique recipients per 24 hours.

  4. The agent cannot check its own work

    Without a way to see the provider's side, the agent declares victory when the code compiles. Three providers also mean three keys in .env, three error formats and three webhook handlers to review.

Two ways in

MCP or API: which does the agent use, and when?

The MCP server is for the agent while it builds. The API key is for your app once it runs. Most projects use both.

MCP or API: which does the agent use, and when?
Hand-coded providersSMTP, Twilio, MetaUnlimited Messaging MCPIn the coding agentUnlimited Messaging APIIn your app's code
Who sendsYour app, through three providersThe agent, after you confirmYour app, through one endpoint
AuthenticationThree sets of credentialsOAuth in the browser, no key in filesOne API key from the environment
ChannelsOne provider per channelEmail, WhatsApp and SMSEmail, WhatsApp and SMS in one request
Can check deliveryOnly through each provider's dashboardYes, list-messages with statusesYes, GET /message and webhooks on paid plans
Cost to testTrials, then per messageFree planFree plan, footer line on messages

Provider fees from Twilio's and Meta's own pages, checked on 9 November 2026.

Step by step

From zero to a tested integration in five steps

  1. 01

    Connect the MCP server

    Claude Code: run the one-line claude mcp add command below. Cursor: add the block to .cursor/mcp.json. Windsurf: add it to mcp_config.json with serverUrl.

  2. 02

    Sign in through OAuth

    On the first tool call, your browser opens the Unlimited Messaging sign-in. No account yet? It is created on the free plan, with no credit card. In Claude Code, /mcp starts the sign-in.

  3. 03

    Connect your channels

    Ask the agent to connect your mailbox or your WhatsApp (it shows the QR code), and install the Android app on a phone with a SIM for SMS.

  4. 04

    Create an API key for the app

    In the dashboard's API section, create a key and put it in .env as UM_API_KEY yourself. The agent only needs the variable name.

  5. 05

    Paste the prompt

    The agent writes the module, wires it to your trigger, then sends a test to you through MCP and confirms the status.

Copy, paste

What prompt do you paste into Cursor or Claude Code?

Paste this into the agent chat, in Agent mode. It works in Cursor, Claude Code and Windsurf. Name the event that should trigger the message.

Prompt for Cursor, Claude Code or WindsurfPrompt
Add Unlimited Messaging to this project so it can send email, WhatsApp and SMS (docs: https://docs.unlimitedmessaging.app).

1. Create a server-side module, src/lib/notify.ts (or notify.py), that calls POST https://api.unlimitedmessaging.app/message with "Authorization: Bearer ${UM_API_KEY}" and the body { "to": { "phone": "<E.164>", "email": "<email>" }, "subject": "...", "text": "...", "channels": [{ "channel": "EMAIL" }, { "channel": "WHATSAPP" }, { "channel": "SMS" }] }. Throw on a non-2xx response with the body in the error.
2. Read UM_API_KEY from the environment. Add it to .env.example with an empty value, check that .env is in .gitignore, and never read, print or hardcode the real key.
3. Call notify() when <your event: signup, new order, failed payment...>, only from server code.
4. Use the unlimited-messaging MCP tools: list-accounts to see which channels I connected (drop the others from the call), then, after asking me, send-message to my own contact as a test, and list-messages to confirm its status.
Step 4 is what MCP adds: the agent checks your connected channels and verifies a real delivery instead of assuming the code works.
MCPClaude Code / Cursor / Windsurf
# Claude Code
claude mcp add --transport http unlimited-messaging https://mcp.unlimitedmessaging.app

# Cursor: ~/.cursor/mcp.json or .cursor/mcp.json
{ "mcpServers": { "unlimited-messaging": { "url": "https://mcp.unlimitedmessaging.app" } } }

# Windsurf: mcp_config.json
{ "mcpServers": { "unlimited-messaging": { "serverUrl": "https://mcp.unlimitedmessaging.app" } } }
Connecting the hosted server in each tool. All three sign in with OAuth on first use, so no key goes into these files.
notify.tsTypeScript
// src/lib/notify.ts (server-side only)
type Channel = 'EMAIL' | 'WHATSAPP' | 'SMS';

export async function notify(
  to: { phone?: string; email?: string },
  text: string,
  channels: Channel[],
  subject?: string,
) {
  const res = await fetch('https://api.unlimitedmessaging.app/message', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.UM_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to, text, subject,
      channels: channels.map((channel) => ({ channel })),
    }),
  });
  if (!res.ok) throw new Error(`notify failed: ${res.status} ${await res.text()}`);
  return res.json(); // one message per channel, sharing a dispatchId
}
The kind of module the agent should write: server-side, one request, the key from the environment, errors surfaced.

Safety

How do you keep the API key away from the agent and the repo?

The key belongs in .env, read by server code. Each tool gives you a way to keep the agent out of that file.

  1. MCP needs no key at all

    The agent's connection uses OAuth: tokens are stored by the client and can be revoked, in Claude Code from the /mcp menu. Nothing secret goes into .cursor/mcp.json or .mcp.json, so those files are safe to commit.

  2. Claude Code: deny reading .env

    Anthropic's settings docs show permission rules such as Read(./.env) and Read(./.env.*) under deny. Add them to .claude/settings.json and Claude Code cannot open the file.

  3. Cursor: .cursorignore, with its limits

    Files listed in .cursorignore are blocked from Agent, Tab, Inline Edit and @ mentions. Cursor's docs warn that terminal and MCP tools used by the agent are not covered, so do not rely on it alone.

  4. Server code only

    Never prefix the key with NEXT_PUBLIC_ or VITE_, and never import the notify module from client components. Review the agent's diff for that before you commit.

  5. Keep the confirmation on

    send-message sends real messages from your accounts. The server tells assistants to confirm each send, and your editor can ask before each tool call: do not auto-approve this one.

Replies

How does your app receive replies?

Replies always show up in the Unlimited Messaging inbox, and the agent can read them through list-messages while you build. For your app to react to them in production, you need webhooks, which come with the paid plans: Medium at €19 a month and Max at €29 a month.

Ask the agent for a webhook route, register its public URL on the Webhooks page of the dashboard, and subscribe to message.received (an inbound message) and message.status (sent, delivered, read, failed). Store the whsec_ secret as UM_WEBHOOK_SECRET in .env.

Deliveries are signed per the Standard Webhooks spec, with an HMAC-SHA256 over the raw body in the webhook-signature header. Answer 2xx within 10 seconds and process afterwards. Retries run for a little more than 3 days, so deduplicate on eventId.

message.receivedJSON
{
  "type": "message.received",
  "eventId": "whd_...",
  "occurredAt": "2026-11-09T10:00:01.000Z",
  "data": {
    "channel": "WHATSAPP",
    "message": {
      "text": "Yes, see you Monday!"
    }
  }
}
A message.received event, shortened. data.message carries the sender and the text, plus subject and attachments for an email.
verify-webhook.tsTypeScript
import { createHmac } from 'node:crypto';
import { Buffer } from 'node:buffer';

// Standard Webhooks signature check, before trusting the body.
export function isValid(rawBody: string, h: Headers, secret: string) {
  const id = h.get('webhook-id');
  const ts = h.get('webhook-timestamp');
  const sig = h.get('webhook-signature') ?? '';
  if (!id || !ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = createHmac('sha256', key)
    .update(`${id}.${ts}.${rawBody}`)
    .digest('base64');
  return sig.split(' ').some((s) => s === `v1,${expected}`);
}
The signature check to ask for. Read the raw body first, verify, and only then parse the JSON.

Limits

What are the limits, honestly?

Unlimited Messaging sends from personal accounts at a human pace: right for notifications and conversations, wrong for blasts.

  1. WhatsApp is unofficial

    Your number is linked as a device, like WhatsApp Web, outside Meta's Business Platform. Unsolicited messages get numbers banned. For official WhatsApp at scale, use Meta's Cloud API.

  2. Email follows your mailbox

    A personal Gmail account sends up to 500 emails a day. Newsletters and large volumes belong with an email service provider.

  3. SMS at Android's pace

    Android asks for confirmation beyond 30 SMS in 30 minutes, so the app spaces messages out. Texts count against your carrier plan, and the sending phone must be an Android.

  4. Free plan trade-offs

    Messages on Free end with "Sent with UnlimitedMessaging.app". Media, voice messages and webhooks need Medium or Max, which also remove the footer.

FAQ

Questions about messaging with coding agents

Ilian, senior software engineer

Written by

· Senior Software Engineer

A senior software engineer, he founds and runs several tech companies. He builds products around messaging APIs, automation and AI integration, and shares here what he learns while building.

View LinkedIn profileUpdated

Give your agent a mailbox, a number and a phone.

Create a free account, connect the MCP server in your editor, and paste the prompt.