메일서버에 임시메일(Temp Mail) 기능 추가
목차
기존 8도메인 자체 메일서버에, 사용자가 직접 아이디를 정해서 30분만 쓰고 버리는 1회용 수신 전용 이메일(임시메일) 기능을 추가했다. 기존 실계정 운영 환경(virtual_domains/virtual_users)과 완전히 분리된 채로 얹는 게 목표였다. **tempmail.osmsn.com**에서 바로 써볼 수 있다.
사용법 #
- 아이디 입력 — 영문 소문자와 숫자,
.,_,-만 사용해서 3~30자 사이로 원하는 아이디를 입력한다. - 도메인 선택 — 드롭다운에서 도메인을 하나 고른다. (관리자가 활성화해둔 도메인만 목록에 뜬다)
- “임시메일 생성” 클릭 — 예:
abc123@snscu.com같은 주소가 즉시 발급된다. - 메일 받기 — 이 주소로 온 메일은 화면에 자동으로 목록이 뜨고(약 15초마다 새로고침), 목록의 메일을 클릭하면 본문을 볼 수 있다.
- 필요 없어지면 삭제 — “삭제하고 종료” 버튼을 누르면 즉시 계정과 받은 메일이 전부 지워진다. 안 눌러도 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, 파일 소유권, 오래된 마스터 프로세스가 설정 변경을 못 읽고 계속 살아있던 것 등, 서버 하나 붙일 때마다 매번 비슷한 종류의 삽질이 반복됐다. 다음에 비슷한 걸 붙일 땐 이 글을 체크리스트 삼아야겠다.