# Send and receive email in OpenOrange Workers

[OpenOrange](https://openorange.dev) runs Cloudflare Workers on AWS, Google Cloud, Azure or your own servers. Its email binding sends through an SMTP server you choose, and a Worker's `email()` handler runs when a raw message is POSTed to `/cdn-cgi/handler/email`. Groundmail does both: it sends your Worker's mail, and it receives mail on your domain and forwards each message to your Worker.

---

## Prerequisites

- A Groundmail account with a [verified domain](https://docs.groundmail.dev/domains). To receive, use a subdomain such as `inbound.yourdomain.com` so your main mailboxes keep their MX.
- An SMTP credential (`gmsmtp_…`) from **SMTP** in the dashboard, to send.
- An OpenOrange Worker (`orange dev` locally, or deployed with `orange deploy`).

---

## Send

Bind the sender in `wrangler.jsonc`, as on Cloudflare:

```jsonc
{
  "send_email": [{ "name": "EMAIL" }]
}
```

Point OpenOrange at Groundmail's SMTP relay in `.dev.vars` (and `orange deploy --secrets-file .dev.vars` on a cluster). URL-encode the password if it has `/`, `+` or `=`:

```dotenv
ORANGE_EMAIL_SMTP_URL=smtp://groundmail:gmsmtp_your_password@smtp.groundmail.dev:587
```

Send from the Worker:

```ts
await env.EMAIL.send({
  from: "hello@yourdomain.com",
  to: "user@example.com",
  subject: "Welcome",
  text: "Thanks for signing up.",
})
```

The From address must be on your verified domain. `message.forward()` and `message.reply()` in your `email()` handler send through the same relay. To set the email's [category](https://docs.groundmail.dev/smtp#category), add an `X-Groundmail-Category` header or set it on the SMTP credential.

---

## Receive

1. Turn on inbound for your domain and publish the MX record it returns. See [Inbound email](https://docs.groundmail.dev/inbound).
2. Pick a secret and give it to the Worker as `ORANGE_EMAIL_INBOUND_SECRET` (in `.dev.vars`, and with `--secrets-file` on a cluster).
3. Add a route that sends the address to your Worker's email route, with the same secret. In the dashboard: the domain's **Routing** tab, **Add route**, **Send to URL**. Or by API:

```bash
curl -X POST https://api.groundmail.dev/domains/inbound.yourdomain.com/routes \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "*",
    "action": "url",
    "url": "https://your-worker.example.com/cdn-cgi/handler/email",
    "secret": "the-same-secret-as-ORANGE_EMAIL_INBOUND_SECRET"
  }'
```

`*` sends every address to the Worker. Use `support` (or any address) to send just that one, and give other addresses their own routes: forward `billing@` to a teammate, drop `noreply@`. See [Email routing](https://docs.groundmail.dev/routing).

Each email to the domain now runs your Worker's handler:

```ts
export default {
  async email(message, env) {
    if (message.headers.get("subject")?.includes("unsubscribe")) {
      message.setReject("Not accepted")
      return
    }
    await message.forward("team@yourdomain.com")
  },
}
```

A `setReject()` answers `400`, which Groundmail records as `rejected` and doesn't retry. If the Worker is down or answers `5xx`, Groundmail retries for about 2 hours. See [Send to a URL](https://docs.groundmail.dev/routing#send-to-a-url) for the request format and retries. Each received email also stays in Groundmail, with its `forward.status`, and `email.received` webhooks still go.

---

## Related

- [SMTP relay](https://docs.groundmail.dev/smtp)
- [Inbound email](https://docs.groundmail.dev/inbound)
- [OpenOrange email docs](https://openorange.dev/docs/bindings/email/)
