← 블로그 목록
이메일 발송SPFDKIM스팸백엔드

웹사이트에 이메일 발송 기능 붙이기: 도착률까지 챙기는 법

서버에서 직접 보내면 안 되는 이유, 도착률을 좌우하는 SPF·DKIM·DMARC 설정, 광고성 메일 규칙과 반송 처리까지 정리했습니다.

Vibeollio 팀-

메일은 보내는 것보다 도착하는 게 어렵다

가입 인증, 비밀번호 재설정, 알림. 서비스를 만들면 메일을 보낼 일이 생깁니다. 코드로 보내는 것 자체는 몇 줄이면 되는데, 정작 문제는 보낸 메일이 받는 사람 받은편지함에 도착하느냐입니다.

개발 중에는 잘 되다가 실제 사용자에게 보내기 시작하면 스팸함으로 들어가거나 아예 차단됩니다. 원인은 대부분 정해져 있습니다.

서버에서 직접 보내면 안 되는 이유

nodemailer로 SMTP를 붙이면 되지 않냐고 생각하기 쉽습니다. 기술적으로는 됩니다. 문제는 받는 쪽입니다.

메일 서비스들은 발신 서버의 평판을 봅니다. 처음 보는 IP에서 갑자기 메일이 오면 일단 의심합니다. 클라우드 서버의 IP 대역은 스팸 발송에 자주 쓰여 처음부터 불리한 경우가 많고, 아예 25번 포트를 막아둔 제공자도 있습니다.

그래서 현실적인 선택은 메일 발송 전문 서비스를 쓰는 것입니다. Resend, SendGrid, Mailgun, Amazon SES 같은 것들이 있습니다. 평판 관리를 대신해주고, 발송 결과와 반송 정보를 알려줍니다. 개인 프로젝트 규모에서는 대체로 무료 구간 안에서 해결되는데, 한도와 조건은 자주 바뀌므로 쓰려는 서비스에서 직접 확인하세요.

도착률을 결정하는 건 도메인 인증이다

여기가 가장 중요한데 가장 많이 건너뜁니다. 서비스에 가입하고 API 키만 받아서 보내면 대체로 스팸함으로 갑니다.

받는 쪽이 확인하는 것은 **"이 메일이 정말 그 도메인에서 보낸 게 맞는가"**입니다. 이를 증명하는 장치가 셋입니다.

SPF — "내 도메인 이름으로 메일을 보낼 수 있는 서버는 이것들이다"를 DNS에 적어두는 것입니다.

DKIM — 보내는 메일에 서명을 붙이고, 검증용 공개키를 DNS에 둡니다. 내용이 중간에 바뀌지 않았음도 함께 증명됩니다.

DMARC — 위 둘이 실패했을 때 어떻게 처리할지(무시/격리/거부)를 정하고, 결과 보고서를 받을 주소를 지정합니다.

셋 다 DNS에 레코드를 추가하는 일입니다. 발송 서비스가 넣어야 할 값을 알려주므로, 도메인 관리 화면에서 그대로 등록하면 됩니다. 반영에는 시간이 걸릴 수 있습니다.

이걸 안 하면 도착률이 눈에 띄게 나빠집니다. 반대로 이것만 제대로 해도 대부분 해결됩니다.

발신 주소

noreply@내도메인.com처럼 내 도메인 주소로 보내야 합니다. 개인 메일 주소를 발신자로 쓰면 인증이 안 되어 스팸 판정을 받습니다.

그리고 회신 주소를 따로 지정할 수 있습니다. 발신은 noreply로 하더라도 회신은 실제로 읽는 주소로 받아두면, 사용자가 답장했을 때 허공으로 사라지지 않습니다.

광고성 메일은 규칙이 다르다

가입 인증이나 비밀번호 재설정 같은 거래성 메일과, 소식·프로모션 같은 광고성 메일은 취급이 다릅니다.

국내에서 광고성 정보를 보내려면 수신 동의를 받아야 하고, 제목에 광고임을 표시해야 하며, 수신 거부 방법을 본문에 안내해야 합니다. 야간 발송에도 제한이 있습니다. 구체적인 요건은 관련 법령과 지침을 확인하세요.

실무적으로는 처음부터 두 종류를 분리해서 설계하는 것이 편합니다. 동의 여부를 따로 저장하고, 발송 코드도 나눠두면 나중에 섞이지 않습니다.

보내는 방식: 요청 안에서 보내지 않기

메일 발송은 외부 서비스 호출이라 느리고 실패할 수 있습니다. 회원가입 처리 중에 메일을 기다리면 응답이 늦어지고, 메일 발송이 실패하면 가입까지 실패합니다.

그래서 작업을 저장해두고 따로 처리하는 방식이 맞습니다.

// 가입 처리는 즉시 끝내고
await db.insert(users).values(newUser);
await enqueue("send-welcome-email", { userId: newUser.id });
return Response.json({ ok: true });

여기서 await 없이 발송 함수를 호출하고 응답을 먼저 반환하는 방식은 쓰지 마세요. 서버리스 환경에서는 응답 후 실행이 보장되지 않아 메일이 조용히 사라집니다.

그리고 재시도가 있으면 중복 발송이 생깁니다. 작업마다 고유 키를 두고 이미 보낸 것인지 확인하는 장치를 넣으세요. 인증 메일이 세 통씩 가면 사용자는 뭔가 잘못됐다고 느낍니다.

반송과 수신 거부 처리하기

없는 주소로 계속 보내면 발신 평판이 나빠집니다. 발송 서비스가 반송 정보를 웹훅으로 알려주므로, 받아서 처리해두세요.

  • 하드 바운스(없는 주소) → 해당 주소로 더 보내지 않도록 표시
  • 스팸 신고 → 즉시 발송 대상에서 제외
  • 수신 거부 → 광고성 발송에서 제외

이 처리를 안 하면 시간이 지날수록 도착률이 떨어집니다.

개발 중에 실수로 실제 사용자에게 가면 어떻게 하나요?

개발·스테이징 환경에서는 실제 발송을 막아두는 것이 안전합니다. 방법은 두 가지입니다. 발송 서비스의 테스트 모드를 쓰거나, 환경변수로 발송을 끄고 로그에만 남기는 것입니다. 운영 데이터를 복사해서 개발 환경에 넣는 경우 특히 위험하니, 발송 전에 환경을 확인하는 조건을 코드에 넣어두세요.

메일이 스팸함으로 가는데 원인을 어떻게 찾나요?

받은 메일의 원문 보기에서 헤더를 확인하면 SPF·DKIM·DMARC 검증 결과가 나옵니다. 여기서 fail이 보이면 DNS 설정 문제입니다. 셋 다 통과인데도 스팸으로 간다면 내용 쪽을 봐야 합니다. 링크가 지나치게 많거나, 이미지만 있고 텍스트가 거의 없거나, 단축 URL을 쓴 경우가 불리합니다.

무료 서비스로 시작해도 되나요?

개인 프로젝트라면 충분합니다. 다만 무료 구간에서는 발송량 제한이 있고, 서비스에 따라 공용 발신 IP를 쓰게 되어 다른 사용자의 발송 품질에 영향을 받을 수 있습니다. 어느 쪽이든 도메인 인증은 무료 구간에서도 할 수 있으니 반드시 하세요. 유료로 올리는 것보다 그게 도착률에 훨씬 큰 영향을 줍니다.

자기에게 먼저 보내보기

네이버·구글·다음 계정으로 각각 보내보면 어디서 스팸으로 분류되는지 바로 보입니다. 서비스마다 판정이 다르므로 한 곳만 확인하면 안 됩니다.

메일 기능이 들어간 프로젝트를 만들었다면 Vibeollio에 프로젝트 등록해서 공개해보세요. 실제 사용자가 가입해봐야 메일이 제대로 도착하는지 확인됩니다.