# Resend Robot
**letter_opener for the [Resend](https://resend.com) gem.**
Intercepts Resend API calls in development, stores emails as JSON on disk, and provides a web UI to browse, preview, and simulate replies. Production code runs unchanged — same pipeline in dev and prod.
## Installation
Add to your Gemfile:
```ruby
gem "resend_robot", group: :development
```
Run `bundle install`, then run the install generator:
```bash
bin/rails generate resend_robot:install
```
This creates:
- `config/initializers/resend_robot.rb` — configuration (reply domain, mount path, etc.)
- `.claude/skills/resend-robot-read/skill.md` — Claude skill for reading dev emails
- `.claude/skills/resend-robot-send/skill.md` — Claude skill for simulating inbound emails
The generator is safe to re-run — it will ask before overwriting existing files.
Resend Robot automatically:
- Installs shims that intercept `Resend::Emails.send`, `Resend::Batch.send`, etc.
- Sets a dummy API key so `Resend::Mailer` doesn't raise
- Injects routes at `/dev/email` for the web UI
- Provides rake tasks for CLI interaction
## Web UI
Visit `http://localhost:3000/dev/email` to browse your dev mailbox:
- **Outbox** — all intercepted emails, newest first, with search/filter
- **Preview** — inline HTML rendering with sanitization (no iframes)
- **Reply** — simulate inbound email through your real webhook pipeline
## Configuration
```ruby
# config/initializers/resend_robot.rb
ResendRobot.configure do |config|
config.reply_domain = "reply.example.com" # required for inbound simulation
config.webhook_path = "/webhooks/resend" # default
config.dev_port = 3000 # auto-detected from action_mailer
config.open_in_browser = true # auto-open emails in browser
config.mount_path = "/dev/email" # change to mount UI elsewhere
end
```
### Bypass the shim
To send real emails via the Resend API in development:
```bash
RESEND_IN_DEV=1 bin/dev
```
### Suppress auto-open
```bash
OPEN_EMAILS=0 bin/dev
```
## Test Helpers
Resend Robot includes Minitest assertions for verifying emails in your test suite:
```ruby
class UserSignupTest < ActiveSupport::TestCase
include ResendRobot::TestHelper
test "sends welcome email on signup" do
clear_emails
User.create!(email: "alice@example.com")
assert_email_sent_to "alice@example.com"
assert_email_sent_to "alice@example.com", subject: /Welcome/
assert_equal 1, emails_sent.count
assert_equal "Welcome", last_email[:subject]
end
test "does not send email without signup" do
clear_emails
assert_no_emails_sent
end
end
```
### Available assertions
| Method | Description |
|--------|-------------|
| `clear_emails` | Clear all stored emails (call in setup) |
| `emails_sent` | All outbound emails as symbol-keyed hashes |
| `last_email` | Most recent outbound email |
| `assert_email_sent_to(addr, subject: nil)` | Assert email was sent to address, optionally matching subject (String or Regexp) |
| `assert_no_emails_sent` | Assert no emails were sent |
## JSON API
For E2E tests (Playwright, Capybara), the web UI also serves JSON:
```
GET /dev/email.json → array of all emails
GET /dev/email/:id.json → single email object
```
## Claude Skills
The install generator adds two [Claude Code](https://claude.ai/claude-code) skills:
| Skill | Description |
|-------|-------------|
| `/resend-robot-read` | List, search, and inspect dev emails from the CLI |
| `/resend-robot-send` | Simulate inbound emails through your webhook pipeline |
These let Claude interact with your dev mailbox conversationally:
- "Show me the last email sent" → runs `/resend-robot-read`
- "Simulate a reply to the welcome email" → runs `/resend-robot-send`
Skills are plain markdown files in `.claude/skills/` — customize them for your app (e.g., add app-specific models or webhook details).
## Rake Tasks
```bash
bin/rails resend_robot:outbox # List recent emails
bin/rails resend_robot:show[rl_xxx] # Show specific email
bin/rails resend_robot:receive[from,to,subject,body] # Simulate inbound
bin/rails resend_robot:reply[index,body] # Reply to Nth most recent
bin/rails resend_robot:clear # Clear all emails
```
## How It Works
Resend Robot monkey-patches 5 Resend gem methods in development:
| Method | Shimmed behavior |
|--------|-----------------|
| `Resend::Emails.send` | Stores email as JSON on disk |
| `Resend::Batch.send` | Stores each email individually |
| `Resend::Emails::Receiving.get` | Returns stored inbound email |
| `Resend::Emails::Receiving::Attachments.get` | Returns stub attachment |
| `Resend::Webhooks.verify` | Returns `true` (bypasses signature validation) |
Your production code (mailers, webhook controllers, inbound email handlers) runs unchanged. The shim operates at the Resend gem boundary — everything downstream is real.
## vs letter_opener
| | letter_opener | Resend Robot |
|-|---------------|--------------|
| Works with | SMTP delivery | Resend API client |
| Storage | Rendered HTML files | JSON (preserves all metadata) |
| Inbound simulation | No | Yes — POSTs to your real webhook endpoint |
| Test helpers | No | Yes (`assert_email_sent_to`, etc.) |
| JSON API | No | Yes (for E2E tests) |
| Web UI | Basic | Search, filter, reply simulation |
## Requirements
- Ruby >= 3.1
- Rails >= 7.0
- [resend](https://github.com/resendlabs/resend-ruby) gem >= 1.0
## Documentation
- [ARCHITECTURE.md](ARCHITECTURE.md) — System design, component overview, data flow diagrams
- [CONTRIBUTING.md](CONTRIBUTING.md) — Dev setup, running tests, project structure
- [CHANGELOG.md](CHANGELOG.md) — Release history
- [TODOS.md](TODOS.md) — Planned future work
## License
MIT
README-Quelldatei lesen