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

# Send an email

> Everything you need to know about sending emails with Torpedo

## Basic send

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.torpedo.co.mz/api/v1/emails \
    -H "X-API-Key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "from": "Acme <hello@myapp.com>",
      "to": "user@example.com",
      "subject": "Welcome to Acme",
      "html": "<h1>Welcome!</h1>"
    }'
  ```

  ```js Node.js theme={null}
  const res = await fetch('https://api.torpedo.co.mz/api/v1/emails', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.TORPEDO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      from: 'Acme <hello@myapp.com>',
      to: 'user@example.com',
      subject: 'Welcome to Acme',
      html: '<h1>Welcome!</h1>',
    }),
  })
  const { data } = await res.json()
  ```

  ```python Python theme={null}
  import httpx

  res = httpx.post(
      'https://api.torpedo.co.mz/api/v1/emails',
      headers={'X-API-Key': TORPEDO_API_KEY},
      json={
          'from': 'Acme <hello@myapp.com>',
          'to': 'user@example.com',
          'subject': 'Welcome to Acme',
          'html': '<h1>Welcome!</h1>',
      },
  )
  data = res.json()['data']
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"os"
  )

  func main() {
  	payload, _ := json.Marshal(map[string]string{
  		"from":    "Acme <hello@myapp.com>",
  		"to":      "user@example.com",
  		"subject": "Welcome to Acme",
  		"html":    "<h1>Welcome!</h1>",
  	})

  	req, _ := http.NewRequest("POST", "https://api.torpedo.co.mz/api/v1/emails", bytes.NewBuffer(payload))
  	req.Header.Set("X-API-Key", os.Getenv("TORPEDO_API_KEY"))
  	req.Header.Set("Content-Type", "application/json")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()
  	fmt.Println(resp.Status)
  }
  ```

  ```java Java theme={null}
  import java.net.http.*;
  import java.net.URI;

  var body = """
      {"from":"Acme <hello@myapp.com>","to":"user@example.com",
       "subject":"Welcome to Acme","html":"<h1>Welcome!</h1>"}""";

  var request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.torpedo.co.mz/api/v1/emails"))
      .header("X-API-Key", System.getenv("TORPEDO_API_KEY"))
      .header("Content-Type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();

  var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
  System.out.println(response.body());
  ```

  ```csharp C# theme={null}
  using var client = new HttpClient();
  client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("TORPEDO_API_KEY"));

  var payload = new {
      from = "Acme <hello@myapp.com>",
      to = "user@example.com",
      subject = "Welcome to Acme",
      html = "<h1>Welcome!</h1>",
  };

  var response = await client.PostAsJsonAsync("https://api.torpedo.co.mz/api/v1/emails", payload);
  var result = await response.Content.ReadAsStringAsync();
  Console.WriteLine(result);
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": {
    "id": "01960000-0000-0000-0000-000000000000",
    "status": "queued"
  }
}
```

The response is `202 Accepted` — delivery is asynchronous.

***

## The `from` field

Accepts two formats:

```
hello@myapp.com
Acme <hello@myapp.com>
```

The domain (`myapp.com`) must be verified in your workspace. If you use a domain-scoped API key, the `from` domain must also match the key's domain.

For best deliverability and trust, prefer a monitored sender like `hello@` or `support@` instead of `no-reply@` whenever possible.

***

## `html` and `text`

Provide `html`, `text`, or both. If you send only `html`, Torpedo generates a plain-text version automatically.

Plain text is useful for:

* Email clients that don't render HTML
* Spam filters that check text vs HTML ratio
* Accessibility tools

If you want full control over the plain-text part, provide `text` explicitly:

```json theme={null}
"text": "Welcome! Visit https://myapp.com to get started."
```

To send an HTML-only email without a text part, set `text` to an empty string.

***

## Email status lifecycle

```
queued → sent → delivered
              ↘ bounced
              ↘ complained
              ↘ failed
```

Poll `GET /api/v1/emails/{id}` to check status, or use webhooks for real-time updates.

***

## Idempotency

Pass an `Idempotency-Key` header to prevent duplicate sends if your request is retried:

```bash theme={null}
curl -X POST https://api.torpedo.co.mz/api/v1/emails \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: order-123-welcome-email" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
```

If a request with the same key was already accepted, Torpedo returns the original `202` response without sending again. Keys are scoped to your workspace.

***

## Reply-to

Set a different address for replies:

```json theme={null}
{
  "from": "Acme <hello@myapp.com>",
  "replyTo": "support@myapp.com",
  ...
}
```

***

## Quota limits

Sending fails with `402` if you exceed your plan's monthly or daily quota:

```json theme={null}
{
  "errors": [{ "message": "Monthly email quota exceeded" }]
}
```

Check current usage via `GET /api/v1/workspace/usage`.
