Files

60 KiB

AriMail(mail.arihakdang.com) 설치·운영

1.0.0 부터 프로젝트 이름은 AriMail, 저장소는 https://source.ibit.kr/OZ-Group/AriMail 입니다(내용은 같고 이름만 바뀜). 운영 설정(Resend 키 등)은 환경 파일이 아니라 설정 파일 + 초기 설정 페이지(10절)에서 다루고, 수·발신은 Resend 또는 내장 Haraka(12절)로 할 수 있습니다.

www.arihakdang.com 과 같은 서버(187.53.133.173), 같은 실행 계정(arihakdang), 같은 Node(/opt/arihakdang-node) 에서 돌아갑니다. 관리자 계정은 www 의 users 테이블을 읽기 전용으로 읽어 인증하므로 별도 계정·비밀번호가 없습니다. 사용자 이름 admin / admin@arihakdang.com 은 메일함 소유 관리자의 별칭입니다. 소유자는 MAILBOX_OWNER_LOGIN(www 로그인 ID 또는 이메일) → 이메일이 메일함 주소인 관리자 → 유일한 활성 관리자 순으로 정합니다. 관리자가 둘 이상이면 MAILBOX_OWNER_LOGIN 을 꼭 지정하세요.

Resend 수신(admin@arihakdang.com)
   ├─ 웹훅 email.received ──▶ POST https://mail.arihakdang.com/api/webhooks/resend  (즉시)
   └─ GET /emails/receiving ──▶ 1시간마다 동기화                                       (보충)
                                     │  원본(.eml)·첨부·정제 본문을 SQLite 에 기한 없이 보관
                                     ├─ 웹 메일함 https://mail.arihakdang.com/  (보기·답장·발송)
                                     ├─ IMAP mail.arihakdang.com:993 (TLS) / 143 (STARTTLS)
                                     ├─ POP3 mail.arihakdang.com:995 (TLS) / 110 (STLS 또는 일반 접속)
                                     └─ SMTP 제출 mail.arihakdang.com:465 (TLS) / 587 (STARTTLS)
                                          로그인 비밀번호로 인증 → 서버 내부 RESEND_API_KEY 로 Resend API 발송

1. DNS 와 인증서

  1. Cloudflare DNS 에 mail A 레코드 → 187.53.133.173, 프록시 끔(DNS only, 회색 구름). POP3 포트(110/995)는 Cloudflare 프록시를 지나지 못하고, 메일 클라이언트는 공개 CA 인증서를 요구합니다. 그래서 이 호스트만 원본 IP 가 공개됩니다. www 의 Cloudflare 전용 차단(444)은 이 vhost 에 적용하지 않습니다.
  2. 서버에서 인증서 발급:
    sudo apt install -y certbot
    sudo mkdir -p /var/www/letsencrypt
    sudo certbot certonly --webroot -w /var/www/letsencrypt -d mail.arihakdang.com
    
    발급 후 sudo bash deploy/setup.sh 를 다시 실행하면 갱신 훅(deploy/letsencrypt-deploy-hook.sh.in 에서 만든 /etc/letsencrypt/renewal-hooks/deploy/arihakdang-mail.sh)이 인증서 사본을 /etc/arihakdang-mail/tls/ 에 서비스 계정 권한으로 만들고, nginx(443)와 POP3(995/STLS)가 같은 인증서를 씁니다. certbot 이 갱신할 때마다 훅이 사본을 갱신하고 서비스를 재시작합니다. (프로세스도 1시간마다 인증서 파일을 다시 읽으므로 재시작 없이도 새 인증서가 적용됩니다.)
  3. 방화벽에 110, 995(POP3)와 143, 993(IMAP) TCP 를 엽니다. 80/443 은 www 와 공유합니다.

2. 서버 준비와 배포

sudo bash deploy/setup.sh                      # 디렉터리, 환경 파일(배포에 필요한 값만), systemd, nginx, 내장 Haraka 준비
sudo -u arihakdang /srv/arihakdang-mail/repo/deploy/deploy.sh   # 끝에 "초기 설정 페이지" 주소(토큰 포함)가 출력됩니다
journalctl -u arihakdang-mail -n 50 --no-pager

1.0.0 부터 환경 파일에 Resend 키를 적지 않습니다. 배포가 끝나면 출력된 초기 설정 페이지 주소(http(s)://<서버>:8791/?token=…)를 열어 수·발신 방식과 키를 채우세요(10절). 값이 비어 있어도 배포는 실패하지 않고, 저장하면 재시작 없이 적용됩니다.

배포 스크립트는 git reset --hard origin/main → 테스트 → releases/<시각> 복사 → DB 백업·마이그레이션 → current 링크 교체 → 재시작 순서이며, 재시작에 실패하면 이전 릴리스로 되돌립니다. 서버에는 source.ibit.kr 자격 증명을 두지 않습니다. 작업 PC 에서 번들을 만들어 올리고 번들로 배포합니다.

git bundle create /tmp/mail-gateway.bundle main && scp /tmp/mail-gateway.bundle hostinger:/tmp/
ssh hostinger 'sudo install -o arihakdang -g arihakdang -m 644 /tmp/mail-gateway.bundle /srv/arihakdang-mail/source.bundle && sudo -u arihakdang env SOURCE_BUNDLE=/srv/arihakdang-mail/source.bundle /srv/arihakdang-mail/repo/deploy/deploy.sh'

배포 스크립트의 재시작에는 sudoers 규칙이 필요합니다(setup.sh 가 /etc/sudoers.d/arihakdang-mail 로 만듭니다).

deploy/ 의 systemd 유닛·nginx·certbot 훅·환경 파일은 0.8.18 부터 .in 템플릿이며, setup.sh 가 사이트 설정(deploy/sites/<이름>.env, 기본 arihakdang)으로 채워 설치합니다. 다른 도메인에 설치하는 방법은 8절, 설치 옵션(로그인 방식 LOGIN_MODE, 시스템 제한 LIMIT_*)은 9절을 보세요.

3. Resend 웹훅

  1. Resend 대시보드 → Webhooks → Add Endpoint
    • URL: https://mail.arihakdang.com/api/webhooks/resend
    • Events: email.received
  2. 만들어진 엔드포인트의 Signing Secret(whsec_…) 을 초기 설정 페이지(또는 /admin → 메일 서비스 설정)의 Resend 카드 "웹훅 서명 비밀키" 에 넣고 저장합니다(재시작 불필요, 1.0.0). (0.9.x 까지는 gateway.env 의 RESEND_WEBHOOK_SECRET 이었습니다. 남아 있는 값은 1.0.0 첫 시작 때 설정 파일로 가져갑니다.)
  3. 웹훅은 본문을 담지 않으므로 게이트웨이가 GET /emails/receiving/{id} 로 전문·헤더·첨부·원본(raw) 을 받아 저장합니다. 서명이 없거나 틀리면 401, 저장에 실패하면 500 을 돌려주어 Resend 가 재시도합니다.
  4. 웹훅과 무관하게 1시간마다 수신·발송 목록을 확인해 빠진 메일을 보충합니다(관리자 페이지 → 서버 상태의 "지금 동기화"로 즉시 실행). 한 번에 받은 메일 500건·보낸 메일 200건(최신순)까지 거슬러 올라갑니다. 웹 메일함 목록을 열어 두면 1분마다 빠른 확인을 합니다. 최신 메일부터 보다가 이미 저장한 메일을 만나면 멈추므로 Resend 호출이 한두 번에 그칩니다.

4. 보관·중복 방지

  • 받은 메일과 보낸 메일은 기한 없이 보관합니다(0.8.0 에서 30일 제한과 RETENTION_DAYS 설정을 없앴습니다). 메일은 휴지통에서 영구 삭제할 때만 원본·첨부와 함께 지워지고, Resend 목록에 남아 있어도 다시 가져오지 않습니다(tombstone).
  • 저장 공간은 SQLite 파일(/srv/arihakdang-mail/data/mail-gateway.sqlite)이 계속 커지므로 디스크 사용량을 가끔 확인하세요. 왼쪽 패널 아래에 메일함별 사용량이 보입니다.
  • POP3 로 RETR(내려받기) 하거나 클라이언트가 DELE 한 메일은 pop_deliveries 에 기록되어 같은 계정에 다시 내려주지 않습니다. 서버 저장본은 유지되어 웹에서 계속 볼 수 있고, 필요하면 메일 상세의 "POP 전달 기록 초기화"로 다시 내려받게 할 수 있습니다.
  • 계정마다 따로 기록하므로 관리자가 두 명이면 각자 한 번씩 받습니다.

4a. 웹 메일함 사용법

  • 왼쪽 패널: 메일 쓰기 버튼, 받은 메일함(읽지 않음 수)·중요 표시·보낸 메일함·임시 보관함·휴지통, 보기 필터(읽지 않음·첨부), 설정(관리자에게는 관리자 페이지 링크), 저장 사용량(적용 중인 메일함 최대 크기가 있으면 막대와 비율). 0.9.0 부터 누구나 자기 메일함만 봅니다. 예전의 "관리자가 다른 계정의 메일함을 열어 보는" 기능(상단 메일함 선택)은 없앴고, 관리자 권한은 관리자 페이지의 계정·설정 관리에만 쓰입니다.
  • 로그인 화면 → 회원 가입(0.9.0~): 관리자가 켜 두었으면 접속 단추 아래에 "회원 가입" 이 보입니다. 로그인 ID(= 메일 주소의 앞부분)·이름·비밀번호(2회, 눈 단추)를 넣으면 계정이 생기며, 관리자 설정에 따라 바로 쓰거나 "관리자 승인 후 사용"(승인 전 로그인하면 "가입 승인을 기다리는 계정입니다") 입니다.
  • 목록: 체크박스로 여러 통을 골라 삭제·복원·읽음/읽지 않음·이동을 한 번에 처리합니다. ☆ 를 누르면 중요 표시(IMAP \Flagged 와 공유)입니다. 상단 검색은 보낸 사람·제목·본문을 찾습니다.
  • 읽기 창: 답장·전체 답장·전달·삭제·이동·중요·읽지 않음, 원본(.eml) 내려받기, 상세 정보(헤더·POP 전달 기록), 외부 이미지 표시(기본 차단).
  • 로그인 화면: 오른쪽 위 (?) 를 누르면 접속 안내 팝업(이 도메인의 메일 서비스, 부여받은 계정 또는 도메인을 포함한 이메일 주소로 로그인, 웹 접속 계정과 같은 아이디·비밀번호)이 뜹니다.
  • 설정 → 내 비밀번호 변경(0.9.0~): 현재 비밀번호를 넣고 새 비밀번호를 두 번 같게 입력합니다(눈 단추로 글자 확인). 바꾸면 세션이 모두 끝나 새 비밀번호로 다시 로그인합니다. www 관리자 계정은 www 사이트에서 관리하므로 이 카드가 안내문으로만 보입니다.
  • 설정 → 메일함 사용량·제한(0.9.0~): 사용량(MIME 기준)과 적용 중인 메일함 최대 크기, 첨부 한 개 최대 크기, 한 메일의 받는 사람 수를 보여 줍니다.
  • 설정: 화면 기본값(외부 이미지), 메일 프로그램 접속 허용(POP3 / IMAP) 스위치, IMAP·SMTP·POP 설정 안내. 끈 서비스는 그 계정의 메일 프로그램 로그인을 거부합니다(웹 메일·SMTP 는 영향 없음). 관리자가 서비스 자체를 껐으면 스위치가 비활성화되고 "관리자가 서비스를 껐습니다" 가 붙습니다.
  • 설정 → 메일 전달(0.8.16~): "받은 메일을 아래 주소로 전달" 을 체크하고 전달 주소(쉼표로 구분, 최대 5개)를 넣고 저장하면 이 메일함으로 받는 모든 메일을 그 주소로 보냅니다. 체크를 풀고 저장하면 전달하지 않습니다. 전달 중이면 체크 상자 옆에 "현재 메일은 다음 주소로 전달되고 있습니다" 가 굵게 보입니다. 원본은 받은 메일함에 그대로 남습니다. 전달 메일의 보내는 사람은 자기 메일함 주소(발송 서비스가 우리 도메인만 허용)이고 Reply-To 가 원래 발신자라 전달받은 쪽에서 답장하면 원래 보낸 사람에게 갑니다. 첨부·본문 이미지도 함께 가며(첨부 합계 25MB 까지), 전달 메일에는 X-Ari-Auto-Forwarded 헤더가 붙어 두 메일함이 서로를 전달 대상으로 두어도 한 번만 전달됩니다. 전달 주소에서 온 메일, 72시간이 지난 뒤 동기화가 뒤늦게 가져온 메일은 전달하지 않습니다. 전달 메일은 보낸 메일함에 쌓지 않고 메일 상세 정보의 "자동 처리" 줄에 기록만 남깁니다.
  • 설정 → 자동 응답(0.8.16~): 응답 제목(비우면 Re: 원래 제목)과 메시지를 쓰고, 기간을 기한 제한 없음 또는 기간 지정(시작·종료 날짜와 시간, 시작을 비우면 즉시)으로 정해 저장합니다. 기간 선택과 시작·종료 상자는 항상 보이며(기한 제한 없음이면 시각 상자는 비활성), 켜고 끄는 것은 "자동 응답 사용" 체크 상자와 저장 버튼입니다. 켜져 있으면 체크 상자 옆에 "메일이 수신되면 자동으로 아래 메일을 발신자에게 회신합니다." 가 굵게 보이고(지정 기간 밖이면 기간 안내가 덧붙음), 작동 중이면 왼쪽 패널 아래에도 "자동 응답 작동 중" 이 보입니다. 같은 상대에게는 24시간에 한 번만 보내고, 자동 발송 메일(Auto-Submitted, Precedence: bulk/list, 메일링 리스트, no-reply·mailer-daemon 발신)과 72시간 지난 메일에는 응답하지 않습니다. 자동 응답에는 Auto-Submitted: auto-replied 가 붙어 상대 서버의 자동 응답과 핑퐁이 생기지 않습니다. 보낸 자동 응답은 보낸 메일함에 남고 원래 메일에는 "답장함"(IMAP \Answered) 표시가 붙습니다.
  • 메일 쓰기: 참조 토글, 서식 도구(실행취소·글꼴·크기·굵게/기울임/밑줄/취소선·글자색·강조(기본 8색 팔레트 → 더 많은 색상은 브라우저 색상 선택창)·정렬·목록·들여쓰기·링크·인용·구분선·이미지·서식 지우기·HTML 편집), 파일 첨부(파일당 상한은 관리자 설정, 합계 20MB), 본문 이미지 삽입·붙여넣기·끌어놓기(발송 시 cid 인라인 첨부로 변환), 4초 간격 자동 임시 저장. 보내는 주소는 로그인 계정의 메일함 주소입니다.
  • 참조 / 숨은 참조(0.9.5): 받는 사람 줄 오른쪽의 "참조" 와 "숨은 참조" 를 각각 누르면 그 입력 줄이 열립니다. 숨은 참조도 초안에 저장되고 발송됩니다(받는 사람·참조에게는 보이지 않음).
  • 대용량 첨부(0.9.3~, 관리자가 켠 경우): "☁ 대용량 첨부" 로 올린 파일은 메일에 붙지 않고 서버에 보관되며 본문 끝에 다운로드 링크 블록이 붙어 나갑니다. 올리는 동안 칩 자리에 진행 막대·퍼센트·보낸 양/전체가 보이고 ✕ 로 취소할 수 있습니다(0.9.5). nginx 가 요청을 막으면(413) 그 사실을 따로 알립니다. 일반 첨부 상한을 넘는 파일을 첨부하거나 끌어 놓으면 자동으로 대용량으로 올라갑니다. 대용량 파일이 하나라도 있으면 발송 조건(보관 일수·다운로드 횟수) 줄이 보이며 둘 다 반드시 관리자 상한 이내로 정해야 합니다(제한 없는 발송 불가, 기본값은 상한). 받는 사람은 링크를 열면 랜딩 페이지(파일 이름·크기·남은 횟수·기한)를 보고 "다운로드" 단추를 눌러 받습니다 (단추를 누를 때만 횟수가 줄어 메일 보안 장치가 링크를 미리 열어도 소진되지 않음). 기한이 지나거나 횟수를 다 쓰면 파일이 서버에서 지워지고 링크는 "만료" 페이지가 됩니다. 보낸 메일함에서 메일을 열면 파일마다 남은 횟수·기한이 보이고 ✕ 로 링크를 끊을(파일 즉시 삭제) 수 있습니다.

4b. 관리자 페이지 (https://mail.arihakdang.com/admin)

관리자 페이지는 /admin 주소로 고정되어 있고(0.8.4~), 웹 메일과 별도의 로그인을 거칩니다. 로그인 정보는 같습니다 (www.arihakdang.com 관리자 계정 = 별칭 admin, 또는 역할이 관리자인 게이트웨이 계정). 세션 쿠키가 다르므로(mg_admin) 웹 메일에 로그인돼 있어도 관리자 페이지는 다시 로그인해야 하고, 관리자 세션으로는 메일 API 를 쓸 수 없습니다. 웹 메일의 설정 메뉴에는 관리자에게만 "관리자 페이지 ↗" 링크가 보입니다. 일반 계정으로 /admin 에 로그인하면 "관리자 역할 계정만" 안내와 함께 거부됩니다.

계정 관리

  • 계정 항목: 로그인 ID(영문 소문자·숫자·.·_·-, 2~64자), 이름, 비밀번호(아래 비밀번호 정책을 따름), 역할(관리자/사용자), 상태(사용/정지), 메일 프로그램 접속(POP3 / IMAP 허용).
  • 비밀번호 재설정(0.9.0): 다른 계정의 비밀번호는 계정을 열어 한 번만 입력하고 눈 단추로 확인한 뒤 저장합니다(0.8.25 의 두 번 입력·현재 비밀번호 확인은 설정 화면의 "내 비밀번호 변경" 으로 옮겼습니다). 자기 계정을 열면 비밀번호 칸 대신 "도구 → 설정에서 바꾸세요" 안내만 보이며, API 로 시도해도 self_password_settings 로 거부됩니다.
  • 메일함 최대 크기(0.9.0): 계정마다 MB 로 지정합니다(0 = 관리자 기본값·시스템 값). 시스템 제한(설치 시 LIMIT_MAILBOX_BYTES)이 있으면 그 이내만 저장됩니다. 적용값은 "계정별 → 관리자 기본값 → 시스템" 중 가장 작은 0 아닌 값이며, 목록의 "저장 사용량" 열에 사용량 / 적용값이 보입니다.
  • 승인 대기 계정(0.9.0): 회원 가입으로 만들어진 계정은 상태가 "승인 대기" 로 보이고, 계정을 열면 승인(상태를 사용으로) / 거절(계정 삭제) 단추가 있습니다.
  • 비밀번호 정책(0.8.24~): 계정 관리 화면 아래 "비밀번호 정책" 카드에서 최소 글자 수(6~64, 기본 6)와 영문 대·소문자 혼용 / 숫자 포함 / 특수문자 포함 요구를 각각 켜고 끕니다. 예전에는 10자 이상이 코드에 고정돼 있었습니다. 정책은 새로 정하는 비밀번호(계정 추가·수정, 명령줄 도구)에만 적용되고 기존 비밀번호에는 소급하지 않습니다. 규칙에 어긋나면 어느 항목이 어긋났는지("비밀번호가 정책에 맞지 않습니다: 8자 이상, 숫자 포함")를 알려 줍니다.
  • 메일함 주소는 입력하지 않습니다. 항상 로그인 ID@arihakdang.com 으로 자동 지정됩니다(예: info → info@arihakdang.com). 0.8.4 이전에 만든 계정은 등록된 주소를 유지합니다.
  • 만든 계정은 웹 메일함·IMAP·POP·SMTP 에 로그인 ID 또는 메일함 주소 + 비밀번호로 접속하며 자기 메일함의 메일만 봅니다.
  • 수신 메일은 받는 사람 주소가 등록된 메일함으로 들어옵니다(기본 메일함 admin@ + 활성 계정의 메일함).
  • POP3 / IMAP 허용을 끄면 그 계정의 메일 프로그램 로그인을 거부합니다(-ERR [SYS/PERM] POP access is disabled, NO [UNAVAILABLE] IMAP access is disabled). 사용자도 웹 메일 설정에서 직접 켜고 끌 수 있습니다.
  • 비밀번호를 바꾸거나 정지하면 해당 계정의 웹 세션이 즉시 종료됩니다. 계정을 삭제해도 메일함의 메일은 남습니다.
  • www 관리자 계정(admin 행) 자체는 www 관리자 화면에서 관리하며 여기서는 POP/IMAP 허용만 바꿉니다.

회원 가입 / 메일 제한 / POP·IMAP 서비스(0.9.0, 계정 관리 화면 아래 카드)

  • 회원 가입: "로그인 화면에서 회원 가입 받기" 와 가입 계정의 사용 시점(즉시 사용 / 관리자 승인 후 사용). 끄면 관리자가 만든 계정만 메일을 쓸 수 있습니다(POST /api/signup 은 404).
  • 메일 제한: 계정별 기본 메일함 크기, 첨부 한 개 최대 크기(MIME 기준), 한 메일의 받는 사람 수. 각 값은 시스템 제한 이내만 저장되며(limit_exceeds_system), 0 은 시스템 값을 따릅니다. 그 아래 "시스템 설정" 에 설치 시 정한 LIMIT_* 값이 읽기 전용으로 보입니다(9절).
  • POP3 / IMAP 서비스: 서비스 자체를 켜고 끕니다. 끄면 모든 계정의 POP(또는 IMAP) 로그인이 -ERR [SYS/PERM] POP service is disabled by the administrator / NO [UNAVAILABLE] IMAP service is disabled by the administrator 로 거부됩니다.
  • 일반 접속 로그인 허용(0.9.7, 기본 켬): 110/143 포트에서 STARTTLS 없이 보낸 비밀번호로 로그인을 받을지. 끄면 CAPA 에서 USER 가 빠지고(IMAP 은 LOGINDISABLED) -ERR [SYS/PERM] use STLS or port 995 before login / NO [PRIVACY] use STARTTLS or port 993 before login 으로 거부합니다. 995/993·웹 메일·SMTP 에는 영향이 없고, 바꾼 값은 다음 접속부터 적용됩니다.

대용량 첨부파일(0.9.3, 카드)

  • "대용량 첨부파일 기능 사용" 스위치와 파일 한 개 최대 크기(MB)·보관 최대 기간(일)·다운로드 최대 횟수. 세 값은 비울 수 없고(무제한 불가) 시스템 상한(LIMIT_LARGE_FILE_*, 9절) 이내여야 하며 기본값은 시스템 상한의 50%(1GB/30일/20회 → 512MB/15일/10회)입니다. 기본은 꺼짐. 카드에 현재 서버에 보관 중인 파일 수·용량이 보입니다(서버 상태의 현황에도 표시).
  • nginx(0.9.5): 업로드 경로 /api/files 는 client_max_body_size 0 + proxy_request_buffering off, 다운로드 /dl/ 는 버퍼링 없이 1시간 시간 제한으로 프록시합니다(템플릿 deploy/nginx/*.conf.in). 이미 설치된 서버의 vhost 는 sudo bash deploy/nginx-apply.sh <APP_DIR>/site.env(site.env 가 없으면 deploy/sites/<이름>.env)로 다시 적용합니다 — 환경 파일·인증서는 건드리지 않고 vhost 파일만 바꿔 쓰고(이전 설정은 .bak-<시각>) nginx -t 뒤 reload 합니다. 0.9.3 까지는 server 블록의 client_max_body_size 32m 때문에 32MB 가 넘는 대용량 파일이 nginx 에서 413 으로 끊겼습니다.
  • 파일은 <APP_DIR>/data/files/<id>(또는 FILES_DIR)에 있으며 만료·횟수 소진·링크 끊기·버려진 초안(하루 뒤)은 1시간마다 정리됩니다. 메일함 용량 제한(LIMIT_MAILBOX_BYTES)에는 들지 않습니다.

서버 상태: 메일 현황(0.9.0: 일일·주간·월간 발신/수신/수발신 통수 — Asia/Seoul 기준, 월간은 1일~말일; 메일 계정·총 메일함 사용량·이달 통수를 시스템 제한과 견준 "8개 사용 중 / 최대 10개 (80%)"; 사용자별 메일함 사용량과 적용 중인 최대 크기), 서비스 포트·TLS 인증서·POP/IMAP 스위치 상태, Resend 웹훅·동기화 기록·저장 통계·POP 전달 기록, "지금 동기화" 버튼. 사용량은 모두 MIME 기준(저장 원본 크기)입니다.

5. IMAP 사용법

IMAP 은 서버의 메일함을 그대로 보여 주는 방식입니다. 읽음·삭제·이동이 웹 메일함, 다른 기기와 함께 공유되므로 휴대폰과 PC 에서 같은 메일함을 쓰려면 POP 대신 IMAP 을 권장합니다.

항목 값
받는 메일 서버 mail.arihakdang.com, 포트 993 (SSL/TLS) 또는 143 (STARTTLS)
사용자 이름 / 비밀번호 admin 또는 admin@arihakdang.com (www 관리자 로그인 ID·이메일도 가능) / www 관리자 로그인 비밀번호
메일함 INBOX(받은), Sent(보낸), Drafts(초안), Trash(휴지통). 새 폴더는 만들 수 없습니다.
보내는 메일 서버 mail.arihakdang.com, 포트 465 (SSL/TLS) 또는 587 (STARTTLS). 받는 서버와 같은 계정이므로 "받는 서버 설정 사용"을 켜도 됩니다.

설정 순서: 계정 추가 → 수동 설정(IMAP) → 받는 서버에 위 값 입력 → 보내는 서버에 같은 호스트·계정과 465/587 입력 → 보낸 메일 복사본은 Sent, 임시 저장은 Drafts, 삭제는 Trash 폴더를 쓰도록 지정(대부분 자동 인식).

동작 규칙:

  • 클라이언트에서 읽으면 웹 메일함도 읽음으로 바뀝니다(\Seen ↔ read_at). 깃발(\Flagged), 답장함(\Answered)은 서버에 저장됩니다.
  • 클라이언트의 삭제는 Trash 로 이동입니다. Trash 에서 다시 삭제(EXPUNGE)하면 영구 삭제되며 같은 메일을 다시 가져오지 않습니다.
  • 클라이언트가 Sent/Drafts 에 올린 메일(APPEND)은 원본 그대로 저장되어 웹 메일함에도 보입니다.
  • IDLE 을 지원하므로 새 메일이 오면 10초 안에 클라이언트에 알립니다.
  • 서버의 메일은 기한 없이 보관되므로 IMAP 에서도 계속 보입니다. 지우려면 클라이언트에서 삭제 후 Trash 를 비우세요.
  • 지원 명령: IMAP4rev1 전체 기본 명령, LITERAL+, IDLE, UIDPLUS, MOVE, SPECIAL-USE, NAMESPACE, ID, UNSELECT, ENABLE. CREATE/RENAME/DELETE 는 거부합니다.

확인:

openssl s_client -connect mail.arihakdang.com:993 -quiet      # * OK [CAPABILITY ...] 가 보여야 함

6. 메일 클라이언트 설정

항목 값
받는 메일(IMAP, 권장) mail.arihakdang.com, 포트 993 (SSL/TLS) 또는 143 (STARTTLS, 또는 일반 접속*)
받는 메일(POP3) mail.arihakdang.com, 포트 995 (SSL/TLS) 또는 110 (STARTTLS, 또는 일반 접속*)
보내는 메일(SMTP) mail.arihakdang.com, 포트 465 (SSL/TLS) 또는 587 (STARTTLS), 인증: 일반 비밀번호
사용자 이름 admin 또는 admin@arihakdang.com (www 관리자 로그인 ID·이메일도 가능). 게이트웨이 계정은 그 로그인 ID
비밀번호 웹 메일 로그인 비밀번호 (받는 서버·보내는 서버 모두 같음)
보내는 사람 계정의 메일함 주소(예: admin@arihakdang.com). 다른 주소는 서버가 거부합니다(관리자는 게이트웨이의 모든 메일함 주소 가능).

* 일반 접속(암호화 없이 110/143 에 비밀번호를 보내는 방식)은 관리자 페이지 → 계정 관리 → "POP / IMAP 서비스" 카드의 일반 접속 로그인 허용 스위치(0.9.7, 기본 켬)를 따릅니다. Gmail 의 "다른 계정에서 메일 확인(POP3)" 은 110 포트에서 STLS 를 쓰지 않고 바로 로그인하므로 이 스위치가 꺼져 있으면 Gmail 이 "사용자 이름과 비밀번호가 일치하지 않음" 으로 보여 줍니다. 가능하면 Gmail 에서 "보안 연결(SSL) 사용" 을 켜고 995 를 쓰세요. 환경 변수 POP_ALLOW_PLAINTEXT=1 은 관리자 설정과 무관하게 항상 허용(진단용)이고, 그 밖의 값은 관리자 설정을 따릅니다.

POP3 로 받아도 서버 저장본은 지우지 않으며("서버에 메시지 복사본 남기기" 옵션과 무관) 웹에서 영구 삭제할 때까지 남습니다. SMTP 로 보낸 메일은 곧바로 웹의 보낸 메일함에 저장됩니다. 메일 프로그램이 IMAP Sent 에 사본을 올려도 Message-ID 로 한 통으로 합칩니다.

SMTP 제출 서버 (0.8.1~)

0.8.0 까지는 메일 프로그램이 Resend SMTP 에 직접 접속하며 Resend API 키를 SMTP 비밀번호로 썼습니다. 키가 사용자 PC 와 메일 프로그램 설정에 퍼지는 문제를 없애려고, 게이트웨이가 SMTP 제출 서버(server/smtp.mjs)를 직접 엽니다.

  • 포트 465(접속 즉시 TLS)·587(STARTTLS). 인증서는 POP·IMAP 과 같은 파일이고, 인증(AUTH PLAIN/LOGIN)은 TLS 위에서만 받습니다.
  • 계정·비밀번호는 웹 메일·IMAP·POP 과 같습니다. 인증한 사용자는 자기 메일함 주소로만 보낼 수 있습니다(헤더 From 과 봉투 MAIL FROM 모두 검사).
  • 받은 원본을 해석해 서버의 RESEND_API_KEY 로 Resend API(POST /emails) 를 호출합니다. 숨은 참조는 봉투(RCPT)에만 있는 주소로 판단합니다. 같은 원본을 다시 보내면 멱등 키로 한 번만 발송합니다.
  • 한도: 한 통 30MB(원본 기준), 받는 사람 50명. 인증 실패는 IP 당 15분 20회.
  • 인증하지 않은 접속은 MAIL 단계에서 거부하므로 오픈 릴레이가 아닙니다. 외부 메일을 받는 MX 도 아닙니다(수신은 Resend).
  • 환경 변수: SMTP_PORT(기본 587), SMTPS_PORT(기본 465), SMTP_DISABLED=1(끄기), SMTP_ALLOW_PLAINTEXT=1(진단용 평문 인증 허용).
  • 기존에 메일 프로그램에 넣어 둔 Resend API 키는 더는 필요 없습니다. Resend 대시보드에서 그 키를 폐기하세요(서버 환경 파일의 키와 다른 키였다면).

7. 문제 확인

journalctl -u arihakdang-mail -n 100 --no-pager   # 동기화·웹훅·POP 로그
curl -s http://127.0.0.1:8790/api/health
openssl s_client -connect mail.arihakdang.com:995 -quiet   # +OK ... POP3 ready 가 보여야 함
  • 메일 화면 상단의 연결 상태가 mail_auth_failed 이면 RESEND_API_KEY 가 폐기·변경된 것입니다.
  • 연결 상태가 mail_domain_unverified(1.0.11~)이면 Resend 가 발신 도메인을 검증하지 못한 것입니다. 관리자 › 서버 상태(또는 전체 설정 화면 › 자동 설정)에 어느 DNS 레코드가 없거나 값이 다른지 문장으로 보입니다(예: 검증 실패 — 값이 다른 레코드: TXT resend._domainkey.<도메인> (지금 p=… / 필요 p=…)). 값이 다르면 다른 Resend 계정·예전 등록의 값이니 표의 값으로 바꾸세요. 바꾸면 1시간마다 도는 자동 재검증이 Resend 에 검증을 다시 요청합니다(RESEND_AUTO_VERIFY=0 으로 끔). mail_domain_missing 은 Resend 계정에 도메인이 없는 것 — 자동 설정 실행. mail_forbidden 은 그 밖의 403(키 권한 등)에만 씁니다.
  • 관리자 페이지 → 서버 상태의 웹훅 처리 내역이 비어 있으면 Resend 엔드포인트 URL·이벤트·Signing Secret 을 확인합니다.
  • 995/993 이 열리지 않으면 인증서 경로(POP_TLS_CERT/KEY)와 arihakdang 계정의 읽기 권한을 확인합니다.
  • POP 이 "되다말다" 하면 journalctl 의 POP3 줄을 봅니다(0.9.7~): POP3 평문 로그인 거부(110 에서 STLS 없이 로그인 → 일반 접속 로그인 허용 스위치 확인), POP3 접속 거부 … 다른 세션 … 사용 중(같은 계정의 다른 메일 프로그램이 아직 연결돼 있음 → -ERR [IN-USE], 그 세션이 끝나면 바로 됨), POP3 종료 … RETR n·DELE m … (quit|timeout|error:…)(세션 요약 — timeout 은 10분 동안 명령이 없어 끊은 것, error: 는 상대가 연결을 끊은 것).
  • 화면에 "처리하지 못했습니다 (코드)" 가 보이면 괄호 안 코드가 서버 오류 코드입니다(0.8.25~). 배포 직후라면 화면이 서버 버전(X-App-Version)을 보고 스스로 새로 고치므로 보통 한 번만 보입니다.
  • 메일 전달·자동 응답이 나가지 않으면 메일 상세 정보의 "자동 처리" 줄을 봅니다. 전달하지 않음 — … 은 규칙상 건너뛴 것(되풀이 방지·72시간 지난 메일·같은 상대 24시간 안 응답 등)이고, 전달 실패 / 자동 응답 실패 는 발송 서비스 오류입니다(journalctl 에 메일 전달 실패 / 자동 응답 실패 로 남음). 기록은 DB 의 auto_actions 표에 있습니다.

8. 다른 도메인에 독립 설치 (예: mail.heart-m.com)

0.8.18 부터 www 사이트 없이 독립 실행(standalone) 으로 다른 서버·도메인에 설치할 수 있습니다. 설치는 mail.heart-m.com(115.68.193.164, 실행 계정 webmaster)을 기준으로 적습니다.

  1. 사이트 설정 파일 deploy/sites/heart-m.env — 서비스 이름(heart-mail), 실행 계정, 경로(/srv/heart-mail, /etc/heart-mail), Node 경로, 포트, 호스트, 기본 메일함(admin@heart-m.com), 탭 제목(SITE_TITLE, 비우면 서비스 이름과 같음), LOGIN_MODE=standalone(0.9.0 설치 옵션, 9절). 새 사이트 파일은 0.9.0 부터 deploy/new-site.sh 로 만듭니다:
    bash deploy/new-site.sh cafe --host mail.cafe.heart-m.com --mailbox admin@cafe.heart-m.com --user webmaster \
      --limit-accounts 20 --limit-mailbox 500M --limit-attachment 20M      # 기본은 --login standalone, 제한 0 = 없음
    bash deploy/new-site.sh shop --host mail.shop.test --mailbox admin@shop.test --login www --www-db /srv/shop/data/shop.sqlite   # www 연동
    
    왼쪽 위 서비스 이름은 0.8.25 부터 메일 도메인에서 자동으로 정합니다(heart-m.com → "HEART-M 메일", cafe.heart-m.com → "CAFE 메일"). SITE_BRAND 는 더 쓰지 않습니다(남아 있어도 무시).
  2. 서버 준비(root): 실행 계정이 있어야 하고(id webmaster), Node 22.13+ 를 NODE_BIN 경로에 둡니다(패키지 저장소 없이 nodejs.org 바이너리를 푸는 방식).
    curl -fsSLO https://nodejs.org/dist/v22.23.3/node-v22.23.3-linux-x64.tar.xz && sudo mkdir -p /opt/heart-mail-node \
      && sudo tar -xJf node-v22.23.3-linux-x64.tar.xz -C /opt/heart-mail-node --strip-components=1
    git bundle create /tmp/mail-gateway.bundle main && scp /tmp/mail-gateway.bundle heart:/tmp/      # 작업 PC 에서
    sudo install -o webmaster -g webmaster -m 644 /tmp/mail-gateway.bundle /srv/heart-mail/source.bundle   # 서버에서(디렉터리는 setup.sh 가 만들지만 번들은 먼저 둬도 됨)
    sudo env SITE=heart-m SOURCE_BUNDLE=/srv/heart-mail/source.bundle bash /path/to/repo/deploy/setup.sh
    
    setup.sh 는 /srv/heart-mail/{repo,releases,data}, /srv/heart-mail/site.env, /etc/heart-mail/gateway.env, heart-mail.service, sudoers, nginx vhost(mail.heart-m.com.conf), certbot 훅을 만듭니다.
  3. 환경 파일 /etc/heart-mail/gateway.env 에 RESEND_API_KEY 와 RESEND_WEBHOOK_SECRET 를 채웁니다(heart-m.com 이 Resend 에 수신·발신 도메인으로 등록되어 있어야 함). Resend 웹훅 엔드포인트는 https://mail.heart-m.com/api/webhooks/resend(3절과 같음).
  4. 배포: sudo -u webmaster env SOURCE_BUNDLE=/srv/heart-mail/source.bundle /srv/heart-mail/repo/deploy/deploy.sh — 테스트·릴리스 복사·마이그레이션·재시작·health. 이후 갱신도 같은 명령입니다(번들만 새로 올림).
  5. 첫 관리자 계정 (0.8.24~): 독립 실행이고 계정이 하나도 없으면 서버가 시작할 때 admin / adminpasswd 관리자 계정(기본 메일함 admin@heart-m.com 의 주인)을 자동으로 만들고 journal 에 안내를 남깁니다. https://mail.heart-m.com/admin 에 그 비밀번호로 로그인하면 "초기 비밀번호 사용 중" 경고가 보이며, 계정 관리에서 admin 계정을 열어 비밀번호를 바꾸면 사라집니다. 초기 비밀번호는 비밀번호 정책과 무관하게 글자 수 하한(6)만 적용됩니다(예전에는 10자 규칙 때문에 짧은 초기 비밀번호를 만들 수 없었음). 셸에서 비밀번호를 재설정하거나 계정을 추가하려면:
    sudo -u webmaster env $(sudo grep -E '^(DB_FILE|MAILBOX)=' /etc/heart-mail/gateway.env | xargs) \
      /opt/heart-mail-node/bin/node /srv/heart-mail/current/server/user-cli.mjs passwd admin      # 또는 add <login> [--admin] / list
    # 비밀번호는 단말에서 숨겨 입력(또는 PASSWORD=... 환경 변수 / --password-stdin). 규칙은 관리자 페이지의 비밀번호 정책을 따릅니다.
    
    나머지 계정은 https://mail.heart-m.com/admin 에서 만듭니다.
  6. DNS 와 인증서: mail.heart-m.com A 레코드를 115.68.193.164 로 바꿉니다(프록시 없이). DNS 가 바뀌기 전에는 nginx 가 80 포트(ACME 챌린지)만 열고 나머지는 503 이며, heart-mail-tls.timer 가 15분마다 A 레코드를 확인해 이 서버를 가리키면 certbot(webroot)으로 발급 → setup.sh 재실행 → 443·995/993/465 TLS 를 켜고 타이머를 끕니다. 바로 하려면 sudo bash /srv/heart-mail/repo/deploy/tls-when-ready.sh /srv/heart-mail/site.env. 갱신은 certbot 타이머와 훅이 맡습니다.
  7. 확인: curl -s http://127.0.0.1:8790/api/health, journalctl -u heart-mail -n 50 --no-pager, systemctl list-timers heart-mail-tls.timer. 메일 프로그램 설정값은 6절과 같되 호스트가 mail.heart-m.com, 사용자 이름이 게이트웨이 계정의 로그인 ID 또는 로그인ID@heart-m.com 입니다.

9. 설치 옵션: 로그인 방식과 시스템 제한 (0.9.0~)

설치·배포 때 사이트 설정 파일(deploy/sites/<이름>.env, new-site.sh 가 만듦)에서 정합니다. setup.sh 가 처음 한 번 gateway.env 를 만들 때 이 값을 넣으므로, 이미 설치된 서버에서 바꾸려면 <ETC_DIR>/gateway.env 를 직접 고치고 systemctl restart <서비스> 합니다(mail.heart-m.com 의 환경 파일은 0.9.0 배포에서 건드리지 않았으며, 값이 없으면 모두 "제한 없음" 과 standalone 으로 동작합니다).

로그인 방식 LOGIN_MODE — 독립 실행이 기본 설치 형태입니다.

  • standalone(기본): www 사이트와 연동하지 않습니다. 첫 관리자 admin / adminpasswd 가 자동으로 생기고, 계정은 관리자 페이지(또는 회원 가입)로 만듭니다. WWW_DB_FILE 은 무시됩니다.
  • www: www 사이트 DB(WWW_DB_FILE, 필수)로 관리자를 인증합니다(mail.arihakdang.com). 경로가 없으면 서비스가 시작하지 않습니다.
  • 값이 없으면 0.8.x 처럼 WWW_DB_FILE 유무로 정합니다(기존 서버 호환).

시스템 제한 LIMIT_* — 메일 시스템 전체의 상위 설정입니다. 관리자 페이지에서는 볼 수만 있고, 관리자가 정하는 값(계정별 메일함 크기, 첨부 크기, 받는 사람 수)은 이 값 이내로만 저장됩니다. 모두 0 이면 제한 없음.

변수 뜻 어긋나면
LIMIT_ACCOUNTS 허용 가능한 메일 계정 수(정지·승인 대기 포함) 계정 추가·회원 가입이 account_limit_reached 로 거부
LIMIT_TOTAL_BYTES 모든 메일함을 합친 최대 용량 수신은 storage_full 로 건너뜀(공간이 생기면 다음 동기화가 가져옴), 발송은 거부
LIMIT_MAILBOX_BYTES 개별 메일함 최대 용량(관리자·계정별 값의 상한) 그 메일함 수신 건너뜀(quota_exceeded), 웹·SMTP 발송 거부(452 4.2.2), IMAP APPEND NO [OVERQUOTA]
LIMIT_ATTACHMENT_BYTES 첨부 파일 한 개 최대 크기(관리자 값의 상한, 내장 상한 25MB) 웹 첨부·전달 복사 attachment_too_large, SMTP 552 5.3.4
LIMIT_RECIPIENTS 한 메일의 받는 사람 수(참조·숨은 참조 포함, 내장 상한 50) 웹 발송 too_many_recipients, SMTP 452 4.5.3
LIMIT_MONTHLY_MESSAGES 월간(1일~말일, Asia/Seoul) 최대 수발신 통수(보낸 + 받은) 발송 거부(monthly_limit_exceeded, SMTP 550 5.7.1), 자동 전달·자동 응답 건너뜀(limit)

대용량 첨부파일 상한(0.9.3) — 다른 LIMIT_* 와 달리 무제한이 없습니다. 비우거나 0 이면 기본값이 적용됩니다.

변수 뜻 기본값
LIMIT_LARGE_FILE_BYTES 대용량 파일 한 개 최대 크기(관리자 설정의 상한) 1G
LIMIT_LARGE_FILE_DAYS 보관 최대 기간(일) 30
LIMIT_LARGE_FILE_DOWNLOADS 다운로드 최대 횟수 20
FILES_DIR 파일 저장 디렉터리(비우면 DB 옆 files/)

크기 값은 바이트 또는 K/M/G 접미사(500M, 2G)이며 MIME 변환 뒤의 크기 기준입니다. 메일은 저장·전송될 때 첨부가 base64 로 바뀌어 약 1.37배가 되므로 사용량(저장된 원본 .eml 크기)과 모든 제한을 그 크기로 셉니다. 첨부 파일을 올릴 때는 파일 크기 × 4/3 을 제한과 비교합니다. 메일 현황의 시간대는 STATS_TIMEZONE(기본 Asia/Seoul)으로 바꿀 수 있습니다.

10. 설정 파일과 초기 설정 페이지 (1.0.0)

설정은 두 층입니다.

층 어디에 무엇 바꾸는 방법
배포 설정 /etc/<서비스>/gateway.env (systemd EnvironmentFile) DB 경로, 포트, 기본 메일함·공개 호스트, 로그인 방식, 시스템 제한 LIMIT_*, TLS 사본 경로, SETUP_PORT, HARAKA_DIR root 가 파일을 고치고 서비스 재시작
운영 설정 <APP_DIR>/data/config.json (실행 계정만 읽음) 수·발신 방식, Resend API 키·웹훅 비밀키, SMTP 중계 서버, 내장 Haraka(포트·DKIM), 발신 표시 이름, 탭 제목, 통계 시간대, 동기화 주기 초기 설정 페이지 또는 /admin → 메일 서비스 설정. 저장 즉시 적용(재시작 없음)
  • 왜 나눴나: 운영 설정은 처음 배포할 때 알 수 없거나(키를 아직 만들지 않음) 배포 자체에는 필요하지 않습니다. 비어 있어도 배포는 성공해야 하고, 채울 때 root 작업과 재시작이 없어야 합니다.
  • 기존 서버: 설정 파일이 없으면 첫 시작 때 환경 파일에 있던 값(RESEND_API_KEY 등)을 그대로 읽어 설정 파일을 만듭니다. 환경 파일은 그대로 두어도 됩니다. 그 뒤로는 설정 파일 값이 우선합니다(설정 파일에서 지운 값은 환경 파일 값으로 되살아나지 않음). 설정 화면의 각 항목 아래에 출처(설정 파일 / 환경 파일 값 / 기본값)가 보입니다.
  • 초기 설정 페이지: 게이트웨이가 전용 포트(SETUP_PORT, 기본 8791, 모든 주소)에서 엽니다. 접속에는 데이터 디렉터리의 setup-token(배포 계정만 읽음)이 필요하며, deploy.sh 가 끝에 전체 주소(…:8791/?token=…)를 출력하고 data/setup-url.txt 에도 남깁니다. 틀린 토큰은 IP 당 15분 10회만 받습니다. 메일 포트 인증서(POP_TLS_CERT)를 읽을 수 있으면 HTTPS 로, 없으면 HTTP 로 엽니다(새 서버는 인증서 전이라 HTTP — 토큰이 노출되므로 설정이 끝나면 토큰 파일을 바꾸거나 SETUP_PORT=0 으로 끌 수 있음). 방화벽을 쓰면 8791 을 열어야 합니다(sudo ufw allow 8791/tcp). 운영 중에는 https://<호스트>/setup(관리자 세션)으로 같은 화면을 엽니다.
  • 화면 구성: 현재 상태(수·발신 방식, 발신 서비스 연결, Haraka, 설정 파일) → ① 수·발신 방식 → ② Resend → ③ SMTP 중계 서버 → ④ 내장 Haraka(DKIM 키 생성, DNS 레코드, 서비스 상태·로그) → ⑤ 사이트 정보 → ⑥ 마무리(점검표, 완료 표시, systemd 아래면 재시작). 고른 방식에 필요한 카드만 보입니다. 비밀값은 가려서 보이며(re_••••abcd) 입력 칸을 비워 두면 그대로, "비우기" 를 누르면 지워집니다.
  • API: GET/PUT /api/setup/config, POST /api/setup/check {what:'outbound'|'haraka'}, POST /api/setup/dkim, POST /api/setup/restart. 전용 포트는 X-Setup-Token(또는 ?token=), 웹 서버는 관리자 세션 + X-Mail-Scope: admin.

11. 발송 모듈 (1.0.0)

발송은 server/providers/index.mjs 의 sendMail(env, payload, key) 한 곳을 거치며, 설정의 발신 방식(outbound) 이 모듈을 고릅니다.

발신 방식 모듈 설명
resend providers/resend.mjs Resend REST API(예전과 같음). 도메인이 Resend 에 검증되어 있어야 함
haraka providers/smtp.mjs → 127.0.0.1:HARAKA_RELAY_PORT(2525) 내장 Haraka 의 로컬 제출 포트로 넘기면 Haraka 가 받는 쪽 MX 에 직접 배달(12절)
smtp providers/smtp.mjs → SMTP_RELAY_HOST:PORT 외부 SMTP 중계 서버(587 STARTTLS / 465 TLS / 인증 선택). 보낸 사람 도메인을 그 서버가 허용해야 함

메시지 객체는 Resend 요청과 같은 모양(from, to, cc, bcc, reply_to, subject, text, html, headers, attachments[{filename, content(base64), content_type, content_id}])이라 웹 발송(store.mjs)·SMTP 제출 서버(smtp.mjs)·자동 전달/응답(automation.mjs)은 어느 모듈을 쓰는지 모릅니다. SMTP 쪽은 mime.mjs 의 buildEml 로 원본을 만들고(Bcc 는 봉투에만), 의존성 없는 SMTP 클라이언트(providers/smtp-client.mjs)로 보냅니다. 오류 코드는 공통(mail_not_configured / mail_auth_failed / mail_rejected / mail_rate_limited / mail_provider_unavailable)이라 화면 문구와 SMTP 제출 서버의 응답 변환이 그대로 동작합니다. 새 발송 서비스를 더하려면 providers/<이름>.mjs 에 같은 모양의 send 를 만들고 index.mjs 에 한 줄씩 더합니다.

수신 방식(inbound)은 resend(웹훅 + 동기화) 또는 haraka(12절)입니다. Resend 동기화는 Resend 를 쓰는 방향만 훑습니다(수신이 Haraka 면 받은 메일 목록은 보지 않음).

12. 내장 Haraka (1.0.0)

Haraka(Node.js 메일 서버)를 함께 띄워 Resend 없이 메일을 받거나(MX) 보낼 수 있습니다. 조합은 자유입니다(예: 수신 Haraka + 발신 Resend).

  • 설치: 저장소의 haraka/package.json(Haraka 3.3.4 + haraka-plugin-dkim)을 deploy/haraka-apply.sh 가 <APP_DIR>/haraka 에 npm 으로 설치하고 systemd 서비스 <APP_NAME>-haraka 를 등록합니다. 새 사이트는 setup.sh 가 자동으로 부르고, 기존 사이트는 한 번 실행합니다: sudo bash <APP_DIR>/repo/deploy/haraka-apply.sh(인터넷 필요, 환경 파일·데이터는 건드리지 않음).
  • 동작: 관리 프로세스 haraka/run.mjs 가 항상 떠 있으면서 설정 파일을 5초마다 보고, 수신 또는 발신 방식이 haraka 이면 Haraka 설정(<APP_DIR>/haraka/config/*, 플러그인 plugins/arimail.js)을 만들어 Haraka 를 자식 프로세스로 띄우고, 아니면 끕니다. 상태는 data/haraka-status.json 에 써서 설정 화면·서버 상태에 보입니다. 로그: journalctl -u <APP_NAME>-haraka -f.
  • 수신: Haraka 가 HARAKA_PORT(25)로 받아 RCPT 단계에서 게이트웨이에 메일함이 있는지 확인하고(없는 사용자는 550), DATA 가 끝나면 원본을 POST /api/inbound/haraka(로컬, 토큰 인증)로 넘깁니다. 게이트웨이는 Resend 경로와 같은 규칙으로 저장합니다(중복·영구 삭제·용량·사본·자동 전달/응답). 메일함이 가득 차면 452 로 보낸 쪽이 나중에 다시 보냅니다. DNS 의 MX 를 이 서버(HARAKA_HOSTNAME, 기본 공개 호스트 이름)로 바꾸고 방화벽에서 25 를 열어야 합니다.
  • 발신: 게이트웨이가 127.0.0.1:HARAKA_RELAY_PORT(2525, 로컬 전용)로 넘기면 Haraka 가 받는 쪽 MX 에 직접 배달하고 실패는 재시도·반송합니다. 설정 화면에서 DKIM 키 만들기 를 누르면 RSA 2048 키가 설정 파일에 저장되고 Haraka 가 서명합니다. 화면의 SPF·DKIM(·DMARC) TXT 레코드를 DNS 에 올리고, 서버 IP 의 역방향 DNS(PTR)가 메일 서버 이름을 가리키게 호스팅 업체에 요청하세요(없으면 스팸 분류가 잦습니다). STARTTLS 는 메일 포트 인증서(POP_TLS_CERT)를 그대로 씁니다.
  • 환경 변수: HARAKA_DIR(설치 위치), HARAKA_LOGLEVEL(기본 info). 그 밖의 값(포트·호스트 이름·DKIM)은 설정 파일에 있습니다.

13. 자동 설정과 DNS 모듈 (1.0.5)

설정 화면의 자동 설정 카드가 수·발신 방식과 도메인에 맞는 준비 상태를 진단하고(GET /api/setup/plan), 자동으로 할 수 있는 일은 자동 설정 실행(POST /api/setup/plan/run) 한 번으로 처리합니다(server/provision.mjs). 단계마다 상태(완료 / 자동 실행 가능 / 반영 대기 / 사용자 작업 / 오류 / 참고)와 안내 문구가 붙고, 사람이 해야 하는 단계는 입력 칸이나 단추를 함께 보여 줍니다.

구성 자동으로 하는 일 사용자가 하는 일(안내)
Resend 수·발신 도메인 등록(지역 RESEND_REGION)과 발송/수신 기능 맞춤, 요구 DNS 레코드(DKIM TXT·send CNAME·수신 MX) 적용(DNS 모듈이 있을 때), 검증 요청과 상태 추적(pending/verified/failed/temporary_failure), 수신 웹훅 생성과 서명 비밀키 자동 저장 API 키 입력, DNS 모듈이 없으면 표의 레코드를 직접 넣고 "반영 확인", 웹훅 비밀키를 잃었으면 입력 또는 "웹훅 새로 만들기"
내장 Haraka DKIM 키 생성, MX/SPF/DKIM 레코드 적용(기존 SPF 가 있으면 우리 메커니즘만 끼워 넣음), 실행 상태·PTR 확인 deploy/haraka-apply.sh 실행(설치), 방화벽 25 개방, 호스팅 업체에 PTR 요청
외부 SMTP 중계 접속·인증 확인 접속 정보 입력
  • DNS 모듈(server/dns/, 요구사항 1): DNS_PROVIDER 가 google(Google Cloud DNS, 서비스 계정 JSON) 또는 route53(AWS Route 53, 액세스 키)이면 존을 자동으로 찾아 레코드를 UPSERT 합니다(외부 의존성 없이 OAuth2 JWT / SigV4 서명 구현). none 이면 자동 적용 없이 표의 레코드를 보여 주고 공개 리졸버(8.8.8.8 / 1.1.1.1)로 반영 여부만 확인합니다. DMARC 는 권장 레코드로 보여 주되 자동으로 넣지 않습니다(기존 정책을 덮어쓰지 않기 위해). 권한: Google 은 프로젝트의 "DNS 관리자", AWS 는 route53:ListHostedZonesByName/GetHostedZone/ListResourceRecordSets/ChangeResourceRecordSets.
  • Resend 모듈(server/providers/resend.mjs, 요구사항 3): 도메인 목록·등록·기능 변경·검증, 웹훅 목록·생성·삭제를 담당합니다. 자동 설정은 이 모듈만 통해 Resend 를 바꿉니다.
  • 검증 실패 진단과 자동 재검증(1.0.11): Resend 도메인이 failed 면 레코드마다 공개 DNS 조회로 "없음(전파 대기)" 과 "값이 다름(바꿔야 함)" 을 구분해 단계 문구와 표의 "지금 값" 으로 보여 줍니다. Resend 자신이 verified 라고 하는 레코드는 우리 조회 값이 달라도 정상으로 봅니다(예: 예전 수신 MX 값). 레코드가 모두 보이면 정기 동기화 주기(기본 1시간)마다 검증을 자동으로 다시 요청하므로 "지금 검증 요청" 을 누르지 않아도 됩니다. 발신 상태(/api/mail/status, 서버 상태 화면)도 mail_domain_unverified(원인 문장 detail, 레코드별 records) / mail_domain_missing 으로 구분합니다.
  • 상황별 안내: 인증 정보 없음·형식 오류·권한 없음·존 없음·연결 실패(DNS), 키 거부·권한·요청 제한(Resend), 미설치·서비스 꺼짐·실행 대기·PTR 불일치(Haraka) 등 각 단계의 detail 에 원인과 다음 할 일이 적힙니다. 모든 단계가 끝나면 "메일 사용 준비가 끝났습니다" 를 알립니다.

14. 다중 도메인과 전체 설정 화면 (1.0.7)

한 서버(한 프로세스)가 여러 도메인의 메일 서비스를 섬깁니다(server/domains.mjs). 도메인마다 계정·세션·메일·설정 파일·파일 저장소가 따로이고, 로그인과 계정 관리는 도메인 단위입니다.

기본 도메인 추가 도메인
정해지는 곳 환경 파일(MAILBOX·PUBLIC_HOST·LOGIN_MODE·WWW_DB_FILE·DB_FILE) 전체 설정 화면의 도메인 카드(재시작 없음)
데이터 data/mail-gateway.sqlite, data/config.json, data/files/ (예전 그대로) data/domains/<도메인>/{mail.sqlite, config.json, files/}
웹 메일 주소 PUBLIC_HOST (+ 등록부의 추가 호스트) 등록한 호스트 이름(기본 mail.<도메인>) — nginx vhost 와 인증서는 sudo bash deploy/domain-nginx.sh <호스트>
로그인 방식 환경 파일 local(자체 계정, 첫 관리자 admin 자동 생성) 또는 www(연결된 웹 서비스의 SQLite users 표로 관리자 인증)
메일 프로그램 PUBLIC_HOST:993/995/465/587 같은 서버 이름(기본 도메인 호스트)에 아이디@도메인 으로 로그인 — 인증서가 하나이므로
  • 등록부 data/domains.json: 1.0.7 로 올린 서버는 첫 시작에 기본 도메인만 담아 만들고(데이터 이동 없음) 예전과 똑같이 동작합니다. 추가 도메인의 설정 파일은 기본 도메인의 설정에서 API 키·DNS 인증 정보·SMTP 중계·Haraka 포트·시간대를 복사해 시작할 수 있습니다.
  • 요청 → 도메인: HTTP 는 Host(X-Forwarded-Host). 등록된 호스트 이름과 같거나 도메인의 하위 이름이면 그 도메인, IP·localhost 는 기본 도메인. 모르는 호스트에는 "메일 서비스가 존재하지 않습니다", 중지한 도메인에는 "메일 서비스가 활성화되지 않았습니다"(관리자에게 요청) 화면을 줍니다. 메일 프로그램은 로그인 ID 의 @도메인(없으면 TLS SNI 호스트, 그래도 없으면 기본 도메인).
  • 전체 설정 화면: 기본 도메인의 전용 포트(https://<PUBLIC_HOST>:8791/?token=…, 배포 출력에 주소). 맨 위 도메인 카드에서 추가·수정·중지/사용·삭제(등록 해제만, 디렉터리는 남김)하고, 행의 "선택" 으로 고른 도메인의 설정을 아래 카드에서 바꿉니다. 추가 도메인의 호스트 이름으로 이 포트에 오면 열리지 않습니다(기본 도메인 주소 안내). 관리자 페이지의 /setup 은 자기 도메인만.
  • 서버에 하나뿐인 것(기본 도메인 설정을 따름): 메일 포트·인증서, 내장 Haraka 의 수신/제출 포트·메일 서버 이름·게이트웨이 토큰. Haraka 는 수신 방식이 haraka 인 모든 도메인을 받고(받는 사람 주소로 나눔), 발신 haraka + DKIM 키가 있는 도메인마다 config/dkim/<도메인>/ 키로 서명합니다.
  • www 연동 도메인: 그 SQLite 파일을 서비스 계정이 읽을 수 있어야 하며 systemd 의 ReadWritePaths(사이트 설정 EXTRA_RW)에 그 디렉터리(WAL 의 -shm 파일 때문)가 있어야 합니다.
  • 추가 도메인의 명령줄 계정 도구: DB_FILE=<APP_DIR>/data/domains/<도메인>/mail.sqlite MAILBOX=admin@<도메인> node server/user-cli.mjs …
  • API: GET/POST /api/setup/domains, PATCH/DELETE /api/setup/domains/<도메인>(전용 포트 전용), 그 밖의 /api/setup/* 에 ?domain=<도메인>. 오류: domain_exists, host_taken, www_db_unreadable, primary_domain, domain_not_found, validation(field).

15. webmaker 인증 모드와 사이트의 /mail 경로 (1.0.8)

웹메이커(WebHosting_AI_agent, https://source.ibit.kr/OZ-Group/WebHosting_AI_agent)의 사용자 계정으로 AriMail 에 로그인하게 하는 로그인 방식입니다(server/webmaker.mjs). 웹메이커의 사용자 파일을 읽기 전용으로 참조해 같은 방식(scrypt)으로 비밀번호를 검사하므로 계정을 따로 만들거나 메일 비밀번호를 따로 정하지 않습니다. 웹 메일·IMAP·POP·SMTP 모두 같은 비밀번호이며, 도메인마다 따로 켭니다.

웹메이커 쪽 AriMail 계정 (도메인 D) 역할
사이트 회원 중 로그인 ID 가 있는 계정(사이트 관리자 페이지 › 계정 추가, modules/<사이트>.json members.loginId) loginId@D 일반
사이트 소유자(sites/<사이트>.json ownerId → users.json) 소유자ID@D 일반(웹메이커 관리자이기도 하면 관리자)
웹메이커 관리자(users.json role=admin, 보통 admin) admin@D 관리자
(도메인에 대응하는 사이트가 없을 때) 웹메이커의 모든 회원 아이디@D 관리자는 관리자, 나머지 일반
  • 도메인 D 에 대응하는 사이트: 등록부의 siteId → 웹메이커 domains.json 의 서브 도메인 등록(name=D, siteId) → 사이트의 domains 목록 → 사이트의 메일 도메인(webmail.domain) 순으로 찾습니다. 같은 로그인 ID 가 겹치면 사이트 회원 → 소유자 → 플랫폼 관리자 순으로 먼저 찾은 쪽이 그 주소를 씁니다. 승인이 취소된 회원(approved=false)은 로그인할 수 없습니다.
  • 예: 웹메이커에서 site.ariweb.net 사이트를 만들고 AriMail 에 도메인 site.ariweb.net(로그인 방식 webmaker)을 추가하면 admin@site.ariweb.net(웹메이커 admin 비밀번호)으로 바로 수·발신하고, 사이트 관리자 페이지에서 사용자 user 를 추가하면 user@site.ariweb.net 도 같은 비밀번호로 씁니다.
  • 웹메이커에서 비밀번호를 바꾸거나 계정을 지우면 바로 반영됩니다(스냅샷 3초 캐시, 로그인 실패 시 한 번 다시 읽음). 웹메이커 계정의 이름·비밀번호·역할은 AriMail 에서 바꿀 수 없고(관리자 페이지에 읽기 전용 행), POP/IMAP 허용과 메일함 크기만 둡니다. 게이트웨이 계정(관리자 페이지에서 만든 것)은 함께 쓸 수 있되 웹메이커 계정과 같은 로그인 ID 는 만들 수 없습니다. webmaker 모드 도메인에는 초기 관리자 admin/adminpasswd 를 만들지 않습니다.

켜는 방법

  1. 파일 읽기 권한: 웹메이커 데이터(DATA_ROOT, 예 /data/webmaker)는 보통 웹메이커 서비스 계정(예: master)만 읽을 수 있고(0600/0700) 웹메이커가 파일을 다시 쓸 때마다 권한이 돌아옵니다. 같은 서버라면 root 로 한 번:
    sudo bash /srv/<사이트>/repo/deploy/webmaker-access.sh /data/webmaker
    
    root 소유 사본 /usr/local/lib/arimail/webmaker-snapshot.mjs(node 22+)와 systemd 소켓 도우미(arimail-webmaker-<슬러그>.socket + @.service: /run/arimail-webmaker/data-webmaker.sock 에 접속하면 웹메이커 소유 계정으로 스냅샷 JSON 을 써 줌, 소켓은 AriMail 계정 그룹만 0660)를 설치하고 실행 계정으로 읽어 봅니다. AriMail 서비스 유닛은 NoNewPrivileges=yes 라 sudo 가 동작하지 않으므로 소켓 방식이 기본이고, systemd 가 없는 서버는 --sudo(sudoers 규칙 한 줄, 그 디렉터리 인수만 허용)로 설치합니다. AriMail 실행 계정이 직접 읽을 수 있는 디렉터리면 이 단계는 필요 없습니다.
  2. 도메인 설정: 전체 설정 화면(§14)의 도메인 카드에서 추가·수정 → 로그인 방식 웹메이커(webmaker) 계정과 연동, 웹메이커 데이터 디렉터리 /data/webmaker, 파일을 읽을 계정 master(1단계를 했을 때 — 소켓 도우미가 있으면 소켓, 없으면 sudo 도우미를 씀), 사이트 ID 는 보통 비움. 등록부에는 auth: { mode:'webmaker', webmakerDir, webmakerUser, siteId } 로 저장됩니다. 기본 도메인은 환경 파일(/etc/<app>/gateway.env)에 LOGIN_MODE=webmaker, WEBMAKER_DIR=/data/webmaker, 선택 WEBMAKER_USER=master, WEBMAKER_SITE=<32자리> 를 적고 재시작합니다.
  3. 확인: 관리자 페이지(웹메이커 admin 으로 로그인)의 계정 목록에 "웹메이커 계정" 행이 보입니다. 파일을 못 읽으면 도메인 목록과 계정 목록에 "읽기 실패" 와 사유가 보이고, 마지막으로 읽은 스냅샷이 있으면 그것으로 계속 동작합니다.

사이트 주소의 /mail 로 웹 메일 열기

서브 도메인 사이트(site.ariweb.net)는 메일용 호스트 이름(mail.site.ariweb.net)을 따로 두기 어렵습니다(와일드카드 인증서가 한 단계만 덮음). 그런 도메인은 사이트 주소의 /mail 아래를 웹 메일로 씁니다.

  1. 도메인의 호스트 항목에 site.ariweb.net/mail 처럼 호스트/경로 를 적습니다(같은 호스트 이름을 경로만 달리해 두 번 적을 수는 없음).
  2. 사이트의 nginx 설정에 조각을 넣습니다(root): sudo bash deploy/site-mail-path.sh site.ariweb.net (또는 설정 파일 경로). deploy/nginx/mail-path.conf.in 을 /etc/nginx/snippets/<app>-mail-path.conf 로 채우고 443 server 블록에 include 를 더합니다 — location ^~ /mail/ { proxy_pass http://127.0.0.1:<포트>/; proxy_set_header X-Forwarded-Prefix /mail; … }. 게이트웨이는 Host 로 도메인을, X-Forwarded-Prefix(없으면 등록부의 경로)로 화면의 기준 경로(<html data-base="/mail/">)를 압니다. 웹메이커는 사이트를 다시 발행할 때 vhost 파일(wh-<사이트ID>.conf)을 다시 쓰므로 그때 이 명령을 다시 실행하거나 웹메이커의 vhost 템플릿에 같은 include 를 넣어 둡니다. 사이트 폴더의 mail/index.html(웹메이커의 안내 파일)은 프록시가 먼저 받으므로 영향이 없습니다.
  3. https://site.ariweb.net/mail/ 이 웹 메일, /mail/admin 이 관리자 페이지, /mail/setup 이 설정 화면입니다. 세션 쿠키는 Path=/mail 로 한정됩니다.
  4. 보안 참고: 이 방식은 웹 메일이 사이트와 같은 출처(origin)를 씁니다. 사이트에 스크립트를 넣을 수 있는 사람(사이트 소유자)은 그 사이트를 방문한 로그인 사용자의 메일 API 를 같은 출처에서 부를 수 있으므로, 사이트 소유자를 믿을 수 없는 도메인은 별도 호스트 이름(mail.<도메인>, domain-nginx.sh)을 쓰세요. 메일 프로그램은 §14 와 같이 기본 도메인의 서버 이름에 아이디@site.ariweb.net 으로 접속합니다.

16. 메일 사용량 정보 제공 API (1.0.9)

웹메이커 같은 외부 관리 시스템이 도메인·계정별 디스크 사용량·메일 수·발송량을 읽는 읽기 전용 API 입니다. 전체 설정 화면(기본 도메인)의 사용량 API 카드에서 키를 만들면 켜지고(한 번만 표시), 접속 허용 IP 를 적어 웹메이커 서버만 부를 수 있게 합니다. 호출은 GET /api/usage(전체) · GET /api/usage/<도메인>(상세)에 Authorization: Bearer <키>. 자세한 응답 필드·보안 장치·웹메이커 쪽 예제는 docs/USAGE-API.md.