SMTP Test: Send a Test Email and Inspect It
Send a message through the sandbox's SMTP server, then open what arrived: headers, MIME parts, raw source. send.smtp.dev only delivers to accounts in your sandbox, so a test email can't reach a real recipient.
Settings
| Setting | Value |
|---|---|
| Host | send.smtp.dev |
| Port | 587, STARTTLS |
| Username | The account's full address |
| Password | The account's password |
| Size | Up to 20 MB per message |
Create two accounts on the Accounts page: one to send from, one to receive. The examples use sender@test.example.com and qa@test.example.com, and read the sender's password from SMTP_PASSWORD.
From the Command Line
swaks
swaks is the usual SMTP test tool: brew install swaks or apt install swaks. It prints the whole SMTP conversation, so a refused login or recipient shows the server's exact reply.
swaks --server send.smtp.dev --port 587 --tls \
--auth-user sender@test.example.com --auth-password "$SMTP_PASSWORD" \
--from sender@test.example.com --to qa@test.example.com \
--header "Subject: SMTP test" --body "Sent with swaks"
curl
curl speaks SMTP, so there's nothing to install. Write the message to message.txt:
From: sender@test.example.com
To: qa@test.example.com
Subject: SMTP test
Sent with curl.
Then send it:
curl smtp://send.smtp.dev:587 --ssl-reqd \
--user "sender@test.example.com:$SMTP_PASSWORD" \
--mail-from sender@test.example.com \
--mail-rcpt qa@test.example.com \
--upload-file message.txt
Add -v to see each command and reply.
By Hand with openssl
Typing the SMTP commands yourself shows exactly where a conversation fails. Port 587 starts TLS before login, which telnet can't do, so connect with openssl:
openssl s_client -starttls smtp -connect send.smtp.dev:587 -crlf -quiet
-crlf sends the line endings SMTP expects. -quiet also stops openssl from reading a line that starts with R as a renegotiation command, which would otherwise swallow RCPT TO.
Build the login token in another terminal:
printf '\0%s\0%s' 'sender@test.example.com' "$SMTP_PASSWORD" | base64
Then type one line at a time and wait for each reply:
EHLO localhost
AUTH PLAIN <token>
MAIL FROM:<sender@test.example.com>
RCPT TO:<qa@test.example.com>
DATA
From: sender@test.example.com
To: qa@test.example.com
Subject: SMTP test
Typed by hand.
.
QUIT
The EHLO reply lists what the server supports, including SIZE, its message size limit in bytes. The line with a single . ends the message.
| Reply | Meaning |
|---|---|
220 | Ready |
250 | Accepted |
235 | Logged in |
354 | Send the message, end it with a line holding only . |
530 | Log in first |
535 | Login failed |
550 | Refused, usually the recipient |
552 | Over the size limit |
From Your App
Point the app's mailer at the sandbox. Sending from code:
import nodemailer from 'nodemailer'
const transport = nodemailer.createTransport({
host: 'send.smtp.dev',
port: 587,
requireTLS: true,
auth: {
user: 'sender@test.example.com',
pass: process.env.SMTP_PASSWORD,
},
})
await transport.sendMail({
from: 'sender@test.example.com',
to: 'qa@test.example.com',
subject: 'SMTP test',
text: 'Sent with Nodemailer',
})
import os
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "sender@test.example.com"
msg["To"] = "qa@test.example.com"
msg["Subject"] = "SMTP test"
msg.set_content("Sent with smtplib")
with smtplib.SMTP("send.smtp.dev", 587) as smtp:
smtp.starttls()
smtp.login("sender@test.example.com", os.environ["SMTP_PASSWORD"])
smtp.send_message(msg)
package main
import (
"net/smtp"
"os"
)
func main() {
from := "sender@test.example.com"
msg := []byte("From: sender@test.example.com\r\n" +
"To: qa@test.example.com\r\n" +
"Subject: SMTP test\r\n" +
"\r\n" +
"Sent with net/smtp\r\n")
auth := smtp.PlainAuth("", from, os.Getenv("SMTP_PASSWORD"), "send.smtp.dev")
err := smtp.SendMail("send.smtp.dev:587", auth, from, []string{"qa@test.example.com"}, msg)
if err != nil {
panic(err)
}
}
Or in your framework's mail settings:
MAIL_MAILER=smtp
MAIL_HOST=send.smtp.dev
MAIL_PORT=587
MAIL_USERNAME=sender@test.example.com
MAIL_PASSWORD=your-account-password
MAIL_FROM_ADDRESS=sender@test.example.com
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "send.smtp.dev"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "sender@test.example.com"
EMAIL_HOST_PASSWORD = os.environ["SMTP_PASSWORD"]
DEFAULT_FROM_EMAIL = "sender@test.example.com"
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "send.smtp.dev",
port: 587,
user_name: "sender@test.example.com",
password: ENV["SMTP_PASSWORD"],
authentication: :plain,
enable_starttls: true
}
spring.mail.host=send.smtp.dev
spring.mail.port=587
spring.mail.username=sender@test.example.com
spring.mail.password=${SMTP_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
Some frameworks can send a test email for you. Django has python manage.py sendtestemail qa@test.example.com. In Laravel, run php artisan tinker, then Mail::raw('Sent with Laravel', fn ($message) => $message->to('qa@test.example.com')->subject('SMTP test'));.
delivery_method = :test in the test environment. Mail sent from those tests never reaches SMTP, so set the SMTP settings on the server your end-to-end tests run against.Read It Back
Open the recipient account in Mail, or log in to webmail.smtp.dev with the account's address and password.
To script it, use the REST API. List the inbox, newest first, then fetch a message by id:
curl "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages" \
-H "X-API-KEY: $SMTP_DEV_API_KEY"
curl "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}/source" \
-H "X-API-KEY: $SMTP_DEV_API_KEY"
The /source endpoint returns the message exactly as it arrived. The API quickstart covers finding the account and mailbox ids.
curl reads mail over IMAP and POP3 too, with the recipient's own credentials:
# UIDs of the messages in the inbox
curl "imaps://imap.smtp.dev/INBOX" --request "UID SEARCH ALL" \
--user "qa@test.example.com:$QA_PASSWORD"
# one message, raw
curl "imaps://imap.smtp.dev/INBOX;UID=1" --user "qa@test.example.com:$QA_PASSWORD"
# the first message over POP3
curl "pop3s://pop.smtp.dev/1" --user "qa@test.example.com:$QA_PASSWORD"
What to Check
- Headers.
From,Reply-To,Date, andMessage-IDare present and say what you intended. - Both parts. A multipart message has a plain text and an HTML part. Read both. The plain text part is often the one nobody looked at.
- Links. They point at the right environment, with absolute URLs. A
localhostlink in a staging email is a common bug. - Attachments. Filename, content type, and size match what the app attached. Size limits covers how encoding grows them.
- Encoding. Non-ASCII names and subjects arrive intact.
When It Fails
| Symptom | Cause |
|---|---|
535 after AUTH | Wrong password, or the username isn't the full address |
| Recipient refused | The address isn't an account in your sandbox |
552 | The encoded message is over 20 MB |
| Connection times out | The network blocks outbound SMTP. Some networks block port 587, most cloud providers block 25 |
| TLS error right after connecting | The client expects implicit TLS (port 465 settings). Use STARTTLS on 587 |
Testing Your Own Server
swaks, curl, and openssl work against any SMTP server: change the host, port, and credentials. The sandbox is useful as the far end. Send through your own server or provider to an address on a domain whose MX points at mx.smtp.dev, and the message arrives with SPF, DKIM, and DMARC results next to the raw source.