# Tên miền

Bạn chỉ gửi được email từ tên miền đã thêm vào Hạ Tầng và đã xác minh. Mỗi tên miền có khoá DKIM và tên miền phụ return path riêng, để hộp thư người nhận kiểm tra được thư thật sự do bạn gửi. Trang này hướng dẫn thêm tên miền, các bản ghi DNS, xác minh, cấu hình một chạm bằng Domain Connect và cách xử lý khi một bản ghi không xác minh được.

## Trước khi bắt đầu

- Quyền sửa DNS của tên miền (Cloudflare, nhà đăng ký tên miền hoặc nhà cung cấp DNS).
- Nếu dùng API: key có phạm vi `email:write`. Các endpoint tên miền từ chối key không có phạm vi này.

## Chọn tên miền để gửi {#pick-the-domain-to-send-from}

Phần tên miền trong địa chỉ `From` phải **trùng khớp** với một tên miền đã xác minh. Muốn gửi từ `hello@updates.example.com` thì phải thêm `updates.example.com`; thêm `example.com` là chưa đủ, và ngược lại.

> **Tip: Dùng tên miền phụ cho email marketing**
>
> Tên miền phụ như `updates.example.com` hay `mail.example.com` giữ uy tín gửi thư tách biệt với tên miền gốc. Nếu một chiến dịch không suôn sẻ, thư nhân viên gửi từ `example.com` không bị ảnh hưởng. Nhiều đội dùng một tên miền phụ cho email giao dịch và một tên miền phụ khác cho chiến dịch email.

## Thêm tên miền

**cURL**

```bash
curl -X POST https://api.hatang.dev/domains \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "shop.vn" }'
```

**Node.js**

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

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

const { data: domain, error } = await hatang.domains.create({ name: "shop.vn" });
if (error) throw new Error(error.message);
console.table(domain.records);
```

**Python**

```python
import os
import requests

res = requests.post(
    "https://api.hatang.dev/domains",
    headers={"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}"},
    json={"name": "shop.vn"},
)
res.raise_for_status()
for record in res.json()["records"]:
    print(record["type"], record["name"], record["value"])
```

**PHP**

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

$res = $client->post('/domains', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
    'json' => ['name' => 'shop.vn'],
]);
$domain = json_decode($res->getBody(), true);
```

Trong dashboard, mở **Email → Tên miền**, nhập tên miền vào ô **Tên miền** rồi bấm **Thêm tên miền**. Mục **Tuỳ chọn nâng cao** có các thiết lập giống các trường API dưới đây.

| Trường | Mặc định | Mô tả |
|---|---|---|
| `name` | bắt buộc | Tên miền hoặc tên miền phụ, ví dụ `shop.vn` hay `updates.shop.vn`. Được lưu ở dạng chữ thường. |
| `custom_return_path` | `send` | Nhãn tên miền phụ cho địa chỉ nhận thư trả lại và SPF: `send` thành `send.shop.vn`. Chữ, số và dấu gạch ngang, tối đa 63 ký tự. Trong dashboard: **Return-Path tuỳ chỉnh**. |
| `tracking_subdomain` | không có | Nhãn tuỳ chọn cho link theo dõi mang tên miền của bạn, ví dụ `links` thành `links.shop.vn`. Phải khác nhãn return path. Trong dashboard: **Tên miền phụ theo dõi**. Xem [Theo dõi mở và nhấp](https://hatang.dev/vi/docs/guides/email/tracking.md). |
| `open_tracking` | `false` | Chèn pixel theo dõi lượt mở vào email HTML gửi từ tên miền này. |
| `click_tracking` | `false` | Viết lại link trong email HTML gửi từ tên miền này để ghi nhận lượt nhấp. |
| `region` | vùng của nền tảng | Nhận để tương thích Resend và được lưu lại. Việc gửi luôn dùng vùng Amazon SES của nền tảng. |

Response (`201`) là tên miền kèm các bản ghi cần thêm:

```json
{
  "object": "domain",
  "id": "dom_0199e6c17a2b7f009c1e3f6a2b1d9e10",
  "name": "shop.vn",
  "status": "not_started",
  "created_at": "2026-10-14T09:12:03.000Z",
  "region": "us-west-2",
  "dkim_selector": "rs7hq2kx9p",
  "open_tracking": false,
  "click_tracking": false,
  "tracking_subdomain": null,
  "tracking_domain_verified": false,
  "custom_return_path": "send",
  "ses_status": "pending",
  "tls": "opportunistic",
  "capabilities": { "sending": "enabled", "receiving": "disabled" },
  "records": [
    { "record": "MX", "name": "send.shop.vn", "type": "MX", "ttl": "Auto", "status": "not_started", "value": "feedback-smtp.us-west-2.amazonses.com", "priority": 10, "required": true },
    { "record": "SPF", "name": "send.shop.vn", "type": "TXT", "ttl": "Auto", "status": "not_started", "value": "v=spf1 include:amazonses.com ~all", "required": true },
    { "record": "DKIM", "name": "rs7hq2kx9p._domainkey.shop.vn", "type": "TXT", "ttl": "Auto", "status": "not_started", "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqh…", "required": true },
    { "record": "DMARC", "name": "_dmarc.shop.vn", "type": "TXT", "ttl": "Auto", "status": "not_started", "value": "v=DMARC1; p=none;", "required": false }
  ]
}
```

Thêm một tên miền đã có sẽ nhận lỗi `409`.

## Các bản ghi DNS

Luôn chép tên và giá trị từ dashboard hoặc từ mảng `records`. Các giá trị dưới đây chỉ là ví dụ.

| Bản ghi | Loại | Tên | Giá trị | Bắt buộc |
|---|---|---|---|---|
| DKIM | TXT | `<selector>._domainkey.shop.vn` | `v=DKIM1; k=rsa; p=<khoá công khai>` | Có |
| MX của return path | MX, ưu tiên 10 | `send.shop.vn` | `feedback-smtp.<vùng>.amazonses.com` | Có |
| SPF của return path | TXT | `send.shop.vn` | `v=spf1 include:amazonses.com ~all` | Có |
| DMARC | TXT | `_dmarc.shop.vn` | `v=DMARC1; p=none;` | Không, nhưng nên có |
| Theo dõi | CNAME | `links.shop.vn` | Tên máy chủ theo dõi hiển thị trong dashboard | Chỉ khi có tên miền phụ theo dõi |
| Nhận thư | MX, ưu tiên 10 | `shop.vn` | Máy chủ nhận thư hiển thị trong dashboard | Chỉ khi bật nhận email |

### DKIM

Hạ Tầng tạo một khoá RSA 2048 bit cho mỗi tên miền. Nửa công khai nằm trong bản ghi TXT dưới một selector bắt đầu bằng `rs` và tám ký tự ngẫu nhiên (trường `dkim_selector`). Mọi thư đều được ký bằng nửa bí mật cho tên miền của bạn, nhờ vậy DMARC đạt qua DKIM.

Khoá 2048 bit dài hơn 255 ký tự. Phần lớn nhà cung cấp DNS tự tách thành nhiều chuỗi; bạn chỉ cần dán nguyên giá trị vào một bản ghi. Hạ Tầng bỏ qua khoảng trắng khi so khoá.

### Return path: MX và SPF trên `send.`

Địa chỉ nhận thư trả lại (envelope sender, hay `Return-Path`) của mọi thư nằm trên tên miền phụ return path, ví dụ `bounce+…@send.shop.vn`. Hai bản ghi trên tên miền phụ này giúp nó hoạt động:

- Bản ghi **MX** đưa thư trả lại và báo spam về Amazon SES, rồi SES báo lại cho Hạ Tầng.
- Bản ghi TXT **SPF** cho phép Amazon SES gửi thay tên miền phụ đó. Vì return path là tên miền phụ của tên miền bạn, SPF căn chỉnh (align) với tên miền trong `From` theo chế độ căn chỉnh relaxed của DMARC.

Các bản ghi nằm trên tên miền phụ chứ không phải tên miền gốc, nên không bao giờ đụng tới SPF hay MX mà tên miền gốc đang có. Nếu tên miền phụ đã có bản ghi SPF, Hạ Tầng chấp nhận mọi bản ghi `v=spf1` có chứa `include:amazonses.com`.

### DMARC

DMARC cho hộp thư người nhận biết phải làm gì với thư không đạt căn chỉnh SPF và DKIM. Hạ Tầng đề xuất `v=DMARC1; p=none;`, tức là chỉ theo dõi. Mọi bản ghi bắt đầu bằng `v=DMARC1` đều được tính là đã có, nên nếu đã có chính sách riêng thì cứ giữ. DMARC không chặn việc xác minh. Gmail và Yahoo yêu cầu người gửi số lượng lớn phải có DMARC, nên hãy thêm. Xem [Khả năng vào hộp thư](https://hatang.dev/vi/docs/guides/email/deliverability.md).

### Theo dõi và nhận thư

Hai bản ghi này chỉ xuất hiện khi bạn dùng tính năng tương ứng: CNAME theo dõi khi tên miền có tên miền phụ theo dõi, MX nhận thư khi bạn bật nhận email. Cả hai đều không chặn việc gửi. Xem [Theo dõi mở và nhấp](https://hatang.dev/vi/docs/guides/email/tracking.md) và [Nhận email](https://hatang.dev/vi/docs/guides/email/receiving.md).

> **Caution: Tên bản ghi ở nhà cung cấp DNS**
>
> Nhiều nhà cung cấp DNS tự nối tên zone vào tên bạn nhập. Nhập `send.shop.vn` vào ô **Name** của zone `shop.vn` có thể tạo ra `send.shop.vn.shop.vn`. Với những nhà cung cấp như vậy, chỉ nhập phần đứng trước tên miền (`send`, `rs7hq2kx9p._domainkey`, `_dmarc`).

## Xác minh tên miền

Khi đã thêm bản ghi, bắt đầu xác minh:

- Dashboard: mở tên miền và bấm **Xác minh bản ghi DNS**. Trong lúc tên miền đang chờ, trang tự kiểm tra lại và tự làm mới.
- API: `POST /domains/{id}/verify`, hoặc `hatang.domains.verify(id)` trong SDK Node.js.

```bash
curl -X POST https://api.hatang.dev/domains/dom_0199e6c17a2b7f009c1e3f6a2b1d9e10/verify \
  -H "Authorization: Bearer $HATANG_API_KEY"
```

```json
{ "object": "domain", "id": "dom_0199e6c17a2b7f009c1e3f6a2b1d9e10", "status": "pending" }
```

Hạ Tầng kiểm tra mọi bản ghi ngay lập tức, sau đó tiếp tục kiểm tra ở chế độ nền cho tới khi tên miền được xác minh. Thay đổi DNS có thể mất tới một giờ để lan truyền. Tên miền **Đã xác minh** khi mọi bản ghi bắt buộc (DKIM, MX và SPF của return path) đều khớp **và** Amazon SES đã xác nhận chữ ký DKIM. Bản ghi không bắt buộc không bao giờ làm chậm việc xác minh.

### Trạng thái tên miền

| Trạng thái | Trong dashboard | Ý nghĩa |
|---|---|---|
| `not_started` | Chưa bắt đầu | Tên miền đã được thêm nhưng chưa yêu cầu xác minh. |
| `pending` | Đang chờ | Hạ Tầng đang kiểm tra. Còn bản ghi bắt buộc chưa khớp, hoặc Amazon SES chưa xác nhận DKIM. |
| `verified` | Đã xác minh | Gửi được từ tên miền này. |
| `temporary_failure` | Lỗi tạm thời | Tra cứu DNS lỗi vì lý do khác "không tìm thấy", ví dụ hết thời gian chờ. Hạ Tầng sẽ thử lại. |
| `failed` | Thất bại | Xác minh thất bại, ví dụ Amazon SES từ chối cấu hình DKIM. Sửa bản ghi rồi xác minh lại. |

Mỗi bản ghi cũng có `status` riêng (`not_started`, `pending`, `verified` hoặc `failed`) và, khi lỗi, trường `last_error` cho biết Hạ Tầng thấy gì thay vào đó, như `found: nothing` hoặc giá trị đang có.

Hạ Tầng gửi webhook `domain.created`, `domain.updated` và `domain.deleted`, kèm tên miền giống như `GET /domains/{id}` trả về. Mỗi lần trạng thái đổi sẽ bắn `domain.updated`.

## Cấu hình một chạm với Domain Connect

Nếu nhà cung cấp DNS hỗ trợ [Domain Connect](https://www.domainconnect.org/), trang tên miền có khung **Bản ghi DNS** với nút **Tự động cấu hình với** tên nhà cung cấp. Bấm vào, đăng nhập ở nhà cung cấp DNS và đồng ý thay đổi. Nhà cung cấp thêm bản ghi, đưa bạn quay lại Hạ Tầng và việc xác minh bắt đầu ngay. Nút **Cài đặt thủ công** đưa bạn tới bảng bản ghi để tự thêm.

Domain Connect thêm:

- bản ghi MX của return path và quy tắc SPF (`include:amazonses.com`, gộp vào bản ghi SPF sẵn có trên tên miền phụ đó),
- bản ghi DKIM,
- bản ghi DMARC (`v=DMARC1; p=none;`),
- CNAME theo dõi, khi tên miền có tên miền phụ theo dõi.

> **Note**
>
> Xem kỹ những thay đổi nhà cung cấp DNS hiển thị trước khi đồng ý, nhất là khi tên miền đã có bản ghi DMARC bạn muốn giữ.

Khi nhà cung cấp DNS không hỗ trợ Domain Connect nhưng tên miền nằm trên Cloudflare, trang tên miền có link mở thẳng trang bản ghi DNS của zone.

Qua API:

```bash
# Tên miền này có cấu hình một chạm được không?
curl https://api.hatang.dev/domains/$DOMAIN_ID/domain-connect -H "Authorization: Bearer $HATANG_API_KEY"
# { "object": "domain_connect", "available": true, "reason": null, "dns_host": "Cloudflare", "zone": "shop.vn" }

# Lấy URL đã ký để chuyển người dùng tới
curl -X POST https://api.hatang.dev/domains/$DOMAIN_ID/domain-connect \
  -H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" -d '{}'
# { "object": "domain_connect", "id": "dom_…", "url": "https://…" }
```

Khi `available` là `false`, `reason` là `no_domain_connect` (nhà cung cấp DNS không hỗ trợ), `template_not_supported` (có hỗ trợ Domain Connect nhưng chưa hỗ trợ template của Hạ Tầng) hoặc `not_configured`. `POST` nhận `redirect_uri` tuỳ chọn; nếu không có, người dùng quay về trang tên miền trong dashboard.

`GET /domains/{id}/dns-provider` cho biết nhà cung cấp DNS đang phục vụ tên miền:

```json
{ "object": "dns_provider", "zone": "shop.vn", "provider": "cloudflare", "nameservers": ["ada.ns.cloudflare.com", "bob.ns.cloudflare.com"], "cloudflare_url": "https://dash.cloudflare.com/…" }
```

## Return path tuỳ chỉnh

Nhãn return path là `send`, trừ khi bạn chọn nhãn khác lúc thêm tên miền. Chỉ đổi khi `send.shop.vn` đã được dùng cho việc khác, ví dụ bản ghi thư trả lại của một dịch vụ email khác. Nhãn không đổi được về sau: hãy xoá tên miền rồi thêm lại với nhãn mới.

## Sửa hoặc xoá tên miền

`PATCH /domains/{id}` đổi các thiết lập được phép đổi sau khi tạo:

```bash
curl -X PATCH https://api.hatang.dev/domains/$DOMAIN_ID \
  -H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
  -d '{ "open_tracking": true, "click_tracking": true, "tracking_subdomain": "links" }'
```

| Trường | Tác dụng |
|---|---|
| `open_tracking`, `click_tracking` | Bật hoặc tắt theo dõi cho các email gửi từ lúc đó. |
| `tracking_subdomain` | Đặt, đổi hoặc gỡ (`null`) tên miền phụ theo dõi. Một bản ghi CNAME mới xuất hiện để bạn thêm. |
| `capabilities.receiving` | `"enabled"` hoặc `"disabled"`. Xem [Nhận email](https://hatang.dev/vi/docs/guides/email/receiving.md). |

`DELETE /domains/{id}` xoá tên miền và định danh Amazon SES của nó. Email còn trong hàng đợi của tên miền đó sẽ bị trả lại với lý do "Sending domain no longer exists".

## Xử lý sự cố

| Hiện tượng | Nguyên nhân và cách sửa |
|---|---|
| Bản ghi báo `found: nothing` | Bản ghi chưa có hoặc đặt sai tên. Kiểm tra tên zone có bị nối hai lần không (xem lưu ý ở trên), rồi chờ vài phút cho DNS cập nhật. |
| DKIM lỗi dù đã có bản ghi | Giá trị bị cắt hoặc bị sửa. Dán lại toàn bộ giá trị `v=DKIM1; k=rsa; p=…`. Một số nhà cung cấp cần đặt giá trị dài trong dấu ngoặc kép hoặc tách thành nhiều chuỗi. |
| SPF trên `send.` lỗi | Tên miền phụ có bản ghi SPF không chứa `include:amazonses.com`, hoặc có hai bản ghi SPF. Chỉ giữ một bản ghi `v=spf1` có chứa `include:amazonses.com`. |
| Mọi bản ghi đều khớp nhưng trạng thái vẫn `pending` | Amazon SES chưa xác nhận DKIM. Thường chỉ chậm hơn DNS vài phút; bấm **Kiểm tra lại DNS** sau đó. |
| MX của return path xung đột với bản ghi khác | `send.shop.vn` đã có CNAME. Tên đã có CNAME thì không có bản ghi nào khác được. Gỡ CNAME, hoặc thêm lại tên miền với **Return-Path tuỳ chỉnh** khác. |
| Lỗi `403` "The shop.vn domain is not verified" khi gửi | Tên miền của `From` chưa được thêm, chưa xác minh, hoặc là một tên miền phụ khác. Xem [Chọn tên miền để gửi](#pick-the-domain-to-send-from). |
| Lỗi `401 restricted_api_key` khi gửi | API key bị giới hạn vào tên miền khác. Dùng key của tên miền này hoặc key không giới hạn. |

## Bước tiếp theo

- [Gửi email](https://hatang.dev/vi/docs/guides/email/sending.md)
- [Khả năng vào hộp thư](https://hatang.dev/vi/docs/guides/email/deliverability.md)
- [Theo dõi mở và nhấp](https://hatang.dev/vi/docs/guides/email/tracking.md)
- [Nhận email](https://hatang.dev/vi/docs/guides/email/receiving.md)

Source: https://hatang.dev/vi/docs/guides/email/domains
