Send email from Railway
Railway blocks outbound SMTP on its Free, Trial and Hobby plans. Connections to ports 25, 465, 587 and 2525 never leave Railway, so a mail library that sends through an SMTP server fails with Connection timeout, ETIMEDOUT or Network is unreachable, even when the host, username and password are right. Only Pro and above can send over SMTP.
HTTPS isn't blocked on any plan. There are two ways to send through Groundmail from Railway:
| 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 template, on Railway's private network | None: change the SMTP settings |
Prerequisites
- A Groundmail account with a verified sending domain. A subdomain such as
mail.yourdomain.comworks well. - An API key (
gm_…) from API keys in the dashboard.
1. Send with the API
Add the API key to your service's variables in Railway as GROUNDMAIL_API_KEY, then send with an SDK.
Node.js
npm install groundmailimport { 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, which sends over HTTPS, and keep your sendMail calls.
Python
pip install groundmailimport 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>",
)Django, Rails, Laravel and Symfony have drop-in mail backends that send over HTTPS. Any other language can call POST https://api.groundmail.dev/emails with your key: see the API reference.
2. Keep SMTP: the relay template
For apps that can only send over SMTP, deploy the Groundmail SMTP relay into the same Railway project. Your app sends SMTP to the relay over Railway's private network, which isn't blocked, and the relay delivers each message over HTTPS.
your app ──SMTP (private network)──▶ smtp-relay ──HTTPS──▶ Groundmail- Deploy the Groundmail SMTP Relay template into your project, and paste your API key when it asks for
GROUNDMAIL_API_KEY. The template generates the relay's SMTP password. - In your app's service, set its SMTP settings to the relay with reference variables:
| Setting | Value |
|---|---|
| Host | ${{smtp-relay.RAILWAY_PRIVATE_DOMAIN}} |
| Port | 2525 |
| Encryption | None (traffic stays on the private network) |
| Username | ${{smtp-relay.SMTP_USERNAME}} |
| Password | ${{smtp-relay.SMTP_PASSWORD}} |
| From address | An address on your verified domain |
- Redeploy your app and send a test email. It shows up under Emails in the dashboard with its delivery status.
Bcc, attachments, inline images, Reply-To and custom headers like List-Unsubscribe are kept. If Groundmail rejects a message, for example because the From domain isn't verified, your app gets a 550 with the reason.
The relay's README has the settings for Ghost, n8n, Django and Nodemailer.
On Railway Pro
Pro can reach SMTP servers directly, so you can also use the SMTP relay settings as they are: smtp.groundmail.dev, port 587, STARTTLS, username groundmail and an SMTP credential as the password. Redeploy after upgrading, since the change applies to new deployments.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
ETIMEDOUT or Connection timeout to smtp.groundmail.dev |
SMTP is blocked on your Railway plan | Use the API, or the relay template |
ENOTFOUND smtp-relay.railway.internal |
The relay is in a different project, or has a different service name | Deploy the relay into the same project, and use its service name in the reference variable |
535 Invalid username or password |
The app's password doesn't match the relay's SMTP_PASSWORD |
Use the ${{smtp-relay.SMTP_PASSWORD}} reference variable |
550 … domain is not verified |
The From address isn't on a verified domain | Verify the domain under Domains, or change the From address |