🚀 2026 스타트업 컨퍼런스

수천 달러짜리 메일이 증발했다: SMTP 451 에러와 엔지니어의 기본기

수천 달러짜리 메일이 증발했다: SMTP 451 에러와 엔지니어의 기본기

김현수·2026년 1월 3일·3

SMTP 451 에러와 그레이리스팅 기법을 통해 살펴본 시스템 엔지니어링의 기본기. 일시적 오류에 대응하는 재시도 로직과 견고한 시스템 설계의 중요성을 다룹니다.

개발자로 일하다 보면 때로는 코드 한 줄보다 더 거대한 '표준'의 벽에 부딪히거나, 그 표준을 무시했을 때 벌어지는 황당한 사건들을 마주하게 됩니다. 오늘은 꽤 오래전, 제가 메일 서버를 직접 운영하던 시절에 겪었던 에피소드를 하나 들려드릴까 합니다. 한 소프트웨어 공급업체가 고객들에게 수천 달러에 달하는 라이선스 키를 이메일로 발송하고 있었습니다. 그런데 이 비싼 라이선스 키가 담긴 메일이 저희 회사를 포함한 일부 고객에게 전혀 도착하지 않는 사고가 발생했습니다. 공급업체는 대뜸 저희 쪽 스팸 필터가 잘못되었다며 항의를 해왔습니다. "당신네 시스템이 우리 메일을 막고 있어서 업무에 차질이 생기고 있다"는 것이었죠. 하지만 로그를 뜯어보고 나서 저는 그들의 주장이 완전히 틀렸을 뿐만 아니라, 그들이 엔지니어링의 기본 원칙을 간과하고 있다는 사실을 깨달았습니다.

문제의 핵심에는 'Greylisting(그레이리스팅)'이라는 스팸 차단 기법과 SMTP 프로토콜의 451번 에러 코드가 있었습니다. 그레이리스팅은 일종의 '문지기' 역할을 합니다. 낯선 IP 주소에서 메일이 들어오면 서버는 즉시 받지 않고 "451 Temporary Failure(일시적 오류)"라는 응답을 보냅니다. 쉽게 말해 "지금 바쁘니까 잠시 후에 다시 오세요"라고 정중하게 거절하는 것입니다. 정상적인 메일 서버라면 이 메시지를 받고 일정 시간 대기 큐(Queue)에 메일을 보관했다가 잠시 후 재전송을 시도합니다. 반면, 수많은 메일을 무차별적으로 뿌려야 하는 스팸 봇들은 이런 '재시도' 과정을 거치지 않고 다음 타겟으로 넘어가 버립니다. 이 단순하지만 강력한 차이를 이용해 스팸을 걸러내는 것이 바로 그레이리스팅의 원리입니다.

그런데 로그를 확인해 보니, 그 공급업체의 발송 소프트웨어는 이 451 에러를 받자마자 재시도는커녕, 해당 메일을 즉시 폐기해 버리고 있었습니다. 인터넷 이메일 전송 표준인 RFC2821에 따르면, 메일 발송 프로그램은 일시적인 전송 실패 시 반드시 큐에 저장하고 주기적으로 재시도해야 합니다. 일반적으로 재시도 간격은 최소 30분, 포기하기까지는 4~5일을 권장합니다. 하지만 이 업체의 소프트웨어는 1초도 안 되는 시간에 연결을 시도하고, 실패 응답이 오자마자 수천 달러짜리 라이선스 키가 담긴 데이터를 허공으로 날려버린 것입니다. 그들은 고객의 소중한 자산을 마치 쓰고 버리는 스팸 메일처럼 취급하고 있었습니다.

우리는 흔히 인터넷 서비스를 'Best Effort(최선형)' 서비스라고 부릅니다. 네트워크는 언제든 끊길 수 있고, 서버는 언제든 내려갈 수 있다는 전제하에 동작합니다. 그렇기 때문에 신뢰성 있는 시스템을 구축하기 위해서는 '결함 허용(Fault Tolerance)' 설계가 필수적입니다. 이메일뿐만 아니라 우리가 요즘 흔히 다루는 REST API나 메시지 큐 시스템에서도 마찬가지입니다. 상대방 서버가 503 Service Unavailable이나 429 Too Many Requests 응답을 보냈을 때, 여러분의 클라이언트는 어떻게 반응합니까? 예외를 뱉고 프로세스를 죽여버리나요, 아니면 지수 백오프(Exponential Backoff)를 사용하여 우아하게 재시도하나요? 이 차이가 시스템의 격을 결정합니다.

당시 그 공급업체는 자신들의 소프트웨어가 SMTP 상태 코드를 제대로 이해하지 못한다는 사실을 인정하고, 결국 재전송 로직을 수정했습니다. 이 사건은 저에게도 큰 교훈을 남겼습니다. 우리가 만드는 애플리케이션이 외부 시스템과 통신할 때, 상대방이 항상 "200 OK"만을 돌려줄 것이라고 순진하게 믿어서는 안 된다는 점입니다. "잠시 후에 다시 오세요"라는 451 에러는 단순한 거절이 아니라, 우리 시스템이 얼마나 견고한지를 테스트하는 리트머스 시험지였던 셈입니다. 데이터의 가치가 클수록, 그 데이터를 전달하는 파이프라인은 더욱 끈기 있고 신중하게 설계되어야 합니다.

여러분도 혹시 외부 API 연동이나 데이터 전송 로직을 짤 때, '한 번 실패하면 끝'인 구조로 만들고 있지는 않은지 돌아보셨으면 합니다. 진정한 엔지니어링은 모든 것이 순조로울 때가 아니라, 예상치 못한 거절을 마주했을 때 그 진가를 발휘하니까요. 잠깐의 실패를 영구적인 손실로 만들지 않는 것, 그것이 우리가 지향해야 할 견고한 시스템의 기본일 것입니다.

김현수
김현수10년 차 시니어 개발자

복잡한 기술을 누구나 이해하기 쉽게 풀어내는 것을 즐깁니다. 10년의 개발 여정에서 얻은 인사이트와 시행착오를 솔직하게 공유합니다.

김현수님의 다른 글

댓글 0

첫 번째 댓글을 남겨보세요!