Hey Zeno — I saw your LinkedIn post, commented yes, and you sent me the beta access link. Spent a morning putting the CLI through its paces. Here's what I found.
Setup: macOS, Ghostty terminal, tested both human (TTY) and machine (piped/CI) modes.
Run resend emails send with no flags and it walks you through every field — From, To, Subject, Body — with smart suggestions. It pre-fills your verified domain for From and your known contacts for To. It feels like the CLI actually knows who you are. This is the best part of the entire product.
◇ From address (@updates.zero8.dev)
│ hello@updates.zero8.dev
◇ To address
│ hello@zero8.dev
◇ Subject
│ Hello
◇ Email body (plain text)
│ Test
✓ Email sent
JSON auto-enables when you pipe stdout. Human tables in the terminal, structured JSON in scripts — zero configuration needed. resend contacts list renders a clean ASCII table with Email, First Name, Last Name, Unsubscribed, ID columns. Pipe it to jq and it becomes a data source. This is exactly how it should work.
Red × for failures, green ✓ for success, ▲ for warnings. Error: API key is invalid, Error: Domain not found — short, red, immediately readable. The doctor command with its pass/warn/fail icons is a great health-check UX.
resend login opens the browser to the API keys page automatically, masks your key as you type, validates it against the API before saving, and tells you exactly where it stored it (~/.config/resend/credentials.json). First-run experience is smooth.
◇ Delete contact cli-test-2@example.com?
This cannot be undone.
│ Yes
✓ Contact deleted
"This cannot be undone." is the right copy. Consistent across all delete commands.
Cold start is ~25ms for local commands. API calls around 330ms. The CLI never feels slow.
Error: Not authenticated.
Run `resend login` to get started.
This is wrong. The API key is valid — the team profile just doesn't exist locally. The error should say something like "Team profile 'nonexistent' not found in credentials file." Telling an authenticated user they're not authenticated is confusing.
The docs say FIRST_NAME, LAST_NAME, EMAIL, and UNSUBSCRIBE_URL are reserved and cannot be created. But:
resend contact-properties create --key FIRST_NAME --type string
# → {"object":"contact_property","id":"c064..."} EXIT:0It succeeds. You end up with a duplicate FIRST_NAME property alongside the built-in one. This should return an error.
resend webhooks create --endpoint https://webhook.site/some-path --events email.sent
# → {"error":{"message":"Something went wrong","code":"create_error"}}Something went wrong is the least helpful error message possible. The error should say what the problem is — endpoint_unreachable, invalid_endpoint, or similar.
resend domains create --name updates.zero8.dev
# → Succeeds and creates a second domain with a new IDCreating a domain that already exists should either error or warn. Right now you can silently accumulate duplicate domain records.
Every single command ends with:
Update available: v1.2.1 → v1.2.2
Run: brew upgrade resend
Even error outputs. Even whoami. Once per session would be enough — showing it after every command trains users to ignore it.
Would be really useful on emails send, broadcasts send, contacts create — let me see what would happen without actually doing it. Especially valuable for batch sends.
Right now there are two modes: human summary and --json. There's no middle ground. A --verbose flag that adds timing, request IDs, and extra context would be useful for debugging without going full JSON.
The top-level resend domains --help mentions receiving as a domain capability, but resend domains update --help only exposes --tls, --open-tracking, and --click-tracking. There's no way to enable/disable receiving via the CLI.
I had already compiled a framework for what a great CLI looks like — covering things like TTY auto-detection, exit code contracts, error quality, idempotency, and progressive disclosure — while working on Smriti. Having that as a reference made it easy to know exactly what to look for and move quickly through the test cases.
The core is solid. Email sending, contact management, broadcast lifecycle, the human/machine output split — all work well. The interactive prompt UX is genuinely good and something I haven't seen done this cleanly in other CLIs.
The main things I'd prioritize:
- Fix the
RESEND_TEAMwrong error message — it's actively misleading - Enforce reserved property keys
- Improve the webhook error message
- Reduce the update banner frequency
Happy to answer questions or test specific flows. Good luck with the launch.














