# Mail

Every email carries your app's name and comes from, in order: the `from` address on the email, the project's From address, or no-reply@harakumo.com. Recipients see your project as the sender — "Vertex <hello@vertex.app>" or "Vertex <no-reply@harakumo.com>", never Harakumo.

The name comes from, in order: fromName on the email, the project's saved sender name, the project's name, a team workspace's name, then the project's address read as words (fruitly-store becomes Fruitly Store). A name that breaks the sender-name rules below is skipped for the next one; a personal workspace's name is never used, because it is its owner's own name. A workspace with several projects must pass projectId.

## Send

**curl**

```bash
curl -X POST https://harakumo.com/api/mail/send \
  -H "Authorization: Bearer $HARAKUMO_API_KEY" -H "content-type: application/json" \
  -d '{ "to": "user@yourapp.com", "subject": "Welcome", "text": "Hello!", "projectId": 12 }'
# 200 → { "message": { "status": "sent", "from_name": "Vertex", "from_email": "no-reply@harakumo.com", "message_id": "…" },
#         "from_fallback": null }

# From your own verified domain, with a Reply-To (both optional)
curl -X POST https://harakumo.com/api/mail/send \
  -H "Authorization: Bearer $HARAKUMO_API_KEY" -H "content-type: application/json" \
  -d '{ "to": "user@yourapp.com", "subject": "Welcome", "text": "Hello!", "projectId": 12,
        "from": "hello@yourdomain.com", "replyTo": "support@yourdomain.com" }'
```

> Mail to reserved test domains (example.com, .test, .invalid, .localhost) is never delivered: it is logged with status simulated. Test with a real address.

## What the statuses mean

| Status | Meaning |
| --- | --- |
| sent | The provider accepted the message. Delivery, bounces and opens are not reported yet. |
| simulated | Not sent: a reserved test address, or no provider in this environment. |
| failed | The provider refused it; the response is 502 with the reason. Failed sends do not count toward the quota. |

## Sender names

Set one per project on the Mail page (Sender names & addresses) or with `PATCH /api/projects/:id { mailSenderName }` ("" resets it), or per email with `fromName`. The same rules apply on every address, your own domain's included, because recipients read the name first:

- Up to 64 characters of Latin letters (accents are fine), digits, spaces and basic punctuation. Letters from other scripts are refused, because some pass for Latin ones in an inbox. Angle brackets, quotes, backslashes, @ and invisible characters are taken out.
- Nothing that reads as Harakumo, anywhere in the name: look-alike letters, digits standing in for letters and one-letter misspellings count too, so "Team Harakumo" and "Harakum0" are refused.
- Not only generic words. A name made of nothing but words like Support, Security, Team, No Reply, Account, Billing or Notifications is refused, because it reads as the operator of the address; one word of your own makes it fine ("Vertex Support").
- Subjects may use any script, but may not mention Harakumo (400 `reserved_subject`).
- A name that breaks a rule is refused with 400 and the reason, whether it is saved on the project or passed as `fromName` (code `sender_name` on a send); nothing is sent.

## Send from your own domain

Add a domain you own once, and your apps can send from any address on it, like hello@yourdomain.com. Its DNS lives on Harakumo, so Harakumo adds every email record itself: you never copy DKIM or SPF records. How much you do depends on where the domain's DNS is today. Harakumo finds out for you when you add it.

A subdomain like mail.yourdomain.com works too. Sending domains per workspace: 1 on hobby, 10 on pro, 50 on enterprise.

| You have | What happens |
| --- | --- |
| A domain bought on Harakumo | Nothing to copy. Harakumo adds every email record for you. If its DNS hosting is off, turn it on first (Domains page, or `host_domain_dns` from an AI assistant). |
| A domain connected to a project, nameservers already switched | Nothing to copy. Harakumo adds every email record for you and it is verified within minutes. |
| A domain connected to a project, nameservers not switched yet | Switch the nameservers and sending turns on by itself. |
| A domain at another company (GoDaddy, Namecheap, Squarespace, Hostinger …) | Harakumo sets up DNS for it and copies the records it has today, so its website and email keep working. You switch its nameservers to the two shown, where you bought it: about 5 minutes. Sending turns on by itself once they switch (usually under an hour, sometimes up to a day). |

> Until a domain is verified, your mail still goes out from no-reply@harakumo.com under your app's name, so nothing breaks while you set it up. Mail is never sent from an unverified domain.

## If the domain's DNS is on Harakumo

1. Open Mail → Sending domains and press Add a domain. Type the domain (for example yourdomain.com) and press Continue.
2. Harakumo sees the DNS is here and says "Good news … nothing to copy". Press Set it up.
3. Harakumo adds the email records to your DNS and confirms them. This usually takes a few minutes; the page updates by itself and the workspace owner gets an email.
4. When it shows Verified, choose an address like hello@yourdomain.com for a project under Sender names & addresses.

> If the card says Waiting for nameservers, the domain is set up on Harakumo but its nameservers still point at your old provider: follow the steps in the next section from step 3.

## If the domain's DNS is at another company

1. Open Mail → Sending domains and press Add a domain. Type the domain and press Continue. Harakumo explains what happens next; press Set up DNS on Harakumo.
2. Harakumo sets up DNS for the domain and copies the records it has today: your website's addresses, the MX records your inbox uses, verification records and subdomains. The card lists them under Records we copied.
3. Sign in where you bought the domain and replace its nameservers with the two on the card (each has a copy button, and Copy both copies them together). The card shows the steps for GoDaddy, Namecheap, Squarespace, Hostinger or any other provider, and guesses which one runs your DNS today.
4. Press "I switched them, check now". The switch takes usually under an hour, sometimes up to a day. The page re-checks every minute for 10 minutes, and Harakumo keeps checking every 10 minutes by itself, so you can close the page.
5. Once it switches, Harakumo adds the email records and confirms them. The domain shows Verified and the workspace owner gets an email. Choose an address on it for a project, or pass `from` when you send.

> Switch the nameservers within 14 days of starting. After that the setup stops, the DNS set up for it is removed, and Start again sets it up afresh. Nothing changes for your visitors or your inbox until you switch.

## What we copy, and why your website and email keep working

A domain's nameservers decide which DNS answers for it. Before you switch them, Harakumo copies every record it can find in the domain's current DNS into its new DNS here, so when the switch happens, your website, your inbox and anything else on the domain answer exactly as before.

- Check the list on the card (Records we copied), or open the domain on the Domains page, where you can add, edit or delete records. Harakumo reads the records the domain answers with publicly, so a record nothing asks for, like an unusual subdomain, may be missing: add it there before you switch.
- If the card says only the email records could be copied, or none, add your website's records on the Domains page before you switch.
- If DNSSEC is turned on for the domain at your provider, turn it off before switching, or the domain can stop working.
- Once the switch is seen, Harakumo adds the records sending needs: DKIM (proves the mail really comes from you), SPF and MX on a bounce subdomain (so they never clash with your inbox), and DMARC when the domain has none. It never changes or removes a record you made. Already have a DMARC record at `_dmarc`? Yours is kept.

## Changing nameservers, provider by provider

| Provider | What to do |
| --- | --- |
| GoDaddy | Sign in and open your Domain Portfolio (My Products, then Domains). Choose your domain, then DNS, then the Nameservers tab. Choose Change Nameservers, then I'll use my own nameservers. Paste the two Harakumo nameservers, one per box, delete any extra boxes, and save. GoDaddy may ask you to confirm with a code. |
| Namecheap | Sign in, open Domain List and choose Manage next to your domain. Under Nameservers, change Namecheap BasicDNS to Custom DNS, paste the two Harakumo nameservers and save with the green check mark. Namecheap Email Forwarding only works while Namecheap runs your DNS, so if you use it, set up forwarding somewhere else first. Namecheap Private Email keeps working: its records are copied. |
| Squarespace (domains that were at Google Domains are here now) | Open Domains in your Squarespace account and choose your domain. Go to DNS, then Domain Nameservers, and choose Use custom nameservers. Paste the two Harakumo nameservers and save. Squarespace warns that its own DNS settings stop applying: that is expected. |
| Hostinger | Open Domains in hPanel and choose your domain. Open DNS / Nameservers, then Change Nameservers, choose Change nameservers (custom), paste the two Harakumo nameservers and save. |
| Any other provider | Sign in where you bought the domain (the company you pay for it each year, which is not always where your website or email is). Find Nameservers, often under DNS, Domain settings or Advanced. Choose custom nameservers, replace every one listed with the two Harakumo nameservers, and save. If DNSSEC is on for the domain, turn it off first. Stuck? Ask their support to change the nameservers to the two shown. |

> The dashboard picks your provider for you from the domain's current nameservers; `harakumo mail domains show yourdomain.com --provider godaddy` prints the same steps (namecheap, squarespace, hostinger, other).

## Checking, and what each status means

Check now (or `POST /api/mail/domains/:id/check`, or the `check_mail_domain` tool) looks whether the nameservers switched and, once they have, looks each email record up in public DNS and answers with every record's status. Checks less than 10 seconds apart return the last result. In the background, domains still being set up are checked every 10 minutes and verified ones every 6 hours.

- Each record Harakumo added is `found`, `missing` or `wrong`. A wrong record also lists what public DNS shows instead (`seen`).
- `next_action` is one of `point_nameservers`, `fix_records`, `wait`, `wait_for_platform`, `restart` or `none`, and `next` says it in a sentence. While it is `point_nameservers`, the domain carries `nameservers`, `provider_hints` and `copied_records`.

| Status | can_send | Meaning |
| --- | --- | --- |
| pending | false | Being set up: waiting for the nameservers to switch, or for the records to be confirmed. `next_action` says what, if anything, you need to do. |
| verified | true, or false while `reason_code` is set | Ready. Your apps can send from any address on the domain. A verified domain with a `reason_code` is waiting on Harakumo, not on you. |
| failed | false | Stopped. `reason` says why; Start again (or add it again) sets it up again. |
| removed | false | You removed it. It no longer counts toward your plan. |

## Nameservers not switching?

- Give it time. The switch takes usually under an hour, sometimes up to a day. Press Check now again later; nothing is lost while you wait.
- Changed at the wrong company. Nameservers are set where you bought the domain (where you pay for it each year), which is not always where your website or email is. The card's "Looks like … runs DNS" line says who runs it today.
- An old nameserver left in the list. Replace every nameserver with exactly the two shown and remove the rest; a leftover one keeps answering for part of the internet.
- A typing mistake. Use the copy buttons: one wrong letter points the domain nowhere.
- DNSSEC is on. Turn it off at your provider, then switch.
- The setup stopped after 14 days. Press Start again: Harakumo sets up DNS again and shows the nameservers.
- Still stuck? Ask your provider's support to change the nameservers to the two shown.

## When a domain can't send

A send asking for an address on a domain that cannot send yet still goes out, from no-reply@harakumo.com under your app's name. The response's `from_fallback` says why: `{ requested, reason_code, reason }`. It is null when the address you asked for was used.

An explicit `from` on a domain this workspace never added is refused (400 `from_domain_not_added`) rather than sent from the shared address.

| reason_code | What it means | What to do |
| --- | --- | --- |
| zone_pending | The domain's DNS is set up on Harakumo but its nameservers do not point here yet. | Switch the nameservers (a domain bought on Harakumo with DNS hosting on is switched for you). Sending turns on by itself. |
| records_missing | Harakumo added the records; they are not visible in public DNS yet. | Nothing. It usually takes a few minutes. |
| records_wrong | A record you already had at the same name blocks one of Harakumo's. | Edit it on the Domains page so it matches the Value shown, then press Check now. |
| provider_pending | Everything is in place; the final confirmation is running. | Nothing. It usually takes a few minutes. |
| edge_not_permitted | Harakumo is finishing setup on its side for domains hosted here. | Nothing. The page updates by itself and this never expires. |
| expired | The setup did not finish within 14 days. | Start again. |
| verification_lost | It was verified, but its email records are gone. | Nothing, usually: Harakumo adds them back on the next check. If a record of yours blocks one, edit it on the Domains page. |
| domain_left_workspace | The domain's DNS is no longer hosted on Harakumo by this workspace. | Start again to set it up again. |
| replaced | Another workspace set the domain up after this setup stopped making progress. | If it is yours, press Start again. |
| domain_claimed_elsewhere | Another workspace on Harakumo now hosts or holds this domain. | If it is yours, contact support@harakumo.com. |
| domain_removed | The project's saved From address is on a domain this workspace no longer has (only in `from_fallback`). | Pick a new From address, or add the domain again. |
| method_retired | The domain was set up the old way, with records added at another DNS provider, which no longer sends. | Press Start again and switch its nameservers to Harakumo to keep the address. Its existing records are copied first. |

## Choosing the From address and Reply-To

- Per project: on the Mail page under Sender names & addresses, or `PATCH /api/projects/:id { mailFrom, mailReplyTo }` ("" resets either). A domain that is still pending can be chosen in advance; mail uses no-reply@harakumo.com until it is verified.
- Per email: `from` and `replyTo` on `/api/mail/send` (the SDK, CLI and `send_email` tool take the same).
- The part before @ uses lowercase letters, numbers and . _ + - (up to 64 characters). postmaster@, abuse@, hostmaster@ and mailer-daemon@ are reserved for mail servers.
- On the shared domain only no-reply@harakumo.com can be used. Add your own domain for any other address.
- Reply-To is one address and needs no verification, but it cannot be a Harakumo address.
- A domain belongs to one workspace. Once a workspace has verified yourdomain.com, or holds its DNS here, other workspaces cannot send from it or from names under it. A setup another workspace started keeps the name for 3 days; one that never went further than that does not block you.
- Removing a domain (Remove on its card, or `DELETE /api/mail/domains/:id`) moves projects that used it back to no-reply@harakumo.com; their sender names stay. The email records Harakumo added are removed, and so is DNS it set up for the domain whose nameservers were never switched.

## Set it up with an AI assistant

With the connector attached, ask: "Set up yourdomain.com as a sending domain on Harakumo and walk me through it." The assistant adds the domain, tells you which records were copied, gives you the two nameservers and the steps for your provider, checks until the switch is seen, and sets your project's From address once it is verified.

- `add_mail_domain` — start (or start again). Returns the nameservers, the numbered steps, provider hints and the copied records.
- `check_mail_domain` — check now: whether the nameservers switched, then each record's status.
- `list_mail_domains` / `get_mail_domain` — status, steps and every project's current From address.
- `set_mail_from` — a project's From address and Reply-To.
- `remove_mail_domain` — stop sending from a domain.
- `host_domain_dns` — for a domain bought on Harakumo, host its DNS here so every record is added for you.
- `send_email` — accepts `from` and `replyTo`.

## From code: API, SDK and CLI

**curl**

```bash
# 1. Add the domain
curl -X POST https://harakumo.com/api/mail/domains \
  -H "Authorization: Bearer $HARAKUMO_API_KEY" -H "content-type: application/json" \
  -d '{ "domain": "yourdomain.com" }'
# 201 → { "created": true, "domain": { "id": 7, "domain": "yourdomain.com", "method": "platform", "status": "pending",
#   "can_send": false, "reason_code": "zone_pending", "next_action": "point_nameservers",
#   "nameservers": ["<first nameserver>", "<second nameserver>"],
#   "copied_records": [{ "type": "MX", "host": "@", "value": "<your mail server>" }, …],
#   "steps": ["We set up DNS for yourdomain.com on Harakumo and copied the 6 records it has today …", …], … } }

# 2. After switching the nameservers where you bought the domain
curl -X POST https://harakumo.com/api/mail/domains/yourdomain.com/check -H "Authorization: Bearer $HARAKUMO_API_KEY"

# 3. Once verified, make it the project's From address
curl -X PATCH https://harakumo.com/api/projects/12 \
  -H "Authorization: Bearer $HARAKUMO_API_KEY" -H "content-type: application/json" \
  -d '{ "mailFrom": "hello@yourdomain.com", "mailReplyTo": "support@yourdomain.com" }'
```

**SDK**

```js
const { domain } = await hk.mail.domains.add('yourdomain.com');   // domain.nameservers, domain.steps
// … switch the nameservers where the domain was bought …
await hk.mail.domains.check('yourdomain.com');                    // status, can_send, next
await hk.mail.domains.waitUntilVerified('yourdomain.com');        // polls every 30 s, up to 10 minutes
await hk.mail.setFrom(project.id, { from: 'hello@yourdomain.com', replyTo: 'support@yourdomain.com' });
```

**CLI**

```bash
harakumo mail domains add yourdomain.com                  # the nameservers and the steps
harakumo mail domains show yourdomain.com --provider godaddy
harakumo mail domains check yourdomain.com --wait
harakumo mail from 12 hello@yourdomain.com --reply-to support@yourdomain.com
```

## Quotas and controls

- 100 on hobby, 5,000 on pro, 100,000 on enterprise, counted over a rolling 30 days (402 `plan_limit`). The owner is emailed at 80% and 100%.
- Custom sending domains: 1 on hobby, 10 on pro, 50 on enterprise (402 `plan_limit`). Pending and failed domains count until you remove them.
- Your own controls at `/api/mail/settings` (admins): `paused` stops every send (403 `mail_paused`), `dailyCap` limits any 24 hours (429 `daily_cap`).
- A suppression list at `/api/mail/suppressions`: a suppressed address is refused with 422 `suppressed` before anything is sent — record unsubscribes there.
- One recipient per call, text and/or HTML, with an optional Reply-To. No attachments, cc, templates or batch sending yet.
- Mail is workspace-wide: use a workspace key, not a project key.

## Reference

**SDK**

```js
await hk.mail.send({ to: 'user@yourapp.com', subject: 'Welcome', text: 'Hello!', projectId: project.id });
await hk.projects.update(project.id, { mailSenderName: 'Vertex' });                       // set once
await hk.mail.send({ to, subject, html, projectId: project.id, fromName: 'Vertex Billing' }); // or per email
const { messages } = await hk.mail.messages();

// Your own domain
await hk.mail.domains.add('yourdomain.com');
await hk.mail.domains.check('yourdomain.com');
await hk.mail.setFrom(project.id, { from: 'hello@yourdomain.com', replyTo: 'support@yourdomain.com' });
const { from_fallback } = await hk.mail.send({ to, subject, text, projectId: project.id, from: 'hello@yourdomain.com', replyTo: 'support@yourdomain.com' });
```

**CLI**

```bash
harakumo mail send user@yourapp.com "Welcome" "Hello from Vertex" --project 12 --from-name "Vertex"
harakumo mail domains add yourdomain.com
harakumo mail domains check yourdomain.com --wait
harakumo mail from 12 hello@yourdomain.com
harakumo mail send user@yourapp.com "Welcome" "Hello" --project 12 --from hello@yourdomain.com --reply-to support@yourdomain.com
```

**REST**

```http
POST   /api/mail/send          { "to", "subject", "text"?, "html"?, "projectId"?, "fromName"?, "from"?, "replyTo"? }   → { message, from_fallback }
GET    /api/mail/messages      ?projectId=&status=sent|failed|simulated&before=&limit=   → { messages, usage, settings }
GET    /api/mail/settings · PUT { "paused"?, "dailyCap"? }        # admin
GET    /api/mail/suppressions · POST { "address", "reason"?: "manual"|"unsubscribe" } · DELETE ?address=
PATCH  /api/projects/:id       { "mailSenderName"?, "mailFrom"?, "mailReplyTo"? }   # "" resets each
GET    /api/mail/domains                        → { domains, limit, default_from, can_add, projects }
GET    /api/mail/domains/preview?domain=        → { domain, root, zone_active, nameserver_move, needs_dns_hosting, nameservers, platform_option, limit, existing }
POST   /api/mail/domains       { "domain" }                  → 201 { domain, created: true } · 200 when it existed
GET    /api/mail/domains/:idOrDomain            → { domain }
POST   /api/mail/domains/:idOrDomain/check      → { domain, throttled }
DELETE /api/mail/domains/:idOrDomain            → { removed, domain, projects_reset, note }
```
