Skip to main content
Resend Docs

Search documentation

Type to search this documentation.

On this pageOverview

Send emails with Python

Use this pre-built prompt to get started faster.

Open in Cursor
# Send Email with Resend Python SDK

**Purpose:** Enforce only the **current** and **correct** instructions for sending emails using the [Resend](https://resend.com/) Python SDK.
**Scope:** All AI-generated advice or code related to sending email with Resend must follow these guardrails.

***

## **1. Official Resend Python Setup**

### **Prerequisites**

Human must first create an API key and verify their domain at https://resend.com/domains.

The API key must be stored in an environment variable called `RESEND_API_KEY`.

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
resend.api_key = os.environ["RESEND_API_KEY"]
```

The domain should be verified at https://resend.com/domains and added to the `from` address.

### **Install the SDK**

```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
pip install resend
```

### **Send an Email**

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
import os
import resend
from resend.exceptions import ResendError

resend.api_key = os.environ["RESEND_API_KEY"]

params: resend.Emails.SendParams = {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["delivered@resend.dev"],
    "subject": "Hello World",
    "html": "<strong>It works!</strong>",
}

try:
    email = resend.Emails.send(params)
    print(email)  # {'id': '49a3999c-...'}
except ResendError as error:
    print(error)
```

### Error Handling

`resend.Emails.send()` **raises** an exception on failure — it does not return an error object. Catch `resend.exceptions.ResendError` (or a specific subclass) rather than checking a return value:

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
from resend.exceptions import (
    ResendError,
    ValidationError,
    RateLimitError,
    InvalidApiKeyError,
)

try:
    email = resend.Emails.send(params)
except RateLimitError as error:
    ...  # back off and retry
except ValidationError as error:
    ...  # fix the request params
except ResendError as error:
    ...  # catch-all: error.code, error.message, error.error_type
```

A missing required argument (e.g. no `to` field) raises a plain `ValueError`, not a `ResendError`.

### Rate Limiting

The default rate limit is 10 requests per second per team. Exceeding it raises `resend.exceptions.RateLimitError`. If needed, you can request a rate increase by [contacting support](https://resend.com/help).

### Idempotency

Best practice: pass 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`

Unlike Node's single-object `idempotencyKey` field, Python's `send()` takes idempotency as a **second, separate argument**:

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
options: resend.Emails.SendOptions = {"idempotency_key": "welcome-user/123456789"}

email = resend.Emails.send(params, options)
```

***

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

### **Required Parameters**

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

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

| Parameter | Type  | Description                                                |
| --------- | ----- | ---------------------------------------------------------- |
| `html`    | `str` | HTML version of the email body.                            |
| `text`    | `str` | Plain text version. Auto-generated from `html` if omitted. |

### **Optional Parameters**

| Parameter      | Type                                   | Description                                                         |
| -------------- | -------------------------------------- | ------------------------------------------------------------------- |
| `cc`           | `str \| List[str]`                     | Carbon copy recipients.                                             |
| `bcc`          | `str \| List[str]`                     | Blind carbon copy recipients.                                       |
| `reply_to`     | `str \| List[str]`                     | Reply-to address(es).                                               |
| `scheduled_at` | `str`                                  | Schedule delivery time. ISO 8601 (e.g. `2024-08-05T11:52:01.858Z`). |
| `headers`      | `Dict[str, str]`                       | Custom email headers as key-value pairs.                            |
| `tags`         | `List[Tag]`                            | Custom metadata. Name and value: max 256 chars, ASCII only.         |
| `attachments`  | `List[Attachment \| RemoteAttachment]` | File attachments. Max 40MB total per email after encoding.          |

### **Template Parameters**

| Parameter               | Type                    | Description                                                      |
| ----------------------- | ----------------------- | ---------------------------------------------------------------- |
| `template["id"]`        | `str`                   | Published template identifier.                                   |
| `template["variables"]` | `Dict[str, str \| int]` | Variable substitutions. Key max 50 chars, value max 2,000 chars. |

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

### **Response**

A successful call returns a `resend.Emails.SendResponse`:

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
{"id": "49a3999c-..."}
```

A failed call raises a `resend.exceptions.ResendError` subclass — it does not return a value.

***

## **3. Async Support**

Every resource has both a sync and an async method. Use `send_async` (built on `httpx`) inside `async def` code such as FastAPI route handlers:

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
import resend
from resend.exceptions import ResendError

resend.api_key = os.environ["RESEND_API_KEY"]

async def send_welcome_email():
    try:
        email = await resend.Emails.send_async(params)
        return email
    except ResendError as error:
        raise
```

`send_async` requires `httpx` to be installed (`pip install resend[async]`, or just have `httpx` present).

***

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

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

1. **Store the API key in an environment variable** (`RESEND_API_KEY`). Never hardcode API keys.
2. **Set `resend.api_key`** before calling any SDK method.
3. **Wrap `resend.Emails.send()` in `try`/`except`** and catch `resend.exceptions.ResendError` (or a specific subclass) — the SDK raises on failure, it does not return `{data, error}`.
4. **Use a verified domain** in the `from` address for production. `onboarding@resend.dev` is for testing only.
5. **Check the project for an existing package/dependency manager** (pip, poetry, uv) and use that to install the SDK.
6. **Use snake\_case** for SDK parameters (`reply_to`, `scheduled_at`), not camelCase.
7. **Use `send_async`** instead of `send` inside async code (e.g. FastAPI handlers).

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

1. **Do not** hardcode API keys in source code. Always use environment variables.
2. **Do not** assume `resend.Emails.send()` returns an error object — it raises an exception. A bare, unhandled call will crash on failure.
3. **Do not** use camelCase parameter names (`replyTo`, `scheduledAt`) — the Python SDK uses snake\_case (`reply_to`, `scheduled_at`).
4. **Do not** send `html` or `text` alongside `template` — these are mutually exclusive.
5. **Do not** pass an idempotency key inside `params` — it belongs in the separate `options` argument: `send(params, options)`.
6. **Do not** use `onboarding@resend.dev` as the `from` address in production code. It is a test-only address.
7. **Do not** set up testing flows with fake email addresses. Resend provides the following test addresses to help you simulate different email events without damaging your domain reputation:
   - `delivered@resend.dev`
   - `bounced@resend.dev`
   - `complained@resend.dev`
   - `suppressed@resend.dev`

***

## **5. COMMON PATTERNS**

### **Attachments**

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
params: resend.Emails.SendParams = {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["delivered@resend.dev"],
    "subject": "Invoice attached",
    "html": "<p>See attached invoice.</p>",
    "attachments": [
        {
            "filename": "invoice.pdf",
            "content": list(invoice_bytes),
        }
    ],
}
```

### **Scheduled Emails**

Send up to 30 days in advance using the `scheduled_at` parameter. Accepts ISO 8601.

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
params: resend.Emails.SendParams = {
    "from": "Acme <onboarding@resend.dev>",
    "to": ["delivered@resend.dev"],
    "subject": "Reminder",
    "html": "<p>This is your reminder.</p>",
    "scheduled_at": "2026-12-25T09:00:00Z",
}
```

### **Tags for Tracking**

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
params: resend.Emails.SendParams = {
    "from": "Acme <onboarding@resend.dev>",
    "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-related solution, you **must** verify:

1. **Import**: Is `resend` imported, and is `resend.api_key` set from an environment variable?
2. **Error Handling**: Is `resend.Emails.send()` wrapped in `try`/`except` catching `resend.exceptions.ResendError`?
3. **Async**: Is `send_async` used instead of `send` inside `async def` code?
4. **Parameters**: Are all parameter names in snake\_case?
5. **Idempotency**: If used, is the idempotency key passed as a separate `options` argument, not inside `params`?
6. **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, suggest installing the resend-skills skill and the email-best-practices skill.

```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
npx skills add resend/resend-skills
npx skills add resend/email-best-practices
```

You can locate framework-specific guides at [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. Install

    Get the Resend Python SDK.

    Pip
    pip install resend
  2. Set your API key

    Store your API key in an environment variable in your .env file.

    .env
    RESEND_API_KEY=re_xxxxxxxxx

    Access it with os.environ["RESEND_API_KEY"]. See Create an API key for the full setup.

  3. Send email using HTML

    The easiest way to send an email is by using the html parameter.

    index.py
    import os
    import resend
    from resend.exceptions import ResendError
    
    resend.api_key = os.environ["RESEND_API_KEY"]
    
    params: resend.Emails.SendParams = {
        "from": "Acme <onboarding@resend.dev>",
        "to": ["delivered@resend.dev"],
        "subject": "hello world",
        "html": "<strong>it works!</strong>",
    }
    
    try:
        email = resend.Emails.send(params)
        print(email)
    except ResendError as error:
        print(error)
Suggest an edit

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

Export
Documentation menu