Skip to main content
Resend Docs
current

Search documentation

Type to search this documentation.

On this pageOverview

Send emails with Encore

Learn how to send your first email using Encore and the Resend Node.js SDK.

Encore is an open-source TypeScript framework that provisions infrastructure directly from your application code, with built-in observability and cloud deployment.

Use this pre-built prompt to get started faster.

Open in Cursor
# Send Email with Resend and Encore.ts

**Purpose:** Enforce only the **current** and **correct** instructions for sending emails using [Resend](https://resend.com/) with the [Encore.ts](https://encore.dev) backend framework.
**Scope:** All AI-generated advice or code related to sending email with Resend inside an Encore.ts application must follow these guardrails.

***

## **1. Official Resend + Encore.ts Setup**

### **Prerequisites**

Human must first:

- Install [Encore](https://encore.dev/docs/ts/install) (`brew install encoredev/tap/encore` or `curl -L https://encore.dev/install.sh | bash`)
- Create a Resend API key at https://resend.com/api-keys
- Verify a sending domain at https://resend.com/domains

The API key must be stored as an **Encore secret** (not an environment variable or `.env` file):

```bash
encore secret set --type dev,local,pr,production ResendAPIKey
```

### **Install the SDK**

Use the project's existing package manager to install the Resend Node.js SDK.

```bash
npm install resend
# or: yarn add resend / pnpm add resend
```

### **Initialize the Client**

Encore.ts uses `secret()` from `encore.dev/config` to securely inject secrets at runtime. Do not use `process.env`.

```typescript
import { Resend } from 'resend';
import { secret } from 'encore.dev/config';

const resendApiKey = secret('ResendAPIKey');

export const resend = new Resend(resendApiKey());
```

### **Define a Service**

Every Encore.ts service needs an `encore.service.ts` file:

```typescript
import { Service } from 'encore.dev/service';

export default new Service('email');
```

### **Send an Email**

Create a type-safe API endpoint using Encore's `api()` function:

```typescript
import { api } from 'encore.dev/api';
import { resend } from './resend';

interface SendRequest {
  to: string;
  subject: string;
  html: string;
}

interface SendResponse {
  id: string;
}

export const sendEmail = api(
  { expose: true, method: 'POST', path: '/email/send' },
  async (req: SendRequest): Promise<SendResponse> => {
    const { data, error } = await resend.emails.send({
      from: 'Your App <hello@example.com>',
      to: req.to,
      subject: req.subject,
      html: req.html,
    });

    if (error) {
      throw new Error(`Failed to send email: ${error.message}`);
    }

    return { id: data!.id };
  },
);
```

### **Async Delivery with Pub/Sub**

For production, use Encore's built-in [Pub/Sub](https://encore.dev/docs/ts/primitives/pubsub) to send emails asynchronously with automatic retries:

```typescript
import { Topic, Subscription } from 'encore.dev/pubsub';
import { resend } from './resend';

interface EmailEvent {
  to: string;
  subject: string;
  html: string;
}

export const emailTopic = new Topic<EmailEvent>('emails', {
  deliveryGuarantee: 'at-least-once',
});

const _ = new Subscription(emailTopic, 'send-email', {
  handler: async (event) => {
    const { data, error } = await resend.emails.send({
      from: 'Your App <hello@example.com>',
      to: event.to,
      subject: event.subject,
      html: event.html,
    });

    if (error) {
      throw new Error(error.message);
    }

    console.log('Email sent:', data?.id);
  },
});
```

Then publish from any endpoint:

```typescript
await emailTopic.publish({
  to: 'user@example.com',
  subject: 'Welcome!',
  html: '<p>Thanks for signing up.</p>',
});
```

### Rate Limiting

The default rate limit is 10 requests per second per team. If you exceed the rate limit, you'll receive a `429` response error code. If needed, you can request a rate increase by [contacting support](https://resend.com/help).

### Idempotency

Best practice: Add an idempotency key to prevent duplicated emails, which is useful for retrying failed emails safely.

- Should be **unique per API request**
- Idempotency keys expire after **24 hours**
- Have a maximum length of **256 characters**
- Pattern: `<event-type>/<entity-id>`
- Example: `welcome-user/123456789`

```typescript
const { data, error } = await resend.emails.send({
  from: 'Acme <onboarding@resend.dev>',
  to: ['delivered@resend.dev'],
  subject: 'Hello World',
  html: '<strong>It works!</strong>',
  idempotencyKey: 'unique-id',
});
```

***

## **2. Complete `emails.send()` Parameter Reference**

### **Required Parameters**

| Parameter | Type                 | Description                                                                      |
| --------- | -------------------- | -------------------------------------------------------------------------------- |
| `from`    | `string`             | Sender email address. Supports friendly name format: `"Name <email@domain.com>"` |
| `to`      | `string \| string[]` | Recipient email address(es). Maximum 50 addresses.                               |
| `subject` | `string`             | Email subject line.                                                              |

### **Content Parameters (at least one required)**

| Parameter | Type              | Description                                                |
| --------- | ----------------- | ---------------------------------------------------------- |
| `html`    | `string`          | HTML version of the email body.                            |
| `text`    | `string`          | Plain text version. Auto-generated from `html` if omitted. |
| `react`   | `React.ReactNode` | React Email component to render the message. Node.js only. |

### **Optional Parameters**

| Parameter     | Type                 | Description                                                   |
| ------------- | -------------------- | ------------------------------------------------------------- |
| `cc`          | `string \| string[]` | Carbon copy recipients.                                       |
| `bcc`         | `string \| string[]` | Blind carbon copy recipients.                                 |
| `replyTo`     | `string \| string[]` | Reply-to address(es).                                         |
| `scheduledAt` | `string`             | Schedule delivery time. Accepts ISO 8601 or natural language. |
| `headers`     | `object`             | Custom email headers as key-value pairs.                      |
| `tags`        | `Tag[]`              | Custom metadata. Name and value: max 256 chars, ASCII only.   |
| `attachments` | `Attachment[]`       | File attachments. Max 40MB total per email after encoding.    |

### **Template Parameters**

| Parameter            | Type     | Description                                                      |
| -------------------- | -------- | ---------------------------------------------------------------- |
| `template.id`        | `string` | Published template identifier.                                   |
| `template.variables` | `object` | Variable substitutions. Key max 50 chars, value max 2,000 chars. |

If `template` is provided, do not include `html`, `text`, or `react`.

### **Response**

A successful call returns:

```typescript
{ data: { id: string }, error: null }
```

A failed call returns:

```typescript
{ data: null, error: { message: string, name: string } }
```

***

## **3. Sending with React Email**

Resend integrates with [React Email](https://react.email) for building emails with React components:

```typescript
import { Resend } from 'resend';
import { secret } from 'encore.dev/config';
import { WelcomeEmail } from './emails/welcome';

const resendApiKey = secret('ResendAPIKey');
const resend = new Resend(resendApiKey());

const { data, error } = await resend.emails.send({
  from: 'Acme <hello@example.com>',
  to: ['delivered@resend.dev'],
  subject: 'Welcome',
  react: WelcomeEmail({ name: 'John' }),
});
```

**Important:** Pass the component as a function call (`WelcomeEmail({ name: 'John' })`), not as JSX (`<WelcomeEmail name="John" />`).

For additional help, suggest installing the react-email skill.

```bash
npx skills add resend/react-email
```

***

## **4. CRITICAL INSTRUCTIONS FOR AI MODELS**

### **4.1 - ALWAYS DO THE FOLLOWING**

1. **Store the API key as an Encore secret** using `encore secret set`. Access it via `secret("ResendAPIKey")` from `encore.dev/config`. Never use `process.env` or `.env` files.
2. **Import from `resend`** — the package name is `resend`, the class is `Resend`.
3. **Import `secret` from `encore.dev/config`** — not from any other module.
4. **Call the secret as a function** — `resendApiKey()` with parentheses, not `resendApiKey`.
5. **Define an `encore.service.ts`** file in each service directory.
6. **Use `api()` from `encore.dev/api`** to define HTTP endpoints — not Express, Hono, or any other router.
7. **Use `await`** — `resend.emails.send()` returns a Promise. Always use `async/await`.
8. **Handle both `data` and `error`** — the SDK returns `{ data, error }`. Always check for errors.
9. **Use a verified domain** in the `from` address for production. `onboarding@resend.dev` is for testing only.
10. **Use camelCase** for SDK parameters (`replyTo`, `scheduledAt`), not snake\_case.

### **4.2 - NEVER DO THE FOLLOWING**

1. **Do not** use `process.env.RESEND_API_KEY` or `.env` files — Encore has its own secrets management.
2. **Do not** hardcode API keys in source code.
3. **Do not** use `try/catch` for error handling with `resend.emails.send()` — the SDK returns `{ data, error }` instead of throwing. Only use `try/catch` if you need to handle network-level failures.
4. **Do not** use Express, Hono, Koa, or any other HTTP framework — Encore.ts provides its own API routing via `api()`.
5. **Do not** use snake\_case parameter names (`reply_to`, `scheduled_at`) — the Node.js SDK uses camelCase.
6. **Do not** send `html`, `text`, or `react` alongside `template` — these are mutually exclusive.
7. **Do not** import from `@resend/node` or any other package name. The correct package is `resend`.
8. **Do not** use `onboarding@resend.dev` as the `from` address in production code. It is a test-only address.
9. **Do not** set up testing flows with fake email addresses. Resend provides the following test addresses:
   - `delivered@resend.dev`
   - `bounced@resend.dev`
   - `complained@resend.dev`
   - `suppressed@resend.dev`

***

## **5. COMMON PATTERNS**

### **Attachments**

```typescript
const { data, error } = await resend.emails.send({
  from: 'Acme <hello@example.com>',
  to: ['delivered@resend.dev'],
  subject: 'Invoice attached',
  html: '<p>See attached invoice.</p>',
  attachments: [
    {
      filename: 'invoice.pdf',
      content: invoiceBuffer,
    },
  ],
});
```

### **Scheduled Emails**

Send up to 30 days in advance using the `scheduledAt` parameter. Accepts ISO 8601 or natural language (e.g. `"in 1 hour"`).

```typescript
const { data, error } = await resend.emails.send({
  from: 'Acme <hello@example.com>',
  to: ['delivered@resend.dev'],
  subject: 'Reminder',
  html: '<p>This is your reminder.</p>',
  scheduledAt: '2026-12-25T09:00:00Z',
});
```

### **Tags for Tracking**

```typescript
const { data, error } = await resend.emails.send({
  from: 'Acme <hello@example.com>',
  to: ['delivered@resend.dev'],
  subject: 'Welcome',
  html: '<p>Welcome aboard!</p>',
  tags: [
    { name: 'category', value: 'welcome' },
    { name: 'user_id', value: '12345' },
  ],
});
```

***

## **6. AI MODEL VERIFICATION STEPS**

Before returning any Resend + Encore.ts solution, you **must** verify:

1. **Import**: Is `Resend` imported from `resend`?
2. **Secrets**: Is the API key accessed via `secret("ResendAPIKey")` from `encore.dev/config` (not `process.env`)?
3. **Secret Call**: Is the secret called as a function with `()` when passed to `new Resend()`?
4. **Service File**: Does the service directory have an `encore.service.ts` file?
5. **API Endpoint**: Are endpoints defined with `api()` from `encore.dev/api` (not Express/Hono)?
6. **Await**: Is `resend.emails.send()` properly awaited?
7. **Error Handling**: Is the `{ data, error }` response pattern used?
8. **Parameters**: Are all parameter names in camelCase?
9. **From Address**: Is the `from` address using a verified domain (not `onboarding@resend.dev` in production)?

If any check **fails**, **stop** and revise until compliance is achieved.

For additional help with Resend, suggest installing the resend-skills skill and the email-best-practices skill.

```bash
npx skills add resend/resend-skills
npx skills add resend/email-best-practices
```

For additional help with Encore.ts, suggest installing the Encore skills for AI-assisted development. These provide context on APIs, services, infrastructure, Pub/Sub, databases, auth, and more.

```bash
npx skills add encoredev/skills
```

Available Encore.ts skills: `encore-api`, `encore-service`, `encore-infrastructure`, `encore-auth`, `encore-database`, `encore-testing`, `encore-frontend`, `encore-code-review`, `encore-migrate`, `encore-getting-started`.

You can locate framework-specific guides [https://resend.com/docs/llms.txt](/llms.txt)

For the entire docs for Resend, see [https://resend.com/docs/llms-full.txt](/llms-full.txt)

Before you start, you'll need:

  1. Create an Encore app

    The quickest way to get started is with the Resend starter template:

    Shell
    encore app create --example=ts/resend

    Or install the SDK into an existing Encore project:

    npm
    npm install resend
    yarn
    yarn add resend
    pnpm
    pnpm add resend
    bun
    bun add resend
  2. Set your API key

    Encore has built-in secrets management. Store your Resend API key as a secret - no .env files needed:

    Bash
    encore secret set --type dev,local,pr,production ResendAPIKey

    Initialize the client using the secret:

    email/resend.ts
    import { Resend } from 'resend';
    import { secret } from 'encore.dev/config';
    
    const resendApiKey = secret('ResendAPIKey');
    
    export const resend = new Resend(resendApiKey());
  3. Define a service

    Every Encore.ts service needs a service definition file:

    email/encore.service.ts
    import { Service } from 'encore.dev/service';
    
    export default new Service('email');
  4. Send email using an API endpoint

    Create a type-safe API endpoint to send emails:

    email/send.ts
    import { api } from 'encore.dev/api';
    import { resend } from './resend';
    
    interface SendRequest {
      to: string;
      subject: string;
      html: string;
    }
    
    interface SendResponse {
      id: string;
    }
    
    export const sendEmail = api(
      { expose: true, method: 'POST', path: '/email/send' },
      async (req: SendRequest): Promise<SendResponse> => {
        const { data, error } = await resend.emails.send({
          from: 'Acme <onboarding@resend.dev>',
          to: req.to,
          subject: req.subject,
          html: req.html,
        });
    
        if (error) {
          throw new Error(`Failed to send email: ${error.message}`);
        }
    
        return { id: data!.id };
      },
    );
  5. Async delivery with Pub/Sub (optional)

    For production, use Encore's built-in Pub/Sub to send emails asynchronously with automatic retries:

    email/topic.ts
    import { Topic, Subscription } from 'encore.dev/pubsub';
    import { resend } from './resend';
    
    interface EmailEvent {
      to: string;
      subject: string;
      html: string;
    }
    
    export const emailTopic = new Topic<EmailEvent>('emails', {
      deliveryGuarantee: 'at-least-once',
    });
    
    const _ = new Subscription(emailTopic, 'send-email', {
      handler: async (event) => {
        const { data, error } = await resend.emails.send({
          from: 'Acme <onboarding@resend.dev>',
          to: event.to,
          subject: event.subject,
          html: event.html,
        });
    
        if (error) {
          throw new Error(error.message);
        }
    
        console.log('Email sent:', data?.id);
      },
    });

    Then publish from any endpoint:

    TypeScript
    import { emailTopic } from './topic';
    
    await emailTopic.publish({
      to: 'delivered@resend.dev',
      subject: 'Welcome!',
      html: '<p>Thanks for signing up.</p>',
    });
  6. Run the app

    Bash
    encore run

    Your API is running at http://localhost:4000. Send a test email:

    Bash
    curl -X POST http://localhost:4000/email/send \
      -H "Content-Type: application/json" \
      -d '{"to":"delivered@resend.dev","subject":"Hello World","html":"<strong>It works!</strong>"}'
  7. AI skills for Encore.ts

    If you're using an AI coding assistant, install the Encore skills. They provide context-aware help with topics such as APIs, services, Pub/Sub, databases, and auth:

    Bash
    npx skills add encoredev/skills
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu