# Quickstart: send your first email

This guide takes you from a new account to a delivered email in about ten minutes. You create an API key, verify the domain you send from, send a message through the REST API and then find it in the dashboard.

## Before you start

- A Ha Tang account at [app.hatang.dev](https://app.hatang.dev) with the **Email** module turned on ([Businesses and modules](https://hatang.dev/help/getting-started/modules)).
- Access to the DNS settings of a domain you own, such as `shop.vn`.
- To create keys you need to be an owner or admin, or a member with the **Developer** access ([Team and roles](https://hatang.dev/help/account/employees-and-roles)).

## 1. Create an API key

1. In the dashboard, open **Settings → API Keys** (the same page is also under **Email → API Keys**).
2. Click **Create key**, give it a name such as `production`, and tick **Send** under Email. That grants the `email:send` scope, which is all you need to send.
3. Optionally pick a **Sending domain** to lock the key to one domain.
4. Copy the key. It starts with `ht_` and is shown only once.

Store it as an environment variable on your server:

```bash
export HATANG_API_KEY="ht_123..."
```

> **Caution**
>
> API keys belong on your server. Never put one in browser JavaScript or a mobile app bundle. See [Authentication](https://hatang.dev/docs/guides/getting-started/authentication.md) for scopes and restricted keys.

## 2. Verify a sending domain

Ha Tang only sends from domains you have proved you own.

1. Open **Email → Domains** and add your domain, for example `shop.vn`.
2. Add the DNS records Ha Tang shows you at your DNS provider.
3. Wait for the domain to show as **Verified**.

The [Domains guide](https://hatang.dev/docs/guides/email/domains.md) explains each record, how long verification takes and how to fix a domain that stays pending.

If you send from a domain that is not verified, the API answers `403` with `name: "validation_error"` and a message such as `The shop.vn domain is not verified. Please, add and verify your domain first.`

## 3. Send an email

Send a `POST` request to `https://api.hatang.dev/emails`. The `from` address must be on your verified domain. `to` takes one address or a list.

**cURL**

```bash
curl -X POST https://api.hatang.dev/emails \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Shop <hello@shop.vn>",
    "to": ["customer@example.com"],
    "subject": "Your order is confirmed",
    "html": "<p>Thanks for your order!</p>"
  }'
```

**Node.js**

```bash
npm install @hatang/sdk
```

```js
import { Hatang } from "@hatang/sdk";

const hatang = new Hatang(process.env.HATANG_API_KEY, { baseUrl: "https://api.hatang.dev" });

const { data, error } = await hatang.emails.send({
  from: "Shop <hello@shop.vn>",
  to: ["customer@example.com"],
  subject: "Your order is confirmed",
  html: "<p>Thanks for your order!</p>",
});

if (error) {
  console.error(error.name, error.message);
} else {
  console.log("Sent", data.id);
}
```

**Python**

```python
from hatang import Hatang, HatangError  # pip install hatang

hatang = Hatang()  # reads HATANG_API_KEY
try:
    email = hatang.emails.send({
        "from": "Shop <hello@shop.vn>",
        "to": ["customer@example.com"],
        "subject": "Your order is confirmed",
        "html": "<p>Thanks for your order!</p>",
    })
    print("Sent", email["id"])
except HatangError as err:
    print(err.name, err.message)
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client(['base_uri' => 'https://api.hatang.dev']);

$res = $client->post('/emails', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
    'json' => [
        'from' => 'Shop <hello@shop.vn>',
        'to' => ['customer@example.com'],
        'subject' => 'Your order is confirmed',
        'html' => '<p>Thanks for your order!</p>',
    ],
    'http_errors' => false,
]);

$body = json_decode((string) $res->getBody(), true);
if ($res->getStatusCode() === 200) {
    echo 'Sent ' . $body['id'];
} else {
    echo $body['name'] . ': ' . $body['message'];
}
```

A successful call answers `200` with the id of the new email:

```json
{ "id": "01a0b904-2978-73e8-b352-4a26fdca0ab5" }
```

The email is queued and delivered in the background. You need `html`, `text` or a published `template`; `to`, `cc` and `bcc` together take at most 50 addresses.

> **Tip: Retries**
>
> Add an `Idempotency-Key` header (for example your order id) so a retried request never sends the same email twice. See [Idempotency](https://hatang.dev/docs/guides/getting-started/idempotency.md).

> **Note: Node SDK base URL**
>
> Pass `baseUrl: "https://api.hatang.dev"` (or set the `HATANG_BASE_URL` environment variable) when you create the client, as in the example above.

## 4. Check it in the dashboard

- **Email → Emails** lists every message with its status: queued, sent, delivered, bounced and so on. Open one to see each recipient and the delivery events.
- **Email → Logs** lists every API request your keys made, with the status code, duration and request and response bodies. A failed call shows up here with its error.

You can read the same status from the API:

```bash
curl https://api.hatang.dev/emails/01a0b904-2978-73e8-b352-4a26fdca0ab5 \
  -H "Authorization: Bearer $HATANG_API_KEY"
```

The response is shortened here:

```json
{
  "object": "email",
  "id": "01a0b904-2978-73e8-b352-4a26fdca0ab5",
  "from": "Shop <hello@shop.vn>",
  "to": ["customer@example.com"],
  "subject": "Your order is confirmed",
  "last_event": "delivered",
  "created_at": "2026-10-23T09:34:12.952Z"
}
```

## What can go wrong

| Status | `name` | Cause | Fix |
|---|---|---|---|
| 401 | `missing_api_key` | No `Authorization` header | Send `Authorization: Bearer ht_...` |
| 403 | `invalid_api_key` | The key is wrong or revoked | Copy the key again or create a new one |
| 401 | `restricted_api_key` | The key is locked to a different sending domain | Use a key for this domain, or one for all domains |
| 403 | `validation_error` | The `from` domain is not verified | Finish [domain verification](https://hatang.dev/docs/guides/email/domains.md) |
| 422 | `validation_error` | A field is missing or invalid; `message` names it | Fix the request body |
| 403 | `product_disabled` | The Email module is turned off | Ask an owner or admin to turn it on |
| 429 | `rate_limit_exceeded` | More than 10 email requests per second | Back off and retry ([Rate limits](https://hatang.dev/docs/guides/getting-started/rate-limits.md)) |
| 429 | `daily_quota_exceeded` | The business reached its daily email quota | Wait for midnight UTC or raise the quota in **Email → Email settings** |

## Next steps

- [Get delivery events with webhooks](https://hatang.dev/docs/guides/getting-started/webhooks.md)
- [Email overview](https://hatang.dev/docs/guides/email/overview.md): templates, broadcasts, audiences and inbound mail
- [Metrics and logs](https://hatang.dev/docs/guides/email/metrics-and-logs.md)
- [Authentication](https://hatang.dev/docs/guides/getting-started/authentication.md)

Source: https://hatang.dev/docs/guides/getting-started/quickstart-email
