- 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.
Steps
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 atresend._domainkey, and SPF records on asendsubdomain 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 fromonboarding@resend.dev, which delivers only to your Resend account's own address.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.
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_secretinstead of asking in the chat. A page opens in your browser on127.0.0.1, saying which file it will write. Paste the key there: the page checks that it starts withre_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.localand tells the agent the key name and four emoji, the same four the page shows. The agent never sees the value.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 thefromaddress for one on your verified domain.npm install resendsend.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.mjsNode 20.6 and later load the file with
--env-file; Next.js loads.env.localby itself, so a route handler or server action readsprocess.env.RESEND_API_KEYthe same way. Keep the call on the server, and never give the name aNEXT_PUBLIC_prefix, which would put the key in the browser. Whenerroris set, its message says what Resend refused, such as afromaddress on a domain that has not verified yet.claude — ~/acme-appBash(node --env-file=.env.local send.mjs)⎿Sent 4ef9a417-02e9-4d39-ad75-9611e0fcc33cResend 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. 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_KEYagain: 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
What the provider's own docs say, as of 31 October 2026.
- Resend: Create an API key
- Resend: API keys
- Resend: Add a domain
- Resend: DMARC
- Resend: Send test emails
- Resend: Send with Node.js
- Resend: How to handle a leaked API key
- Node.js: --env-file
Something here out of date? Open an issue on GitHub.
Try it
In a project's folder, run the command below, then ask the agent for something that needs a key.
npx offprompt init