메인 콘텐츠로 이동하기
  1. 서버구축기/

메일서버에 임시메일(Temp Mail) 기능 추가

·4 분·

기존 8도메인 자체 메일서버에, 사용자가 직접 아이디를 정해서 30분만 쓰고 버리는 1회용 수신 전용 이메일(임시메일) 기능을 추가했다. 기존 실계정 운영 환경(virtual_domains/virtual_users)과 완전히 분리된 채로 얹는 게 목표였다. **tempmail.osmsn.com**에서 바로 써볼 수 있다.

사용법 #

이곳을 클릭 하세요. 임시메일 생성화면으로 갑니다.

  1. 아이디 입력 — 영문 소문자와 숫자, ., _, -만 사용해서 3~30자 사이로 원하는 아이디를 입력한다.
  2. 도메인 선택 — 드롭다운에서 도메인을 하나 고른다. (관리자가 활성화해둔 도메인만 목록에 뜬다)
  3. “임시메일 생성” 클릭 — 예: abc123@snscu.com 같은 주소가 즉시 발급된다.
  4. 메일 받기 — 이 주소로 온 메일은 화면에 자동으로 목록이 뜨고(약 15초마다 새로고침), 목록의 메일을 클릭하면 본문을 볼 수 있다.
  5. 필요 없어지면 삭제 — “삭제하고 종료” 버튼을 누르면 즉시 계정과 받은 메일이 전부 지워진다. 안 눌러도 30분 뒤 자동으로 삭제된다.

메일 본문 안의 링크는 클릭해도 바로 안 열린다. 화면 상단에 “이 메일에는 외부 링크가 포함되어 있습니다” 경고 배너가 뜨는데, 여기서 “링크 허용"을 눌러야만 그때부터 링크가 클릭된다. 피싱/악성 링크로부터 보호하기 위한 안전장치이고, 메일을 새로 열 때마다 다시 허용해야 한다.

제한 사항 #

  • 유지 시간: 30분. 생성 시점부터 30분이 지나면 계정과 받은 메일이 전부 자동으로 삭제된다. 연장 기능은 없다.
  • 하루 생성 횟수: IP당 10회. 같은 인터넷 회선에서 하루에 10개 넘게 새로 만들 수 없다. (삭제하고 다시 만들어도 이 카운트는 초기화되지 않는다)
  • 수신 전용이다. 이 주소로 메일을 받을 수만 있고, 이 주소로 메일을 보낼 수는 없다.
  • 삭제하면 절대 복구가 안 된다. 실수로 삭제 버튼을 누르거나 30분이 지나면, 그 안의 메일 내용도 전부 함께 사라지고 어떤 방법으로도 되돌릴 수 없다.
  • 아이디를 아무거나 못 쓴다. admin, root, master 같은 관리자용 이름이나 흔한 예약어는 사용할 수 없다.
  • 같은 아이디 중복 생성 불가. 다른 사람이 지금 쓰고 있는 아이디(도메인 기준)는 못 쓴다. 단, 그 계정이 삭제/만료된 뒤에는 같은 아이디로 다시 만들 수 있다.
  • 중요한 용도로는 쓰지 말 것. 계정 복구, 금융 서비스, 장기 보관이 필요한 곳의 가입 메일로는 적합하지 않다. 30분 안에 확인 못 하면 그 메일은 영영 사라진다.

자주 하는 실수 두 가지: recovery_token(삭제용 토큰)을 따로 저장해두지 않아도 같은 브라우저/기기에서는 자동으로 기억해서 수신함이 계속 보인다(다만 브라우저나 기기를 바꾸면 그 계정엔 다시 접근할 방법이 없다). “서버와 통신할 수 없습니다” 메시지가 뜨면 대부분 하루 생성 한도 초과이거나 일시적인 트래픽 제한이니 잠시 후 다시 시도하면 된다.

최종 기능 정리 #

항목 내용
도메인 관리자가 8개 도메인 중 활성화한 도메인(1개~복수)만 사용자 화면에 노출
계정 생성 사용자가 ID 직접 입력, 예약어(admin/root/master/phoenix/shadow 등) 블랙리스트, 중복 시 거부
유지시간 30분 자동 만료 (systemd timer, 5분 주기 정리)
삭제 recovery token 발급 — 토큰으로만 즉시 삭제 가능
재생성 삭제/만료 후 동일 ID 재사용 가능
발신 완전 차단 (수신 전용)
Rate limit IP당 하루 10회 (삭제해도 리셋 안 되는 별도 로그 테이블 기준)
메일 열람 HTML 안전 렌더링(sanitize) + 링크 클릭 시 별도 허용 배너

아키텍처 #

기존 메일서버는 8도메인이 virtual_domains/virtual_users 테이블로 운영 중이었다. 임시메일도 결국 실제로 메일을 받으려면 dovecot이 인식하는 virtual_users에 계정이 있어야 해서, 계정 생성 시 트랜잭션으로 두 테이블에 동시에 INSERT하는 방식(실제 계정 테이블 + 임시메일 전용 메타데이터 테이블 tempmail_accounts)으로 설계했다.

사용자 → nginx(tempmail 전용 vhost) → php-fpm(tempmail 전용 pool)
                                              ↓
                                    virtual_users + tempmail_accounts (트랜잭션)
                                              ↓
                                    dovecot이 실제 메일 수신
  • nginx/php-fpm은 기존 mailadmin-ui와 완전히 분리된 별도 vhost·pool(전용 시스템 유저 tempmail)
  • 계정 생성 API만 create.php — 조회/삭제/수신함은 inbox.php, read.php, delete.php로 분리
  • 메일함 조회는 파일시스템 직접 접근 대신 doveadm을 sudoers로 좁게 허용한 wrapper 스크립트로 우회 — dovecot 프로토콜 레벨에서만 접근하니 실계정 메일함(phoenix, shadow 등)에 원천적으로 닿을 방법이 없다.

보안 설계 #

한 번에 다 잡은 게 아니라 계속 구멍이 나올 때마다 메웠다:

  • 레이스 컨디션: tempmail_accounts의 UNIQUE 제약 + DB 트랜잭션으로 동시 생성 요청도 안전하게 처리
  • 재생성 시 하루 제한 우회 버그: 처음엔 rate limit을 tempmail_accounts 카운트로 쟀는데, 삭제/만료되면 레코드가 지워지면서 카운트가 리셋돼 무한 생성이 가능했다. 삭제돼도 안 지워지는 별도 로그 테이블(tempmail_creation_log)을 만들어서 해결
  • 삭제 후 재생성 시 옛날 메일이 다시 보이는 버그: DB 레코드만 지우고 실제 Maildir는 안 지워서 생긴 문제. doveadm expunge로 실제 메일함까지 같이 비우도록 수정
  • HTML 메일 렌더링: <script>, 이벤트 핸들러 제거 후 sandboxed iframe(allow-same-origin 없이)에 렌더링. 링크는 클릭해도 바로 안 열리고 “이 메일에는 외부 링크가 있습니다” 배너에서 허용해야만 열리게 (피싱 방지)
  • quoted-printable/base64 미디코딩: doveadm이 raw MIME 본문을 그대로 주다 보니 Sho= p 같은 깨진 텍스트가 나왔다. bodystructure에서 인코딩 방식을 읽어와 직접 디코딩
  • nginx 404가 php-fpm 워커를 소모하던 버그: SPA 스타일로 try_files ... /index.php를 폴백으로 뒀는데, 실제 홈페이지는 index.html이라 존재하지도 않는 index.php를 매번 php-fpm에 넘기고 있었다. 스캐너가 아무 경로나 두드릴 때마다 워커(pm.max_children=5)를 소모하던 걸, =404로 nginx 선에서 바로 끊도록 수정
  • fail2ban: nginx 표준 jail(botsearch/http-auth/limit-req/noscript) + 임시메일 전용 어뷰징 로그 jail + 404 스캐닝 전용 jail까지 계층별로 구성

관리 대시보드 연동 #

기존 Mail Admin 대시보드(mailapi.py + webui)에 임시메일 관련 로그 4종(어뷰징/만료/nginx 접속/에러)과 도메인별 노출 토글, 활성 계정 통계 카드를 추가했다. 별도로 “보안 점검” 탭도 만들어서 SSH 로그인 이력, fail2ban 차단 현황, 크론잡 목록, 최근 수정된 시스템 파일, 연결 세션 등을 버튼 하나로 볼 수 있게 했다 — 침해 여부를 로그 몇 개 뒤지지 않고 한눈에 판단하기 위한 용도.

소감 #

가장 시간을 많이 먹은 건 기능 자체보다 권한/경로 문제였다. php-fpm open_basedir, 파일 소유권, 오래된 마스터 프로세스가 설정 변경을 못 읽고 계속 살아있던 것 등, 서버 하나 붙일 때마다 매번 비슷한 종류의 삽질이 반복됐다. 다음에 비슷한 걸 붙일 땐 이 글을 체크리스트 삼아야겠다.