Intermediate 10 min SMTP 503

503 Bad Sequence of Commands — AUTH Before STARTTLS

Triệu chứng

- SMTP client fails with "503 5.5.1 Error: send EHLO/HELO first" or "503 Bad sequence of commands"
- Server rejects AUTH because STARTTLS was not negotiated first
- Client receives "503 AUTH command is not permitted when TLS is not active" before any auth attempt
- PHPMailer, Nodemailer, or smtplib throws a 503 exception mid-session
- SMTP debug log shows AUTH being attempted immediately after EHLO without a STARTTLS upgrade

Nguyên nhân gốc rễ

  • Client sending AUTH before issuing STARTTLS when the server requires encryption first
  • SMTP command ordering wrong: MAIL FROM sent before EHLO, or DATA before RCPT TO
  • Client library defaulting to plain text and not following the STARTTLS extension advertised in EHLO
  • Confusion between implicit TLS (port 465, wrap TLS from the start) and STARTTLS (port 587, upgrade within session)
  • Server requiring AUTH but client going straight to MAIL FROM after EHLO

Chẩn đoán

**Step 1 — Understand the correct SMTP session flow**

```
Client → Server
220 mail.example.com ESMTP ready
EHLO client.example.com ← 1. Introduce yourself
250-STARTTLS ← Server advertises STARTTLS
250 AUTH LOGIN PLAIN
STARTTLS ← 2. Upgrade to TLS (BEFORE AUTH)
220 Go ahead
# ... TLS handshake ...
EHLO client.example.com ← 3. Re-introduce after TLS
AUTH LOGIN ← 4. Authenticate (after TLS)
MAIL FROM:<[email protected]> ← 5. Begin message
RCPT TO:<[email protected]>
DATA
QUIT
```

**Step 2 — Trace the actual SMTP session**

```bash
# Use openssl to observe the full session
openssl s_client -connect smtp.example.com:587 -starttls smtp
# Watch for where the 503 appears in the sequence
```

**Step 3 — Check which port you're using**

| Port | Protocol | TLS mode | Use case |
|------|----------|----------|----------|
| 25 | SMTP | Optional STARTTLS | MTA-to-MTA relay |
| 587 | Submission | Mandatory STARTTLS | Client submission (recommended) |
| 465 | SMTPS | Implicit TLS | Legacy, still widely supported |

**Step 4 — Enable SMTP debug logging in your library**

```python
import smtplib
server = smtplib.SMTP('smtp.example.com', 587)
server.set_debuglevel(1) # prints all SMTP commands and responses
server.ehlo()
server.starttls()
server.ehlo() # must EHLO again after STARTTLS
server.login('user', 'password')
```

Giải quyết

**Fix 1 — Ensure STARTTLS is issued before AUTH**

```python
import smtplib, ssl
context = ssl.create_default_context()

# Port 587 with STARTTLS (correct sequence)
with smtplib.SMTP('smtp.example.com', 587) as server:
server.ehlo()
server.starttls(context=context)
server.ehlo() # must re-EHLO after TLS upgrade
server.login('[email protected]', 'app-password')
server.sendmail(from_addr, to_addrs, message)
```

**Fix 2 — Switch to implicit TLS on port 465 if supported**

```python
# Port 465 wraps TLS immediately — no STARTTLS command needed
with smtplib.SMTP_SSL('smtp.example.com', 465, context=context) as server:
server.ehlo()
server.login('[email protected]', 'app-password')
server.sendmail(from_addr, to_addrs, message)
```

**Fix 3 — Django EMAIL settings for correct TLS mode**

```python
# settings.py — port 587 STARTTLS
EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True # STARTTLS on port 587
EMAIL_USE_SSL = False # do not set both to True

# settings.py — port 465 implicit TLS
EMAIL_PORT = 465
EMAIL_USE_TLS = False
EMAIL_USE_SSL = True # wraps connection in SSL from the start
```

Phòng ngừa

- Always issue EHLO, then STARTTLS, then a second EHLO before AUTH — the re-EHLO after TLS is mandatory
- Use port 587 (STARTTLS) for client submission; prefer a library that handles the sequence automatically
- Enable SMTP debug logging in development to catch sequence errors early
- Test your SMTP config with openssl before deploying — it shows the full conversation and makes ordering errors obvious
- Avoid mixing EMAIL_USE_TLS and EMAIL_USE_SSL in Django — only one should be True depending on the port

Mã trạng thái liên quan

Thuật ngữ liên quan