Vibecode Resend
track this build5 phases, 10 steps, beginner friendly0%Sending email is easy; getting it delivered is not. The product is IP reputation, feedback loops with every major mailbox provider, bounce handling and the DKIM/SPF/DMARC plumbing done right. A self-hosted MTA can work for one careful person, and the first spam-folder week teaches why people pay.
You are building a lean indie version of Resend. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Resend · indie build Not an email provider: deliverability is reputation, not code. This is the mail module that makes the provider replaceable: one interface, an SMTP driver, a hosted-API driver, a dev driver that writes .eml files, DNS records checked, bounces suppressed, and an honest page on self-hosting Postal if you insist. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 | one module your app imports | | Drivers | nodemailer for SMTP, fetch for a hosted API | the swap is an env var | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A sending domain you control DNS for** · free on a domain you own - Why: SPF, DKIM and DMARC records are what make mail deliverable. - Get it: Use a subdomain like mail.yourdomain.com so experiments never hurt your main domain's reputation. - [ ] **A hosted provider key (Resend, Postmark or similar)** · free tier; then usage-based - Why: The hosted driver. Free tiers cover thousands of emails a month. - Get it: Resend: resend.com > API Keys > Create; add and verify your domain. Postmark: account > Servers > API Tokens. - [ ] **SMTP credentials (optional)** (optional) · varies - Why: The SMTP driver, for self-hosting or another provider. - Get it: From any mail host. - [ ] **Test inboxes at Gmail and Outlook** · free - Why: Deliverability is judged by real providers. - Get it: Free accounts. ## Quick start ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Being a mail provider. Inbox placement is earned with volume history you do not have. - deliverability built on years of provider relationships - bounce, complaint and suppression handling - the dashboard, logs and webhooks - not being the person who debugs Gmail rejections at 2am If one of those is essential to you, that is the reason to keep paying for Resend, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Resend The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Do not build an email delivery service. Deliverability is reputation and provider relationships, not code. Build the consolation that makes the provider replaceable: a mail module your app talks to, with two implementations. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22. One mail module with an interface: send(to, subject, html, text) returning a message id or a typed error. Two drivers: a hosted provider over HTTP (Resend or Postmark, key in .env) and plain SMTP (nodemailer) for self-hosting. ### Phase 1 · The interface and the SMTP driver Build: the interface, the SMTP driver, and a local dev driver that writes every email to a folder as .eml and prints the path. Every email has a text part. Done when: sending in development writes a file you can open in a mail client, and a missing text part fails the send. Do not build yet: the hosted driver. ### Phase 2 · The hosted driver Build: the HTTP driver with a timeout, three retries on 5xx, no retries on 4xx, and the provider's message id recorded. Done when: a real send arrives, a 429 is retried, and a rejected address returns a typed error the caller can show. ### Phase 3 · The records Build: a script that prints the exact SPF, DKIM and DMARC records for your domain and checks them via DNS, failing loudly on any that are missing. Done when: the check passes against your live DNS and a real send scores clean on a mail-tester style check. ### Phase 4 · Bounces and suppression Build: a webhook receiver for the provider's bounce and complaint events, a suppression table, and a refusal to send to a suppressed address. Done when: a hard bounce suppresses the address and a later send to it is refused before it leaves. ### Phase 5 · Self-host option, honestly Build: a docs page on running Postal on a VPS with reverse DNS and a warm-up plan, and the config to point the SMTP driver at it. Do not automate the warm-up; write down what it costs in weeks. Done when: the SMTP driver sends through Postal and the README states the warm-up timeline. ### Out of scope (and why) - Being a mail provider. Inbox placement is earned with volume history you do not have. ### README must contain - The provider swap: one env var. - The plain sentence that self-hosting mail is a second job. ===== AGENTS.md ===== # Agent instructions · Resend indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, nodemailer for SMTP, fetch for a hosted API. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Resend Not an email provider: deliverability is reputation, not code. This is the mail module that makes the provider replaceable: one interface, an SMTP driver, a hosted-API driver, a dev driver that writes .eml files, DNS records checked, bounces suppressed, and an honest page on self-hosting Postal if you insist. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The interface and two local drivers send() with a typed result; dev writes files; SMTP sends for real. ### Steps 1. Define send(to, subject, html, text) returning an id or a typed error; require a text part ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` 2. Dev driver writing .eml files; SMTP driver over SMTP_URL ### Done when - [ ] Sending in dev writes a file that opens in a mail client - [ ] A missing text part fails the send ## Phase 2 · The hosted driver HTTP with timeout and correct retry rules. ### Steps 1. Resend driver: POST with a timeout, three retries on 5xx and 429, none on 4xx 2. Record the provider message id ### Done when - [ ] A real send arrives - [ ] A 429 is retried - [ ] A rejected address returns a typed error ## Phase 3 · The records SPF, DKIM, DMARC printed and verified against live DNS. ### Steps 1. A script that prints the exact records for your domain from the provider's requirements 2. Check them via DNS and fail loudly on any missing ### Done when - [ ] The check passes against live DNS - [ ] A real send scores clean on a mail-tester style check ## Phase 4 · Bounces and suppression Never send again to an address that hard-bounced. ### Steps 1. A webhook receiver for bounce and complaint events, verified by the provider's signature 2. A suppression table checked before every send ### Done when - [ ] A hard bounce suppresses the address - [ ] A later send to it is refused before leaving ## Phase 5 · Self-host option, honestly A documented Postal path with the warm-up cost stated. ### Steps 1. Write the docs page on running Postal with reverse DNS and a warm-up plan 2. Point the SMTP driver at it and send one test ### Done when - [ ] The SMTP driver sends through Postal - [ ] The README states the warm-up timeline in weeks ## Not in this build - Being a mail provider. Inbox placement is earned with volume history you do not have. ## After v1, if you want it - Templates with a preview page - A Postmark driver ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. dev, smtp or resend. MAIL_DRIVER=dev # Required. From address on your verified domain. MAIL_FROM='Your App' <hello@mail.yourdomain.com> # Optional · secret. Resend dashboard. RESEND_API_KEY=re_... # Optional · secret. For the SMTP driver. SMTP_URL=smtps://user:pass@host:465 # Optional. Where the dev driver writes .eml files. MAIL_DEV_DIR=./mail-out
You are building a lean indie version of Resend. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Resend · indie build Not an email provider: deliverability is reputation, not code. This is the mail module that makes the provider replaceable: one interface, an SMTP driver, a hosted-API driver, a dev driver that writes .eml files, DNS records checked, bounces suppressed, and an honest page on self-hosting Postal if you insist. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 | one module your app imports | | Drivers | nodemailer for SMTP, fetch for a hosted API | the swap is an env var | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A sending domain you control DNS for** · free on a domain you own - Why: SPF, DKIM and DMARC records are what make mail deliverable. - Get it: Use a subdomain like mail.yourdomain.com so experiments never hurt your main domain's reputation. - [ ] **A hosted provider key (Resend, Postmark or similar)** · free tier; then usage-based - Why: The hosted driver. Free tiers cover thousands of emails a month. - Get it: Resend: resend.com > API Keys > Create; add and verify your domain. Postmark: account > Servers > API Tokens. - [ ] **SMTP credentials (optional)** (optional) · varies - Why: The SMTP driver, for self-hosting or another provider. - Get it: From any mail host. - [ ] **Test inboxes at Gmail and Outlook** · free - Why: Deliverability is judged by real providers. - Get it: Free accounts. ## Quick start ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Being a mail provider. Inbox placement is earned with volume history you do not have. - deliverability built on years of provider relationships - bounce, complaint and suppression handling - the dashboard, logs and webhooks - not being the person who debugs Gmail rejections at 2am If one of those is essential to you, that is the reason to keep paying for Resend, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Resend The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Do not build an email delivery service. Deliverability is reputation and provider relationships, not code. Build the consolation that makes the provider replaceable: a mail module your app talks to, with two implementations. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22. One mail module with an interface: send(to, subject, html, text) returning a message id or a typed error. Two drivers: a hosted provider over HTTP (Resend or Postmark, key in .env) and plain SMTP (nodemailer) for self-hosting. ### Phase 1 · The interface and the SMTP driver Build: the interface, the SMTP driver, and a local dev driver that writes every email to a folder as .eml and prints the path. Every email has a text part. Done when: sending in development writes a file you can open in a mail client, and a missing text part fails the send. Do not build yet: the hosted driver. ### Phase 2 · The hosted driver Build: the HTTP driver with a timeout, three retries on 5xx, no retries on 4xx, and the provider's message id recorded. Done when: a real send arrives, a 429 is retried, and a rejected address returns a typed error the caller can show. ### Phase 3 · The records Build: a script that prints the exact SPF, DKIM and DMARC records for your domain and checks them via DNS, failing loudly on any that are missing. Done when: the check passes against your live DNS and a real send scores clean on a mail-tester style check. ### Phase 4 · Bounces and suppression Build: a webhook receiver for the provider's bounce and complaint events, a suppression table, and a refusal to send to a suppressed address. Done when: a hard bounce suppresses the address and a later send to it is refused before it leaves. ### Phase 5 · Self-host option, honestly Build: a docs page on running Postal on a VPS with reverse DNS and a warm-up plan, and the config to point the SMTP driver at it. Do not automate the warm-up; write down what it costs in weeks. Done when: the SMTP driver sends through Postal and the README states the warm-up timeline. ### Out of scope (and why) - Being a mail provider. Inbox placement is earned with volume history you do not have. ### README must contain - The provider swap: one env var. - The plain sentence that self-hosting mail is a second job. ===== AGENTS.md ===== # Agent instructions · Resend indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, nodemailer for SMTP, fetch for a hosted API. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Resend Not an email provider: deliverability is reputation, not code. This is the mail module that makes the provider replaceable: one interface, an SMTP driver, a hosted-API driver, a dev driver that writes .eml files, DNS records checked, bounces suppressed, and an honest page on self-hosting Postal if you insist. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The interface and two local drivers send() with a typed result; dev writes files; SMTP sends for real. ### Steps 1. Define send(to, subject, html, text) returning an id or a typed error; require a text part ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` 2. Dev driver writing .eml files; SMTP driver over SMTP_URL ### Done when - [ ] Sending in dev writes a file that opens in a mail client - [ ] A missing text part fails the send ## Phase 2 · The hosted driver HTTP with timeout and correct retry rules. ### Steps 1. Resend driver: POST with a timeout, three retries on 5xx and 429, none on 4xx 2. Record the provider message id ### Done when - [ ] A real send arrives - [ ] A 429 is retried - [ ] A rejected address returns a typed error ## Phase 3 · The records SPF, DKIM, DMARC printed and verified against live DNS. ### Steps 1. A script that prints the exact records for your domain from the provider's requirements 2. Check them via DNS and fail loudly on any missing ### Done when - [ ] The check passes against live DNS - [ ] A real send scores clean on a mail-tester style check ## Phase 4 · Bounces and suppression Never send again to an address that hard-bounced. ### Steps 1. A webhook receiver for bounce and complaint events, verified by the provider's signature 2. A suppression table checked before every send ### Done when - [ ] A hard bounce suppresses the address - [ ] A later send to it is refused before leaving ## Phase 5 · Self-host option, honestly A documented Postal path with the warm-up cost stated. ### Steps 1. Write the docs page on running Postal with reverse DNS and a warm-up plan 2. Point the SMTP driver at it and send one test ### Done when - [ ] The SMTP driver sends through Postal - [ ] The README states the warm-up timeline in weeks ## Not in this build - Being a mail provider. Inbox placement is earned with volume history you do not have. ## After v1, if you want it - Templates with a preview page - A Postmark driver ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. dev, smtp or resend. MAIL_DRIVER=dev # Required. From address on your verified domain. MAIL_FROM='Your App' <hello@mail.yourdomain.com> # Optional · secret. Resend dashboard. RESEND_API_KEY=re_... # Optional · secret. For the SMTP driver. SMTP_URL=smtps://user:pass@host:465 # Optional. Where the dev driver writes .eml files. MAIL_DEV_DIR=./mail-out
You are building a production product version of Resend. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Resend · product brief ## Problem Sending email is easy; getting it delivered is not. The product is IP reputation, feedback loops with every major mailbox provider, bounce handling and the DKIM/SPF/DMARC plumbing done right. A self-hosted MTA can work for one careful person, and the first spam-folder week teaches why people pay. ## Product outcome Email your app can send through any provider, with the reputation work owned by whoever you pay. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a VPS with a clean IP and reverse DNS - a domain with SPF, DKIM and DMARC records - patience for reputation warm-up ## Explicit non-goals for v1 - Being a mail provider. Inbox placement is earned with volume history you do not have. - deliverability built on years of provider relationships - bounce, complaint and suppression handling - the dashboard, logs and webhooks - not being the person who debugs Gmail rejections at 2am ## Success criteria - DNS check passes - Mail-tester clean - Suppression verified ===== BRIEF.md ===== # Build brief · Resend The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Do not build an email delivery service. Deliverability is reputation and provider relationships, not code. Build the consolation that makes the provider replaceable: a mail module your app talks to, with two implementations. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22. One mail module with an interface: send(to, subject, html, text) returning a message id or a typed error. Two drivers: a hosted provider over HTTP (Resend or Postmark, key in .env) and plain SMTP (nodemailer) for self-hosting. ### Phase 1 · The interface and the SMTP driver Build: the interface, the SMTP driver, and a local dev driver that writes every email to a folder as .eml and prints the path. Every email has a text part. Done when: sending in development writes a file you can open in a mail client, and a missing text part fails the send. Do not build yet: the hosted driver. ### Phase 2 · The hosted driver Build: the HTTP driver with a timeout, three retries on 5xx, no retries on 4xx, and the provider's message id recorded. Done when: a real send arrives, a 429 is retried, and a rejected address returns a typed error the caller can show. ### Phase 3 · The records Build: a script that prints the exact SPF, DKIM and DMARC records for your domain and checks them via DNS, failing loudly on any that are missing. Done when: the check passes against your live DNS and a real send scores clean on a mail-tester style check. ### Phase 4 · Bounces and suppression Build: a webhook receiver for the provider's bounce and complaint events, a suppression table, and a refusal to send to a suppressed address. Done when: a hard bounce suppresses the address and a later send to it is refused before it leaves. ### Phase 5 · Self-host option, honestly Build: a docs page on running Postal on a VPS with reverse DNS and a warm-up plan, and the config to point the SMTP driver at it. Do not automate the warm-up; write down what it costs in weeks. Done when: the SMTP driver sends through Postal and the README states the warm-up timeline. ### Out of scope (and why) - Being a mail provider. Inbox placement is earned with volume history you do not have. ### README must contain - The provider swap: one env var. - The plain sentence that self-hosting mail is a second job. ===== ARCHITECTURE.md ===== # Architecture · Resend ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 | one module your app imports | | Drivers | nodemailer for SMTP, fetch for a hosted API | the swap is an env var | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Interface | send() and errors | The contract | | Drivers | dev, smtp, resend | Add Postmark or SES | | Suppression | bounces and the table | Provider-managed lists later | | DNS check | records verification | Provider-specific requirements | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MAIL_DRIVER` · required · dev, smtp or resend. - `MAIL_FROM` · required · From address on your verified domain. - `RESEND_API_KEY` · optional, secret · Resend dashboard. - `SMTP_URL` · optional, secret · For the SMTP driver. - `MAIL_DEV_DIR` · optional · Where the dev driver writes .eml files. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Resend product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, nodemailer for SMTP, fetch for a hosted API. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ===== MILESTONES.md ===== # Delivery milestones · Resend Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The interface and two local drivers send() with a typed result; dev writes files; SMTP sends for real. ### Steps 1. Define send(to, subject, html, text) returning an id or a typed error; require a text part ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` 2. Dev driver writing .eml files; SMTP driver over SMTP_URL ### Done when - [ ] Sending in dev writes a file that opens in a mail client - [ ] A missing text part fails the send ## M2 · The hosted driver HTTP with timeout and correct retry rules. ### Steps 1. Resend driver: POST with a timeout, three retries on 5xx and 429, none on 4xx 2. Record the provider message id ### Done when - [ ] A real send arrives - [ ] A 429 is retried - [ ] A rejected address returns a typed error ## M3 · The records SPF, DKIM, DMARC printed and verified against live DNS. ### Steps 1. A script that prints the exact records for your domain from the provider's requirements 2. Check them via DNS and fail loudly on any missing ### Done when - [ ] The check passes against live DNS - [ ] A real send scores clean on a mail-tester style check ## M4 · Bounces and suppression Never send again to an address that hard-bounced. ### Steps 1. A webhook receiver for bounce and complaint events, verified by the provider's signature 2. A suppression table checked before every send ### Done when - [ ] A hard bounce suppresses the address - [ ] A later send to it is refused before leaving ## M5 · Self-host option, honestly A documented Postal path with the warm-up cost stated. ### Steps 1. Write the docs page on running Postal with reverse DNS and a warm-up plan 2. Point the SMTP driver at it and send one test ### Done when - [ ] The SMTP driver sends through Postal - [ ] The README states the warm-up timeline in weeks ## M6 · Operate (production only) Watch bounces and complaints like a provider would. ### Steps 1. Log every send with provider id and outcome; a daily bounce and complaint rate 2. Alert when the complaint rate exceeds 0.1 percent ### Done when - [ ] Rates are visible per day - [ ] A synthetic complaint spike triggers the alert ===== OPERATIONS.md ===== # Operations · Resend ## Backup The suppression table nightly. ## Restore Copy back before sending anything. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Bounce and complaint rates daily. ## Incident checklist A complaint spike: pause sending, find the list source, clean it. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] DNS check passes - [ ] Mail-tester clean - [ ] Suppression verified ## Launch constraint Do not market omitted Resend capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. dev, smtp or resend. MAIL_DRIVER=dev # Required. From address on your verified domain. MAIL_FROM='Your App' <hello@mail.yourdomain.com> # Optional · secret. Resend dashboard. RESEND_API_KEY=re_... # Optional · secret. For the SMTP driver. SMTP_URL=smtps://user:pass@host:465 # Optional. Where the dev driver writes .eml files. MAIL_DEV_DIR=./mail-out
# Resend · indie build Not an email provider: deliverability is reputation, not code. This is the mail module that makes the provider replaceable: one interface, an SMTP driver, a hosted-API driver, a dev driver that writes .eml files, DNS records checked, bounces suppressed, and an honest page on self-hosting Postal if you insist. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 | one module your app imports | | Drivers | nodemailer for SMTP, fetch for a hosted API | the swap is an env var | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A sending domain you control DNS for** · free on a domain you own - Why: SPF, DKIM and DMARC records are what make mail deliverable. - Get it: Use a subdomain like mail.yourdomain.com so experiments never hurt your main domain's reputation. - [ ] **A hosted provider key (Resend, Postmark or similar)** · free tier; then usage-based - Why: The hosted driver. Free tiers cover thousands of emails a month. - Get it: Resend: resend.com > API Keys > Create; add and verify your domain. Postmark: account > Servers > API Tokens. - [ ] **SMTP credentials (optional)** (optional) · varies - Why: The SMTP driver, for self-hosting or another provider. - Get it: From any mail host. - [ ] **Test inboxes at Gmail and Outlook** · free - Why: Deliverability is judged by real providers. - Get it: Free accounts. ## Quick start ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Being a mail provider. Inbox placement is earned with volume history you do not have. - deliverability built on years of provider relationships - bounce, complaint and suppression handling - the dashboard, logs and webhooks - not being the person who debugs Gmail rejections at 2am If one of those is essential to you, that is the reason to keep paying for Resend, and the README should say so rather than pretend.
# Build brief · Resend The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Do not build an email delivery service. Deliverability is reputation and provider relationships, not code. Build the consolation that makes the provider replaceable: a mail module your app talks to, with two implementations. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22. One mail module with an interface: send(to, subject, html, text) returning a message id or a typed error. Two drivers: a hosted provider over HTTP (Resend or Postmark, key in .env) and plain SMTP (nodemailer) for self-hosting. ### Phase 1 · The interface and the SMTP driver Build: the interface, the SMTP driver, and a local dev driver that writes every email to a folder as .eml and prints the path. Every email has a text part. Done when: sending in development writes a file you can open in a mail client, and a missing text part fails the send. Do not build yet: the hosted driver. ### Phase 2 · The hosted driver Build: the HTTP driver with a timeout, three retries on 5xx, no retries on 4xx, and the provider's message id recorded. Done when: a real send arrives, a 429 is retried, and a rejected address returns a typed error the caller can show. ### Phase 3 · The records Build: a script that prints the exact SPF, DKIM and DMARC records for your domain and checks them via DNS, failing loudly on any that are missing. Done when: the check passes against your live DNS and a real send scores clean on a mail-tester style check. ### Phase 4 · Bounces and suppression Build: a webhook receiver for the provider's bounce and complaint events, a suppression table, and a refusal to send to a suppressed address. Done when: a hard bounce suppresses the address and a later send to it is refused before it leaves. ### Phase 5 · Self-host option, honestly Build: a docs page on running Postal on a VPS with reverse DNS and a warm-up plan, and the config to point the SMTP driver at it. Do not automate the warm-up; write down what it costs in weeks. Done when: the SMTP driver sends through Postal and the README states the warm-up timeline. ### Out of scope (and why) - Being a mail provider. Inbox placement is earned with volume history you do not have. ### README must contain - The provider swap: one env var. - The plain sentence that self-hosting mail is a second job.
# Agent instructions · Resend indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, nodemailer for SMTP, fetch for a hosted API. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs".
# Build plan · Resend Not an email provider: deliverability is reputation, not code. This is the mail module that makes the provider replaceable: one interface, an SMTP driver, a hosted-API driver, a dev driver that writes .eml files, DNS records checked, bounces suppressed, and an honest page on self-hosting Postal if you insist. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The interface and two local drivers send() with a typed result; dev writes files; SMTP sends for real. ### Steps 1. Define send(to, subject, html, text) returning an id or a typed error; require a text part ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` 2. Dev driver writing .eml files; SMTP driver over SMTP_URL ### Done when - [ ] Sending in dev writes a file that opens in a mail client - [ ] A missing text part fails the send ## Phase 2 · The hosted driver HTTP with timeout and correct retry rules. ### Steps 1. Resend driver: POST with a timeout, three retries on 5xx and 429, none on 4xx 2. Record the provider message id ### Done when - [ ] A real send arrives - [ ] A 429 is retried - [ ] A rejected address returns a typed error ## Phase 3 · The records SPF, DKIM, DMARC printed and verified against live DNS. ### Steps 1. A script that prints the exact records for your domain from the provider's requirements 2. Check them via DNS and fail loudly on any missing ### Done when - [ ] The check passes against live DNS - [ ] A real send scores clean on a mail-tester style check ## Phase 4 · Bounces and suppression Never send again to an address that hard-bounced. ### Steps 1. A webhook receiver for bounce and complaint events, verified by the provider's signature 2. A suppression table checked before every send ### Done when - [ ] A hard bounce suppresses the address - [ ] A later send to it is refused before leaving ## Phase 5 · Self-host option, honestly A documented Postal path with the warm-up cost stated. ### Steps 1. Write the docs page on running Postal with reverse DNS and a warm-up plan 2. Point the SMTP driver at it and send one test ### Done when - [ ] The SMTP driver sends through Postal - [ ] The README states the warm-up timeline in weeks ## Not in this build - Being a mail provider. Inbox placement is earned with volume history you do not have. ## After v1, if you want it - Templates with a preview page - A Postmark driver
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. dev, smtp or resend. MAIL_DRIVER=dev # Required. From address on your verified domain. MAIL_FROM='Your App' <hello@mail.yourdomain.com> # Optional · secret. Resend dashboard. RESEND_API_KEY=re_... # Optional · secret. For the SMTP driver. SMTP_URL=smtps://user:pass@host:465 # Optional. Where the dev driver writes .eml files. MAIL_DEV_DIR=./mail-out
# Resend · product brief ## Problem Sending email is easy; getting it delivered is not. The product is IP reputation, feedback loops with every major mailbox provider, bounce handling and the DKIM/SPF/DMARC plumbing done right. A self-hosted MTA can work for one careful person, and the first spam-folder week teaches why people pay. ## Product outcome Email your app can send through any provider, with the reputation work owned by whoever you pay. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a VPS with a clean IP and reverse DNS - a domain with SPF, DKIM and DMARC records - patience for reputation warm-up ## Explicit non-goals for v1 - Being a mail provider. Inbox placement is earned with volume history you do not have. - deliverability built on years of provider relationships - bounce, complaint and suppression handling - the dashboard, logs and webhooks - not being the person who debugs Gmail rejections at 2am ## Success criteria - DNS check passes - Mail-tester clean - Suppression verified
# Build brief · Resend The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Do not build an email delivery service. Deliverability is reputation and provider relationships, not code. Build the consolation that makes the provider replaceable: a mail module your app talks to, with two implementations. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22. One mail module with an interface: send(to, subject, html, text) returning a message id or a typed error. Two drivers: a hosted provider over HTTP (Resend or Postmark, key in .env) and plain SMTP (nodemailer) for self-hosting. ### Phase 1 · The interface and the SMTP driver Build: the interface, the SMTP driver, and a local dev driver that writes every email to a folder as .eml and prints the path. Every email has a text part. Done when: sending in development writes a file you can open in a mail client, and a missing text part fails the send. Do not build yet: the hosted driver. ### Phase 2 · The hosted driver Build: the HTTP driver with a timeout, three retries on 5xx, no retries on 4xx, and the provider's message id recorded. Done when: a real send arrives, a 429 is retried, and a rejected address returns a typed error the caller can show. ### Phase 3 · The records Build: a script that prints the exact SPF, DKIM and DMARC records for your domain and checks them via DNS, failing loudly on any that are missing. Done when: the check passes against your live DNS and a real send scores clean on a mail-tester style check. ### Phase 4 · Bounces and suppression Build: a webhook receiver for the provider's bounce and complaint events, a suppression table, and a refusal to send to a suppressed address. Done when: a hard bounce suppresses the address and a later send to it is refused before it leaves. ### Phase 5 · Self-host option, honestly Build: a docs page on running Postal on a VPS with reverse DNS and a warm-up plan, and the config to point the SMTP driver at it. Do not automate the warm-up; write down what it costs in weeks. Done when: the SMTP driver sends through Postal and the README states the warm-up timeline. ### Out of scope (and why) - Being a mail provider. Inbox placement is earned with volume history you do not have. ### README must contain - The provider swap: one env var. - The plain sentence that self-hosting mail is a second job.
# Architecture · Resend ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 | one module your app imports | | Drivers | nodemailer for SMTP, fetch for a hosted API | the swap is an env var | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Interface | send() and errors | The contract | | Drivers | dev, smtp, resend | Add Postmark or SES | | Suppression | bounces and the table | Provider-managed lists later | | DNS check | records verification | Provider-specific requirements | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MAIL_DRIVER` · required · dev, smtp or resend. - `MAIL_FROM` · required · From address on your verified domain. - `RESEND_API_KEY` · optional, secret · Resend dashboard. - `SMTP_URL` · optional, secret · For the SMTP driver. - `MAIL_DEV_DIR` · optional · Where the dev driver writes .eml files. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Resend product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, nodemailer for SMTP, fetch for a hosted API. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone.
# Delivery milestones · Resend Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The interface and two local drivers send() with a typed result; dev writes files; SMTP sends for real. ### Steps 1. Define send(to, subject, html, text) returning an id or a typed error; require a text part ```sh mkdir mail && cd mail && git init && npm init -y && npm pkg set type=module && npm install nodemailer@6 mkdir -p mail-out && cp .env.example .env ``` 2. Dev driver writing .eml files; SMTP driver over SMTP_URL ### Done when - [ ] Sending in dev writes a file that opens in a mail client - [ ] A missing text part fails the send ## M2 · The hosted driver HTTP with timeout and correct retry rules. ### Steps 1. Resend driver: POST with a timeout, three retries on 5xx and 429, none on 4xx 2. Record the provider message id ### Done when - [ ] A real send arrives - [ ] A 429 is retried - [ ] A rejected address returns a typed error ## M3 · The records SPF, DKIM, DMARC printed and verified against live DNS. ### Steps 1. A script that prints the exact records for your domain from the provider's requirements 2. Check them via DNS and fail loudly on any missing ### Done when - [ ] The check passes against live DNS - [ ] A real send scores clean on a mail-tester style check ## M4 · Bounces and suppression Never send again to an address that hard-bounced. ### Steps 1. A webhook receiver for bounce and complaint events, verified by the provider's signature 2. A suppression table checked before every send ### Done when - [ ] A hard bounce suppresses the address - [ ] A later send to it is refused before leaving ## M5 · Self-host option, honestly A documented Postal path with the warm-up cost stated. ### Steps 1. Write the docs page on running Postal with reverse DNS and a warm-up plan 2. Point the SMTP driver at it and send one test ### Done when - [ ] The SMTP driver sends through Postal - [ ] The README states the warm-up timeline in weeks ## M6 · Operate (production only) Watch bounces and complaints like a provider would. ### Steps 1. Log every send with provider id and outcome; a daily bounce and complaint rate 2. Alert when the complaint rate exceeds 0.1 percent ### Done when - [ ] Rates are visible per day - [ ] A synthetic complaint spike triggers the alert
# Operations · Resend ## Backup The suppression table nightly. ## Restore Copy back before sending anything. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Bounce and complaint rates daily. ## Incident checklist A complaint spike: pause sending, find the list source, clean it. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] DNS check passes - [ ] Mail-tester clean - [ ] Suppression verified ## Launch constraint Do not market omitted Resend capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. dev, smtp or resend. MAIL_DRIVER=dev # Required. From address on your verified domain. MAIL_FROM='Your App' <hello@mail.yourdomain.com> # Optional · secret. Resend dashboard. RESEND_API_KEY=re_... # Optional · secret. For the SMTP driver. SMTP_URL=smtps://user:pass@host:465 # Optional. Where the dev driver writes .eml files. MAIL_DEV_DIR=./mail-out
$ choose a build depth, inspect the files, then open the complete pack in your agent
Because the first time a password reset lands in spam is the last time anyone argues about $20.
xdeliverability built on years of provider relationships
xbounce, complaint and suppression handling
xthe dashboard, logs and webhooks
xnot being the person who debugs Gmail rejections at 2am
Resend pricing
pro$20/mo · monthly flat · $240/yr
free tierThe free plan sends 3,000 emails a month capped at 100 a day from up to 3 domains.
verified 2026-09-04 · source ↗
Is Resend free?
The free plan sends 3,000 emails a month capped at 100 a day from up to 3 domains. Paid is Pro at $20/mo (checked 2026-09-04).
Vibecode Resend
Not really. Resend's value is not the code: . See the honest breakdown above.
How much does Resend cost?
Resend costs about $20/month (Pro, checked 2026-09-04), which is $240 per year.
What do I lose by replacing Resend?
Honestly: deliverability built on years of provider relationships; bounce, complaint and suppression handling; the dashboard, logs and webhooks; not being the person who debugs Gmail rejections at 2am. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Resend?
Yes: Postal (open-source mail delivery platform). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.