SMTP 550 5.1.1: What Recipient Address Rejected Means
550 5.1.1 at the RCPT TO stage is a permanent recipient-address failure. The receiving server is saying that it cannot deliver to the specified destination mailbox. Retrying the same address unchanged is not the right response.
C: MAIL FROM:<sender@example.net>
S: 250 2.1.0 Sender OK
C: RCPT TO:<missing@example.com>
S: 550 5.1.1 Recipient address rejected: User unknown
The exact command, three-digit reply, enhanced status code, and response text all matter. A different 550, such as 5.7.1, can indicate a policy or security rejection instead of a missing recipient.
Read the response in four pieces
RCPT TOidentifies the stage. The server is evaluating the envelope recipient.550is a permanent negative completion reply under SMTP.5.1.1is the enhanced status for a bad destination mailbox address.User unknownis human-readable detail. Keep it in diagnostics, but automate from the codes.
RFC 5321 section 4.2.3 defines the persistent and transient reply classes. RFC 3463 section 3.2 defines X.1.1 as a bad destination mailbox address.
Preserve the complete transcript when diagnosing a delivery or verification result. A bare “550 error” does not say whether the rejection concerned the recipient, relay authorization, message policy, sender authentication, or another condition.
Parse the reply without treating every 550 alike
Create smtp-reply.mjs:
const replyPattern = /^(?<code>\d{3})[ -](?:(?<enhanced>[245]\.\d{1,3}\.\d{1,3})\s+)?(?<message>.*)$/
export function parseSmtpReply(line) {
const match = replyPattern.exec(line.trim())
if (!match?.groups) throw new Error('Invalid SMTP reply line')
return {
code: Number(match.groups.code),
enhanced: match.groups.enhanced ?? null,
message: match.groups.message
}
}
export function classifyRecipientReply(line) {
const reply = parseSmtpReply(line)
if (reply.code === 550 && reply.enhanced === '5.1.1') {
return {
outcome: 'recipient_rejected',
retry: false,
evidence: reply
}
}
if (reply.code >= 400 && reply.code < 500) {
return {
outcome: 'temporary_failure',
retry: true,
evidence: reply
}
}
if (reply.code >= 500 && reply.enhanced?.startsWith('5.7.')) {
return {
outcome: 'policy_rejection',
retry: false,
evidence: reply
}
}
if (reply.code >= 500) {
return {
outcome: 'permanent_failure',
retry: false,
evidence: reply
}
}
if (reply.code >= 200 && reply.code < 300) {
return {
outcome: 'accepted_at_this_stage',
retry: false,
evidence: reply
}
}
return {
outcome: 'unclassified',
retry: false,
evidence: reply
}
}
The function deliberately says accepted_at_this_stage for a 2xx response. SMTP acceptance during a probe or delivery transaction does not guarantee inbox placement, later delivery, or recipient engagement.
Test recipient, temporary, and policy failures
Create smtp-reply.test.mjs:
import assert from 'node:assert/strict'
import test from 'node:test'
import { classifyRecipientReply } from './smtp-reply.mjs'
test('550 5.1.1 is a permanent recipient rejection', () => {
const result = classifyRecipientReply(
'550 5.1.1 Recipient address rejected: User unknown'
)
assert.equal(result.outcome, 'recipient_rejected')
assert.equal(result.retry, false)
})
test('450 4.1.1 remains temporary', () => {
const result = classifyRecipientReply(
'450 4.1.1 Recipient temporarily unavailable'
)
assert.equal(result.outcome, 'temporary_failure')
assert.equal(result.retry, true)
})
test('550 5.7.1 is not mislabeled as a missing mailbox', () => {
const result = classifyRecipientReply(
'550 5.7.1 Message rejected by policy'
)
assert.equal(result.outcome, 'policy_rejection')
})
Run node --test.
Decide whether to retry, correct, or remove
| Evidence | Default action | Reason |
|---|---|---|
550 5.1.1 at RCPT TO | Ask for a corrected address or suppress the recipient. | The destination mailbox was permanently rejected. |
450 4.x.x | Retry later with a bounded schedule. | A 4xx response is transient. |
550 5.7.x | Investigate policy, authentication, reputation, or content context. | The enhanced code is not a bad-mailbox code. |
| Timeout or connection failure | Keep the mailbox result unknown and retry according to policy. | No recipient response was received. |
One server can still be misconfigured, and a mailbox can be restored later. “Permanent” describes how SMTP tells the current sender to handle this transaction. Keep the timestamp and server response so a later correction or re-verification can replace the old evidence.
Map SMTP evidence to an API result
You do not need to run an SMTP probe from an application server. Outbound port restrictions, provider defenses, catch-all behavior, and temporary policy blocks make a homegrown boolean unreliable.
Create an emailverifier.dev account and project, then copy its API key. Make one address request:
curl https://emailverifier.dev/api/v1/verify \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_PROJECT_KEY" \
-d '{"email":"person@example.com"}'
A clear recipient rejection can support status: undeliverable and action: block with a mailbox_rejected signal. A temporary failure, timeout, catch-all response, or policy block may instead remain status: unknown and action: review.
The complete API status set is deliverable, risky, undeliverable, and unknown. Application actions are allow, review, and block. The SMTP verification guide owns the full command sequence; this page owns the narrower 550 5.1.1 diagnosis.