# Send email from Hetzner Cloud

Hetzner blocks outbound ports 25 and 465 on cloud servers by default. A mail library that connects to an SMTP server on either port fails with `Connection timeout` or `ETIMEDOUT`, even when the host, username and password are right. Hetzner lifts the block on request once your account is a month old and has paid its first invoice, case by case.

Port 587 and HTTPS aren't blocked, so an app on [Hetzner Cloud](https://www.hetzner.com/cloud) can send through Groundmail from day one, with no request to Hetzner:

| Your app | Use | Code change |
|----------|-----|-------------|
| You control the code | The Groundmail API or an SDK, over HTTPS | Replace the send call |
| It only speaks SMTP (Ghost, n8n, WordPress, a framework's mailer) | The SMTP relay on port 587 | None: change the SMTP settings |

---

## Prerequisites

- A Groundmail account with a [verified sending domain](https://docs.groundmail.dev/domains). A subdomain such as `mail.yourdomain.com` works well.
- An API key (`gm_…`) from **API keys** in the dashboard.

---

## 1. Send with the API

Give your app the key as the `GROUNDMAIL_API_KEY` environment variable, the same way it gets its other secrets: an `.env` file, a systemd unit, or your container's environment. Then send with an SDK.

**Node.js**

```bash
npm install groundmail
```

```js
import { Groundmail } from 'groundmail'

const groundmail = new Groundmail(process.env.GROUNDMAIL_API_KEY)

await groundmail.emails.send({
  from: 'App <no-reply@mail.yourdomain.com>',
  to: 'user@example.com',
  subject: 'Reset your password',
  html: '<p>Click the link to reset your password.</p>',
})
```

Already on Nodemailer? Swap the SMTP transport for the [Nodemailer transport](https://docs.groundmail.dev/frameworks/node), which sends over HTTPS, and keep your `sendMail` calls.

**Python**

```bash
pip install groundmail
```

```python
import os
from groundmail import Groundmail

groundmail = Groundmail(os.environ["GROUNDMAIL_API_KEY"])
groundmail.emails.send(
    from_="App <no-reply@mail.yourdomain.com>",
    to="user@example.com",
    subject="Reset your password",
    html="<p>Click the link to reset your password.</p>",
)
```

Go, PHP and Ruby have SDKs too: see [SDKs](https://docs.groundmail.dev/sdks/python). Any other language can call `POST https://api.groundmail.dev/emails` with the key: see the [API reference](https://docs.groundmail.dev/api/emails).

---

## 2. Keep SMTP

If your app can only send over SMTP, point it at Groundmail's relay on port **587**:

| Setting | Value |
|---------|-------|
| Host | `smtp.groundmail.dev` |
| Port | `587` |
| Encryption | STARTTLS |
| Username | `groundmail` |
| Password | An SMTP credential from **SMTP** in the dashboard |

If your app is set to port 465 (implicit TLS), change it to 587 with STARTTLS. See [SMTP](https://docs.groundmail.dev/smtp).

Running your apps with Coolify on a Hetzner server? See [Send email from Coolify](https://docs.groundmail.dev/integrations/coolify) for where these settings go.

---

## Troubleshooting

| What you see | What to do |
|--------------|------------|
| `Connection timeout` or `ETIMEDOUT` when sending | Your app is connecting on port 25 or 465. Change the port to `587` with STARTTLS, or switch to the API. |
| `535` from the SMTP relay | The username isn't `groundmail`, or the password isn't an SMTP credential. Create one under **SMTP**. |
| `550` saying the domain isn't verified | The From address isn't on a verified domain. See [Domains](https://docs.groundmail.dev/domains). |
