GUIDES
  • Resend

Send email with Resend without giving your agent the API key

Make a Resend API key with Sending access, restricted to one verified domain, and let your coding agent ask for it with offprompt. You paste the key on a page in your browser, it is written to .env.local as RESEND_API_KEY, and the code the agent writes reads it from there. The key never enters the chat, and if it leaks anyway, it can only send mail from that one domain.

claude — ~/acme-app
Send a welcome email with Resend when someone signs up.
I'll send it from app/api/signup/route.ts with the resend SDK. That needs RESEND_API_KEY, which isn't in .env.local yet, so I'll ask you for it with offprompt.
offprompt - collect_secret (MCP)
⎿
file: .env.localkeys: RESEND_API_KEY Opened http://127.0.0.1:4123 in your browser. Waiting for you to write it…
✻Waiting on offprompt… (esc to interrupt)
offprompt127.0.0.1:4123
Claude Code asks offprompt for RESEND_API_KEY by name, and offprompt's page for that request opens in your browser. It checks the key as you type it, and its Get it link opens Resend's API Keys page.

Steps

  1. Verify a domain to send from

    In Resend's dashboard, add the domain on the Domains page. Resend recommends a subdomain named for the mail it sends, such as updates.example.com, rather than your root domain, so its sending reputation stays apart from your other mail. Resend then shows the DNS records to add at your DNS provider: a DKIM record at resend._domainkey, and SPF records on a send subdomain of it, a TXT and an MX for domains added before August 2026, CNAMEs for some added since. Copy them exactly as the domain's records tab shows them.

    A domain often verifies within 15 minutes, and DNS can take up to 72 hours. Once it does, add a DMARC record too: Resend calls it important for deliverability, and suggests starting with v=DMARC1; p=none;. To try Resend before then, send from onboarding@resend.dev, which delivers only to your Resend account's own address.

  2. Make a key that can only send, from that domain

    On the API Keys page, click Create API Key and name it after the project. Choose Sending access, not Full access, and under it pick the domain from step 1. A Full access key can create, read, update and delete everything in the account, its domains and other API keys included; a sending key restricted to one domain can only send mail as that domain. Resend shows the key once, so make it when you are ready to paste it in the next step: the page offprompt opens links here too.

  3. Let the agent ask for RESEND_API_KEY

    Ask for the task, not the key: “Send a welcome email with Resend when someone signs up.” When the agent needs the key, it calls offprompt's collect_secret instead of asking in the chat. A page opens in your browser on 127.0.0.1, saying which file it will write. Paste the key there: the page checks that it starts with re_ and is the length of a Resend key before writing it.

    collect_secret
    {
      "secrets": [{ "name": "RESEND_API_KEY", "provider": "resend" }],
      "reason": "The welcome email is sent with **Resend**.",
      "sink": { "kind": "dotenv", "path": ".env.local" }
    }

    offprompt writes RESEND_API_KEY=re_… to .env.local and tells the agent the key name and four emoji, the same four the page shows. The agent never sees the value.

  4. Send a test email

    Install Resend's Node SDK, which needs Node 20 or later, and send to delivered@resend.dev, one of Resend's test addresses: it reports a delivery without reaching a real inbox. Swap the from address for one on your verified domain.

    npm install resend
    send.mjs
    import { Resend } from 'resend'
    
    const resend = new Resend(process.env.RESEND_API_KEY)
    
    const { data, error } = await resend.emails.send({
      from: 'Acme <hello@updates.example.com>',
      to: ['delivered@resend.dev'],
      subject: 'It works',
      text: 'Sent with a key the agent never saw.',
    })
    
    // The SDK returns failures rather than throwing them.
    if (error) {
      console.error(`${error.name}: ${error.message}`)
      process.exit(1)
    }
    
    console.log(`Sent ${data.id}`)
    node --env-file=.env.local send.mjs

    Node 20.6 and later load the file with --env-file; Next.js loads .env.local by itself, so a route handler or server action reads process.env.RESEND_API_KEY the same way. Keep the call on the server, and never give the name a NEXT_PUBLIC_ prefix, which would put the key in the browser. When error is set, its message says what Resend refused, such as a from address on a domain that has not verified yet.

    claude — ~/acme-app
    Bash(node --env-file=.env.local send.mjs)
    ⎿
    Sent 4ef9a417-02e9-4d39-ad75-9611e0fcc33c
    Resend accepted the test email to delivered@resend.dev. Next I'll send the welcome email from the signup route the same way.
    The agent runs the script. Node reads RESEND_API_KEY from .env.local, so the key appears nowhere in the session: only the ID Resend gives the email.
  5. Replace the key when it may have leaked

    Resend keys do not expire. To replace one, make a new sending key as in step 2 and have the agent ask for RESEND_API_KEY again: offprompt's page marks the value the file already holds before replacing it. Once the app sends with the new key, remove the old one from the API Keys page, under its More options button. Removing a key revokes it at once.

What a leaked sending key can still do

Restricting the key limits the damage, but does not end it. Whoever holds a sending key for updates.example.com can send mail from that domain, which spends your quota and can hurt the domain's reputation with inbox providers. They cannot read the mail you have sent, add domains, or make keys of their own. That is the reason for a subdomain, too: a burned updates.example.com leaves the mail from example.com alone.

One key per app and environment keeps the next leak small as well: removing the key for a staging app leaves production sending.

Why not paste the key into the chat

A key pasted into the agent's chat goes to the model provider with every later request, stays in the session's transcript, and can be copied by the agent into a file it writes. Claude asked for my API key. Is it safe to paste it? explains what happens to it, and how offprompt keeps the agent out of the file afterwards.

Sources

Try it

In a project's folder, run the command below, then ask the agent for something that needs a key.

npx offprompt init

More guides