SMTP
Send through smtp.letr.dev from tools that only speak SMTP, with an API key as the password.
For a framework, an auth provider or an app that can only send over SMTP. An email sent over SMTP is an email like any other: it shows up in the API and the dashboard with the same events and webhooks.
Connection
| Setting | Value |
|---|---|
| Host | smtp.letr.dev |
| Port | 465 with implicit TLS, or 587 with STARTTLS (required) |
| Username | letr |
| Password | An API key with sending_access or full_access |
| Authentication | PLAIN or LOGIN |
The key's domain limits apply: a key limited to some domains sends only from those. Use a separate key per tool, so you can roll or revoke one without touching the others. Revoking a key stops open sessions that use it at their next message.
Same rules as the API
A message sent over SMTP goes through the same path as POST /v1/emails: a verified from-domain, the suppression list, 50 recipients and 15 MB per email as sent, measured the same way as on the API, your monthly ceiling, the daily limit and test recipients all apply, and letr.dev adds Feedback-ID and signs with DKIM.
letr.dev keeps your MIME structure and your Message-ID, and turns line endings into CRLF. Before signing, it removes Bcc, Feedback-ID and any other X-Letr-* header than the two below.
Limits
Each key may submit 600 messages a minute over SMTP, a budget of its own next to the account's 600 API requests a minute; above it a MAIL FROM answers 451 4.7.1. Each client network may attempt as many logins a minute as the API's per-minute limit allows, successful ones included (454 4.7.0 above that). A client address holds at most 5 connections at once, each port 200; a session has 30 seconds to authenticate and ends after 10 minutes. Keep a session open and send several messages through it rather than reconnecting per message.
letr.dev headers
X-Letr-Tag: key=valuesets a tag, astagsdoes in the API. Repeat the header for more tags, at most 10.X-Letr-Idempotency-Keyhas the same meaning as theIdempotency-KeyHTTP header.
X-Letr-Tag: type=password-reset
X-Letr-Tag: app=web
X-Letr-Idempotency-Key: reset-7f3a2c
Reply codes
When letr.dev refuses a command or a message, the SMTP reply says why:
530:AUTHorMAILbeforeSTARTTLSon port 587, orMAILwithoutAUTH.535 5.7.8: unknown, revoked orread_onlykey.454 4.7.0: too many logins a minute from your network, successful ones included; see Limits and try again later.550 5.7.1: the from-domain is not verified, or outside the key's domain limit.550 5.1.1: the recipient address is malformed, or the recipient is on the suppression list; only that recipient is refused.452 4.5.3: more than 50 recipients.552 5.3.4: the message is larger than 15 MB as sent, with the headers and DKIM signature letr.dev adds.554 5.6.0: the message is malformed,Fromis missing or not exactly one address, anX-Letr-Tagis invalid, or theX-Letr-Idempotency-Keywas already used with a different message.451 4.7.1: the monthly ceiling or the daily limit is reached, or too many requests; try again later.451 4.3.0: a temporary internal error; try again later.554 5.7.0: the account is not active (payment pending or disabled) or paused.
Codes starting with 4 are temporary, and most tools retry them on their own.
Setup
Most examples use port 587 with STARTTLS. Put the API key in a secret store or an environment variable, never in the code.
Supabase Auth
In the Supabase dashboard, open the Auth SMTP settings (/dashboard/project/_/auth/smtp), enable custom SMTP and enter the host, port, username and password from above. The same with the Supabase Management API:
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"smtp_admin_email": "[email protected]",
"smtp_sender_name": "Acme",
"smtp_host": "smtp.letr.dev",
"smtp_port": "587",
"smtp_user": "letr",
"smtp_pass": "'"$LETR_API_KEY"'"
}'
Django
# settings.py
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.letr.dev"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "letr"
EMAIL_HOST_PASSWORD = os.environ["LETR_API_KEY"]
DEFAULT_FROM_EMAIL = "Acme <[email protected]>"
For port 465 set EMAIL_USE_SSL = True instead of EMAIL_USE_TLS; Django allows only one of the two.
Rails
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.letr.dev",
port: 587,
user_name: "letr",
password: ENV.fetch("LETR_API_KEY"),
authentication: :plain,
enable_starttls: true
}
Spring Boot
# application.properties
spring.mail.host=smtp.letr.dev
spring.mail.port=587
spring.mail.username=letr
spring.mail.password=${LETR_API_KEY}
spring.mail.properties[mail.smtp.auth]=true
spring.mail.properties[mail.smtp.starttls.enable]=true
spring.mail.properties[mail.smtp.starttls.required]=true
Host, port, username and password are Spring Boot's own spring.mail.* properties. The mail.smtp.* keys for login and STARTTLS are Jakarta Mail properties, which Spring Boot passes to the mail session through spring.mail.properties.
WordPress
WordPress needs an SMTP plugin. In WP Mail SMTP, choose the mailer Other SMTP and set:
- SMTP Host:
smtp.letr.dev - Encryption: TLS
- SMTP Port:
587 - Authentication: on
- SMTP Username:
letr - SMTP Password: your API key
For port 465 choose the encryption SSL.
Nextcloud
Nextcloud, here on port 465 with implicit TLS:
// config/config.php
'mail_smtpmode' => 'smtp',
'mail_smtphost' => 'smtp.letr.dev',
'mail_smtpport' => 465,
'mail_smtpsecure' => 'ssl',
'mail_smtpauth' => true,
'mail_smtpname' => 'letr',
'mail_smtppassword' => 'letr_your_api_key',
'mail_from_address' => 'no-reply',
'mail_domain' => 'mail.acme.dev',