# Quickstart

A key, a first email, and a verified sending domain.

Three steps. The first two take about a minute and prove the integration works. The third is DNS, and it is the only part you cannot finish in this tab.

### 1. Get a key

Sign up at https://emails.sh/signup, open https://emails.sh/dashboard/api-keys, and create a key. It starts with esh_ and is shown once. Put it in your environment rather than in a source file.

Where the key goes:
```bash
# .env, and check that .env is in .gitignore.
# The key comes from https://emails.sh/dashboard/api-keys.
EMAILSSH_API_KEY=esh_your_key_here

# or, for a one-off in a shell
export EMAILSSH_API_KEY="esh_your_key_here"
```

### 2. Send an email

Before any DNS exists you can send from onboarding@emails.sh, to an address you own. The sandbox address only sends to addresses that belong to the workspace, which is what keeps it from being an open relay.

First send:
```bash
curl -X POST https://emails.sh/v1/emails \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "onboarding@emails.sh",
    "to": ["you@example.com"],
    "subject": "First email from emails.sh",
    "html": "<p>It works.</p>"
  }'
```

200 OK:
```json
{
  "id": "em_01J9X8Q2K7Y4RN3M",
  "status": "queued"
}
```

Keep the id. GET /v1/emails/em_01J9X8Q2K7Y4RN3M tells you whether the receiving server took it, and if it bounced, what the remote server said.

### 3. Send from your own domain

Add a subdomain you control rather than the domain your own mail runs on: mail.acme.com, not acme.com. A subdomain keeps a sending reputation apart from your human email and lets you publish an MX record without competing with your existing inbox.

Add the domain:
```bash
curl -X POST https://emails.sh/v1/domains \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "mail.acme.com"}'
```

201 Created:
```json
{
  "id": "dom_01J9X8W1A2",
  "domain": "mail.acme.com",
  "verification_status": "pending",
  "records": [
    { "type": "CNAME", "name": "abc123._domainkey.mail.acme.com", "value": "abc123.dkim.amazonses.com", "purpose": "DKIM" },
    { "type": "CNAME", "name": "def456._domainkey.mail.acme.com", "value": "def456.dkim.amazonses.com", "purpose": "DKIM" },
    { "type": "CNAME", "name": "ghi789._domainkey.mail.acme.com", "value": "ghi789.dkim.amazonses.com", "purpose": "DKIM" },
    { "type": "TXT", "name": "_emailssh.mail.acme.com", "value": "emailssh-verify=9f2c4e1b", "purpose": "ownership" },
    { "type": "TXT", "name": "mail.acme.com", "value": "v=spf1 include:amazonses.com ~all", "purpose": "SPF" },
    { "type": "TXT", "name": "_dmarc.mail.acme.com", "value": "v=DMARC1; p=none;", "purpose": "DMARC" }
  ]
}
```

Publish those records at your DNS host, then call POST /v1/domains/dom_01J9X8W1A2/verify. It reads DNS and answers with found: true or false per record, so a missing one is named rather than guessed at. Most hosts propagate in under fifteen minutes.

Once verification passes, change from to an address on that domain and send again. Nothing else in your code changes.

To have a coding assistant do all of this in your codebase instead, run npx skills add emailssh/skill and ask it to add emails.sh. It picks the SDK for your framework, writes the key into the right env file, writes the send call, and reads the DNS records back to you.

### Next

- **The send endpoint in full** (/docs/sending): Every field, attachments, scheduling, idempotency, and tags.
- **Delivery events** (/docs/delivery): Statuses, the event timeline, and what a bounce tells you.
- **Webhooks** (/docs/webhooks): Get delivered, bounced, and complained pushed to you instead of polling.
- **Errors** (/docs/errors): Every code the API returns, and what to do about each.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
