# Troubleshooting

Pick what you're seeing. Each fix says how to check it and what to do next.

- [Emails aren't arriving](#emails-arent-arriving): sent, but not in the inbox, or stuck before sending
- [Domain won't verify](#domain-wont-verify): DNS records added but the domain isn't verified
- [Going to spam](#going-to-spam): delivered, but landing in junk
- [API errors](#api-errors): 401, 403, 422 and 429, and what they mean
- [Webhooks not arriving](#webhooks-not-arriving): events you expect don't reach your endpoint

> **Tip:** Using a coding agent? Ask it to check for you. With the [MCP server](https://docs.groundmail.dev/ai/mcp) or the [CLI](https://docs.groundmail.dev/cli) it can look up the email, the domain and recent bounces itself, so you don't need to paste keys or logs.

---

## Emails aren't arriving {#emails-arent-arriving}

Start with the email itself. Open it on the **Emails** page in the dashboard, or look it up by the `msg_…` id the send returned:

```bash
groundmail emails get --id msg_abc123
```

Its status says where it stopped:

| Status | What it means | What to do |
|--------|---------------|------------|
| `scheduled` | It goes out at `scheduled_at` | Nothing, or [reschedule or cancel it](https://docs.groundmail.dev/emails#schedule-reschedule-and-cancel) |
| `held` | Held for [content review](https://docs.groundmail.dev/content-scanning#held), or sending is paused on the account | Held email is reviewed quickly and then sent or rejected; you get a webhook either way |
| `suppressed` | A recipient is on your [suppression list](https://docs.groundmail.dev/suppressions), or [Groundmail validation](https://docs.groundmail.dev/emails#recipients-held-back-by-groundmail-validation) held every recipient back | Check `details.suppressed` or `held_back`. Remove the suppression only if you know the address works now |
| `failed` | It was rejected, for example by [content scanning](https://docs.groundmail.dev/content-scanning#rejected-emails) | Read `bounce_reason` |
| `sent` | Accepted for delivery and on its way | Wait for `delivered` or `bounced`. A slow receiving server can take a few minutes |
| `bounced` | The receiving server refused it | `bounce_type` says why: `hard` means the address can't receive (it's suppressed now); `block` means the server refused the sender, not the address |
| `delivered` | The recipient's mail provider accepted it | It's in their mailbox system: check spam and filters on their side ([Going to spam](#going-to-spam)) |

### The send itself returned an error

Nothing was sent. The error body's `code` and `hint` say what to fix: see [API errors](#api-errors).

### Only your own address gets mail

Sandbox sends (from `noreply@mail.groundmail.dev`) only go to your account email. To send to anyone else you need a [verified domain](#domain-wont-verify) and a payment method on file at **Settings → Billing**. The Free plan is never charged.

### It's a test send

Sends with a test key (`gm_test_…`) and sends to `@simulator.groundmail.dev` addresses run every check but deliver nothing. They're marked `test: true`. See [Testing](https://docs.groundmail.dev/testing).

---

## Domain won't verify {#domain-wont-verify}

Run verify again from the domain's page in the dashboard, or:

```bash
groundmail domains verify yourdomain.com
```

The response says which records are missing. Then check these, most common first.

### DNS hasn't propagated yet

New records can take a few minutes to show up in public DNS, sometimes longer at some hosts. Wait, then verify again. Re-running verify is always safe.

### Two SPF records

A domain can only have one SPF record. If you already send from another service, add `include:spf.groundmail.dev` to your existing SPF record instead of adding a second one:

```dns
v=spf1 include:_spf.google.com include:spf.groundmail.dev ~all
```

### The record is on the wrong name

DNS hosts handle names differently. Some want the full name (`_groundmail.yourdomain.com`), others only the part before your domain (`_groundmail`); entering the full name in the second kind gives `_groundmail.yourdomain.com.yourdomain.com`. Compare what you added with the names in the dashboard, and make sure the records are in the zone that actually serves your domain.

### The DKIM value was split or cut short

The DKIM TXT value is long. Paste it exactly as shown, as one value; some hosts split long values into quoted chunks, which is fine, but a value cut short fails. If your DNS is on Cloudflare, Vercel or GoDaddy, let Groundmail add the records for you instead: see [Apply DNS](https://docs.groundmail.dev/domains#apply-dns).

### Public DNS looks right, but verify still fails

Verify also waits for the mail server to accept the DKIM and return-path records, which can lag behind public DNS. The response shows `mail_server_signing.dkim_ok` and `return_path_ok`; wait a few minutes and verify again. Details: [Verify DNS](https://docs.groundmail.dev/domains#verify-dns).

---

## Going to spam {#going-to-spam}

1. **Send from your own verified domain**, not the sandbox sender. Verification turns on DKIM signing with your domain, so DMARC can pass.
2. **Add a DMARC record** if your domain has none. Start with `p=none` and watch the reports with [DMARC monitoring](https://docs.groundmail.dev/dmarc).
3. **Keep transactional and marketing apart.** Password resets and receipts go out as `transactional`; onboarding tips and updates as `product`, which sends from separate IPs. See [Transactional and product streams](https://docs.groundmail.dev/deliverability#transactional-and-product-streams).
4. **Check your bounce and complaint rates** on the Deliverability page or with [`GET /deliverability`](https://docs.groundmail.dev/deliverability). Bounces above 2% or complaints above 0.1% hurt inbox placement.
5. **Look at the content.** Link shorteners, image-only emails and a mismatched `from` name all count against you. An email that scores badly in [content scanning](https://docs.groundmail.dev/content-scanning) is held or rejected before it goes out.

---

## API errors {#api-errors}

Every error has a stable `code`, a `message`, a `hint` that says how to fix it, and a `docs_url`. Branch on `code`, never on `message`.

| Status | Usual cause | Fix |
|--------|-------------|-----|
| `401` | Missing or wrong API key | Send `Authorization: Bearer gm_…` with a key that hasn't been revoked |
| `403` | `from` isn't on a verified domain, a sandbox send to someone other than you, or a key without the scope | Read `hint`. See [forbidden](https://docs.groundmail.dev/errors#forbidden) and [insufficient_scope](https://docs.groundmail.dev/errors#insufficient_scope) |
| `400` | A missing or invalid field, or invalid JSON | Read `message` and `details`. Every send needs `from`, `to`, `subject` and `html` or `text` |
| `422` | A recipient is suppressed or opted out | See `details.suppressed`, `details.category_unsubscribed` or `details.held_back` |
| `429` | Rate limit, plan limit or the new-account ramp-up | `rate_limited`: back off and retry. `plan_limit`: upgrade. `daily_ramp_limit`: send the rest after `details.resets_at`. See [Sending limits](https://docs.groundmail.dev/sending-limits) |
| `5xx` | Something failed on our side | Retry with backoff and the same `Idempotency-Key`, so a retry never sends twice |

The full list, with what to do for each code: [Errors](https://docs.groundmail.dev/errors).

---

## Webhooks not arriving {#webhooks-not-arriving}

- **Check the endpoint is subscribed to the event.** Each endpoint lists the events it gets; `email.delivered` and `email.bounced` are separate.
- **Check the signature with the raw body.** Verify `Groundmail-Signature` against the request body exactly as received, before any JSON parsing. See [Signature verification](https://docs.groundmail.dev/webhooks#signature-verification).
- **Answer quickly with a 2xx.** Do the work after you respond. A delivery that doesn't get a `2xx` is retried 4 more times over about 5 minutes.
- **Send a test event** with [`POST /emails/test`](https://docs.groundmail.dev/webhooks#test-sink) to see what your endpoint receives, or send to a [simulator address](https://docs.groundmail.dev/testing#simulator-addresses) to get every event of a real send without delivering mail.

---

## Still stuck?

Use the chat button in the dashboard, or email support@groundmail.dev. Include the `msg_…` id or the domain name; we can see the rest, so there's no need to send keys or logs.
