> ## Documentation Index
> Fetch the complete documentation index at: https://resend.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Send emails with Python

> Learn how to send your first email using the Resend Python SDK.

# 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](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](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](https://resend.com/docs/llms.txt)

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

## Prerequisites

Before you start, you'll need:

* A Resend [API key](/docs/create-an-api-key)
* A [verified domain](/docs/add-a-domain)

## Guide

**1. Install**

Get the Resend Python SDK.

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

**2. Set your API key**

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

```sh .env theme={"theme":{"light":"github-light","dark":"vesper"}}
RESEND_API_KEY=re_xxxxxxxxx
```

Access it with `os.environ["RESEND_API_KEY"]`. See [Create an API key](/docs/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.

```py index.py 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)
except ResendError as error:
    print(error)
```

## Examples

> **[Basic Send](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/basic_send.py)**
>
> Basic email sending

> **[Attachments](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/with_attachments.py)**
>
> Send emails with file attachments

> **[Templates](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/with_template.py)**
>
> Send emails using Resend hosted templates

> **[Scheduling](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/scheduled_send.py)**
>
> Schedule emails for future delivery

> **[Audiences](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/audiences.py)**
>
> Manage contacts and audiences

> **[Domains](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/domains.py)**
>
> Create and manage sending domains

> **[Inbound Webhooks](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/inbound.py)**
>
> Receive and process inbound emails

> **[Double Opt-in](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/double_optin_subscribe.py)**
>
> Double opt-in subscription flow

> **[Flask App](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/flask_app.py)**
>
> Full Flask web application

> **[FastAPI App](https://github.com/resend/resend-examples/blob/main/python-resend-examples/examples/fastapi_app.py)**
>
> Full FastAPI web application

> **[Django App](https://github.com/resend/resend-examples/tree/main/python-resend-examples/django_app)**
>
> Full Django web application
