Dovecot으로 만드는 개인 IMAP 사서함

여러 계정에 흩어진 메일을 모아둘 IMAP 전용 사서함을 구축하고, 인증서 자동화와 삭제 안전망까지 얹은 기록입니다.

Dovecot으로 만드는 개인 IMAP 사서함
Photo by Brett Jordan / Unsplash

메일 계정이 늘어나면 오래된 메일도 자연스럽게 여러 서비스에 흩어집니다. 계정을 정리하려고 할 때마다 중요한 메일이 남아 있어 삭제하지 못하는 상황을 몇 번 겪고 나니, 메일을 한곳에 모아 보관할 수 있는 개인 저장소가 필요하다는 결론에 도달했습니다. 그래서 홈랩에 IMAP 사서함을 하나 추가했습니다.

다만 이 서버의 역할은 처음부터 명확하게 제한했습니다. 메일을 직접 수신하지 않습니다. MX 레코드도 설정하지 않고 SMTP 서버도 운영하지 않습니다. 기존 메일 서비스에서 메일을 가져와 보관하고, 메일 클라이언트에서 열람하고 검색하고 정리하는 역할만 담당합니다. 발신과 수신은 기존 메일 서비스가 계속 담당하고, 이 서버는 메일 아카이브에 집중하는 구조입니다.

기능만 보면 일반적인 메일 서버의 일부에 해당하지만, 개인이 메일을 장기간 보관하는 목적이라면 이 정도로도 충분합니다. SMTP 수신과 발신을 제외하면 운영 난이도도 크게 낮아지고, 메일 서버 운영에서 발생하는 여러 복잡한 문제도 피할 수 있기 때문입니다.

전체 구조

먼저 최종적으로 구성한 구조는 다음과 같습니다.

flowchart LR
  subgraph client["클라이언트"]
    M["Mac / iPhone"]
  end
  subgraph vm["Proxmox VM"]
    subgraph stack["docker compose"]
      D["Dovecot 2.4.4-root<br/>:993 (implicit TLS)"]
      C["certbot-dns-cloudflare<br/>(DNS-01 발급·갱신)"]
      V[("certs volume")]
    end
  end
  M -->|"집: LAN 직결<br/>밖: WireGuard"| D
  C --> V
  V --> D

접근 경로는 사설망으로 제한했습니다. 집에서는 LAN을 통해 직접 접속하고, 외부에서는 기존에 사용하던 WireGuard를 통해 같은 주소로 접근합니다. DNS에는 imap.daeho.ro A 레코드를 등록하고 VM의 사설 IP를 가리키도록 했습니다. 이때 Cloudflare의 Proxy status는 반드시 DNS only로 설정해야 합니다.

공개 DNS에 사설 IP를 등록하는 것이 처음에는 다소 어색하게 느껴질 수 있습니다. 하지만 TLS 인증서는 도메인 이름을 기준으로 발급되므로, 도메인이 가리키는 IP가 공인 IP인지 사설 IP인지는 인증서 발급 자체와 관계가 없습니다. 해당 사설 IP는 외부에서 직접 접근할 수 없기 때문에 실제 서비스 접근은 여전히 LAN이나 WireGuard를 통해서만 가능합니다.

왜 Cloudflare Tunnel이 아닌가

사실 처음부터 지금과 같은 구조로 시작한 것은 아닙니다. 이미 운영 중인 다른 서비스들이 모두 Cloudflare Tunnel 뒤에 있었기 때문에 자연스럽게 같은 방식을 적용하려 했고, Tunnel의 TCP 모드를 이용해 IMAP 연결을 구성하는 것까지는 성공했습니다. 문제는 그다음이었습니다.

Cloudflare Tunnel의 HTTP 모드는 엣지가 프로토콜을 이해하기 때문에 일반적인 브라우저에서 바로 접근할 수 있습니다. 반면 IMAP과 같은 임의의 TCP 프로토콜은 엣지가 애플리케이션 수준에서 처리하지 않고 바이트 스트림을 전달하기 때문에, 접속하는 기기마다 cloudflared 로컬 프록시나 WARP와 같은 별도의 구성이 필요합니다.

Mac에서는 로컬 프록시를 실행하면 되지만, iPhone의 기본 메일 앱에서는 이런 방식을 적용하기 어렵습니다. 메일을 확인할 때마다 별도의 프록시를 실행해야 한다면 일상적인 사용성도 떨어집니다. 웹 서비스에는 매우 편리한 Cloudflare Tunnel이지만, 기본 메일 클라이언트에서 사용하는 IMAP과는 잘 맞지 않는다는 것을 확인한 셈입니다.

결국 사설망을 통한 직접 접근으로 방향을 바꿨습니다. 집에서는 LAN으로, 외부에서는 WireGuard로 접근하면 메일 클라이언트에서는 항상 같은 주소로 IMAP 서버에 접속할 수 있습니다. 클라이언트 측에 별도의 프록시를 구성할 필요도 없고, 구조 자체도 더 단순합니다.

Docker Compose 전체 구성

최종 compose 파일은 다음과 같습니다. 컨테이너는 세 개로 구성했습니다. Dovecot 본체와 인증서를 발급하고 갱신하는 certbot 사이드카, 그리고 메일 볼륨의 소유권을 초기화하는 일회성 init 서비스입니다.

name: dovecot

services:
  vmail-init:
    image: dovecot/dovecot:${DOVECOT_VERSION:-2.4.4-root}
    container_name: vmail-init
    restart: "no"
    entrypoint: ["/bin/sh", "-c", "chown vmail:vmail /srv/vmail && chmod 0700 /srv/vmail"]
    volumes:
      - dovecot-mail:/srv/vmail

  dovecot:
    image: dovecot/dovecot:${DOVECOT_VERSION:-2.4.4-root}
    container_name: dovecot
    pull_policy: always
    restart: unless-stopped
    depends_on:
      certbot:
        condition: service_healthy
      vmail-init:
        condition: service_completed_successfully
    environment:
      TZ: Asia/Seoul
      USER_PASSWORD: ${DOVECOT_USER_PASSWORD}
    configs:
      - source: dovecot-conf
        target: /etc/dovecot/conf.d/zz-custom.conf
    volumes:
      - dovecot-certs:/etc/letsencrypt:ro
      - dovecot-mail:/srv/vmail
    networks:
      - internal
    ports:
      - "993:993"
    healthcheck:
      test: ["CMD", "doveadm", "reload"]
      interval: 6h
      timeout: 10s
      retries: 3
      start_period: 1m

  certbot:
    image: serversideup/certbot-dns-cloudflare:${CERTBOT_VERSION:-v1.4.0}
    container_name: certbot
    pull_policy: always
    restart: unless-stopped
    environment:
      TZ: Asia/Seoul
      CLOUDFLARE_API_TOKEN: ${CF_DNS_TOKEN}
      CERTBOT_EMAIL: ${ACME_EMAIL}
      CERTBOT_DOMAINS: ${IMAP_DOMAIN}
    volumes:
      - dovecot-certs:/etc/letsencrypt
    networks:
      - internal
    healthcheck:
      test: ["CMD", "test", "-f", "/etc/letsencrypt/live/${IMAP_DOMAIN}/fullchain.pem"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 5m

configs:
  dovecot-conf:
    content: |
      protocols = imap
      ssl_server_cert_file = /etc/letsencrypt/live/${IMAP_DOMAIN}/fullchain.pem
      ssl_server_key_file = /etc/letsencrypt/live/${IMAP_DOMAIN}/privkey.pem

      mail_plugins {
        lazy_expunge = yes
      }
      lazy_expunge_mailbox = .EXPUNGED
      namespace inbox {
        mailbox .EXPUNGED {
          autoexpunge = 30days
        }
      }

networks:
  internal:

volumes:
  dovecot-certs:
  dovecot-mail:

호스트 파일 시스템에 데이터를 두지 않기

구성에서 가장 먼저 신경 쓴 부분은 호스트 파일 시스템에 의존하지 않는 것입니다. 메일 데이터와 인증서는 모두 Docker named volume에 저장하고, Dovecot 설정 오버라이드 역시 호스트의 별도 파일을 마운트하지 않고 compose의 인라인 configs를 사용했습니다. 덕분에 리포지토리에는 compose 파일 하나만 두면 전체 스택을 재현할 수 있고, 설정 변경도 git diff를 통해 추적할 수 있습니다.

홈랩 환경에서는 호스트의 디렉터리 구조에 의존하는 구성을 줄이는 것만으로도 서버를 옮기거나 다시 배포할 때 신경 써야 할 부분이 크게 줄어듭니다. 물론 named volume 자체는 별도의 백업 대상이지만, 적어도 서비스가 실행되는 호스트의 특정 경로를 전제로 하지 않는다는 점은 장점입니다.

docker.sock을 마운트하지 않기

인증서 자동화 구성을 찾아보면 사이드카에 Docker 소켓을 마운트하고, 인증서가 갱신되면 Docker API를 통해 Dovecot 컨테이너를 재시작하는 방식이 종종 등장합니다. 하지만 Docker 소켓에 접근할 수 있다는 것은 사실상 호스트의 Docker 데몬을 제어할 수 있는 높은 권한을 부여하는 것과 같기 때문에 처음부터 이 방식은 배제했습니다.

그 대신 인증서가 갱신된 뒤 Dovecot이 스스로 새 인증서를 다시 읽도록 구성했습니다. 이 역할은 뒤에서 설명할 healthcheck가 담당합니다. 별도의 권한을 추가하지 않아도 같은 목적을 달성할 수 있다면, 굳이 Docker 소켓까지 연결할 이유는 없다고 판단했습니다.

수동 초기화 절차 없애기

메일 볼륨의 소유권처럼 원래라면 배포할 때마다 직접 처리해야 하는 작업은 vmail-init 서비스가 대신합니다. 이 서비스는 root 권한으로 컨테이너를 실행해 /srv/vmail의 소유권을 vmail:vmail로 변경하고 권한을 0700으로 설정한 뒤 종료합니다.

Dovecot은 service_completed_successfully 조건을 통해 init 서비스가 정상적으로 종료된 이후에만 시작됩니다. Kubernetes의 initContainer와 비슷한 패턴인데, 초기화 작업이 멱등적이기 때문에 재배포할 때마다 실행해도 문제가 없습니다. 덕분에 서버를 새로 배포할 때마다 chown 같은 수동 작업을 따로 기억할 필요가 없습니다.

환경변수

주입하는 값은 IMAP_DOMAIN, ACME_EMAIL, Cloudflare API 토큰인 CF_DNS_TOKEN, 그리고 DOVECOT_USER_PASSWORD 네 가지입니다. Cloudflare API 토큰은 해당 Zone에 대해 Zone → DNS → Edit 권한만 부여했습니다. 저는 배포에 사용하는 Dockhand의 스택 환경변수로 이 값을 주입하고 있습니다.

Dovecot 비밀번호는 평문 대신 해시를 사용합니다. 다음 명령으로 BLF-CRYPT 형식의 해시를 생성할 수 있습니다.

docker run --rm -it dovecot/dovecot doveadm pw -s BLF-CRYPT

출력되는 {BLF-CRYPT}$2y$... 전체가 하나의 값입니다. 환경변수는 docker inspect 등을 통해 확인될 가능성이 있기 때문에 평문 대신 해시를 저장하고, 실제 메일 클라이언트에서는 원래 비밀번호를 입력합니다.

인증서 자동화

인증서는 Let's Encrypt의 DNS-01 챌린지를 이용해 발급합니다. DNS-01 방식은 TXT 레코드를 이용해 도메인 소유권을 검증하기 때문에 80번이나 443번 포트를 외부에 열 필요가 없습니다. 사설망에만 존재하는 이 서버와는 사실상 가장 잘 맞는 방식입니다.

도메인이 Cloudflare에서 관리되고 있다면 API 토큰 하나로 인증서 발급과 갱신까지 자동화할 수 있습니다. 사이드카 이미지로는 serversideup/certbot-dns-cloudflare를 선택했습니다. 공식 Certbot 이미지 위에 Cloudflare DNS 플러그인과 자동화에 필요한 구성이 포함되어 있어, 환경변수 몇 개만으로 인증서 발급과 갱신을 구성할 수 있다는 점이 편리했습니다.

다만 작은 프로젝트에 의존하는 것이 마음에 걸릴 수도 있습니다. 그래도 인증서 저장 구조는 Certbot 표준인 /etc/letsencrypt를 그대로 사용하기 때문에, 나중에 공식 Certbot 이미지 기반의 구성으로 변경하더라도 마이그레이션 비용이 크지 않습니다. 특정 이미지에 의존하더라도 데이터와 파일 구조가 표준을 따르고 있다면 교체 비용은 충분히 낮출 수 있다고 판단했습니다.

인증서 자동화에서 남는 문제는 갱신된 인증서를 Dovecot에 어떻게 반영할 것인가입니다. Dovecot은 기동할 때 인증서를 읽어 메모리에 올리기 때문에, 인증서 파일이 바뀌었다고 해서 실행 중인 프로세스가 자동으로 새 인증서를 읽지는 않습니다. 그래서 healthcheck를 이용해 주기적으로 doveadm reload를 실행하도록 했습니다.

healthcheck:
  test: ["CMD", "doveadm", "reload"]
  interval: 6h

doveadm reload는 설정과 인증서를 다시 읽도록 Dovecot을 리로드합니다. 6시간마다 실행되기 때문에 인증서가 갱신된 이후 최대 6시간 이내에 새 인증서가 적용됩니다. Let's Encrypt 인증서는 만료 전에 충분한 여유를 두고 갱신되므로 이 정도의 지연은 문제가 되지 않습니다.

또한 doveadm reload가 정상적으로 실행되지 않으면 healthcheck가 실패하기 때문에 프로세스 상태를 확인하는 역할도 함께 수행합니다. 다만 Dovecot 2.4의 기본값인 shutdown_clients = yes에서는 리로드 과정에서 기존 IMAP 세션이 종료될 수 있습니다. 메일 클라이언트가 연결이 끊어진 뒤 자동으로 재접속하기 때문에 실제 사용에서는 크게 문제가 되지 않았습니다.

공식 이미지에서 확인한 세 가지 문제

구축 과정에서 생각보다 많은 시간을 사용한 부분은 Dovecot 자체보다 공식 Docker 이미지의 동작을 확인하는 일이었습니다. Dovecot 2.4.4 공식 이미지를 사용하면서 문서만 봐서는 알기 어려운 차이점을 몇 가지 발견했고, 정확한 동작을 이해하기 위해 결국 이미지의 소스 코드까지 직접 확인해야 했습니다.

1. root 변형의 IMAP 포트는 31143이 아니라 143입니다

Docker Hub 문서에는 IMAP 포트가 31143, IMAPS 포트가 31993으로 안내되어 있습니다. 하지만 이 포트 설정은 rootless.conf에 정의되어 있고, Dockerfile을 확인해 보면 이 파일은 rootless 변형에만 추가됩니다.

rootless 환경에서는 1024 미만의 포트를 사용할 수 없기 때문에 143과 993 대신 높은 포트를 사용하는 것입니다. 반대로 root 변형에서는 Dovecot이 표준 포트를 그대로 사용할 수 있습니다. 따라서 root 변형에서는 다음과 같이 설정해야 합니다.

ports:
  - "993:993"

문서만 보고 993:31993으로 설정하면 컨테이너 내부에서 실제로 수신하지 않는 포트로 연결을 전달하게 됩니다. 이 경우 연결이 정상적으로 성립하지 않기 때문에, root와 rootless 이미지의 차이를 모른 채 설정하면 원인을 찾는 데 꽤 많은 시간을 쓰게 됩니다.

2. conf.d 오버라이드 파일명은 정렬 순서를 고려해야 합니다

이미지는 /etc/dovecot/conf.d/ssl.conf에서 기본 인증서 경로를 설정합니다. Dovecot은 conf.d 디렉터리의 설정 파일을 알파벳 순서로 읽기 때문에 동일한 설정 항목을 나중에 읽힌 파일에서 다시 지정하면 마지막 설정이 적용됩니다.

예를 들어 90-custom.confssl.conf보다 먼저 읽힙니다. 이 경우 커스텀 인증서 경로를 지정하더라도 이후에 읽히는 ssl.conf의 설정으로 덮어써질 수 있습니다. 그 결과 설정 오류가 명확하게 표시되지 않은 상태에서 기본 self-signed 인증서가 사용될 수 있습니다.

그래서 이 구성에서는 ssl.conf보다 나중에 읽히도록 zz-custom.conf라는 이름을 사용했습니다. 별도의 설정 파일을 호스트에서 마운트하지 않고 compose의 configs를 이용해 /etc/dovecot/conf.d/zz-custom.conf에 주입한 것도 이 때문입니다.

3. root 변형에서도 메일 디렉터리의 소유권을 별도로 설정해야 합니다

메일 저장 경로는 /srv/vmail입니다. 문서 상단의 /srv/mail 안내는 현재 이미지의 실제 경로와 맞지 않고, rootless 이미지에는 해당 디렉터리의 소유권을 vmail:vmail로 변경하는 과정이 포함되어 있지만 root 변형에는 이 단계가 없습니다.

문제는 root 변형을 사용하더라도 실제 메일 데이터를 처리하는 Dovecot 프로세스는 vmail 사용자 권한으로 실행된다는 점입니다. 따라서 컨테이너 자체는 정상적으로 실행되고 로그인도 가능하지만, 메일함을 생성하거나 메일을 저장하는 순간 Permission denied가 발생할 수 있습니다.

앞서 설명한 vmail-init 서비스는 이 문제를 해결하기 위한 것입니다. 컨테이너 시작 전에 메일 볼륨의 소유권을 보장하기 때문에 별도의 수동 작업이 필요하지 않습니다.

메일 클라이언트 연결

서버가 정상적으로 실행된 이후에는 메일 클라이언트 설정은 비교적 단순합니다. Apple Mail 기준으로 계정 추가에서 기타 메일 계정을 선택하고, 수신 서버 imap.daeho.ro, 포트 993, SSL/TLS를 지정하면 됩니다. 표준 IMAPS 포트인 993번에서 implicit TLS를 사용하기 때문에 클라이언트에서 자동으로 설정을 인식하는 경우도 있습니다.

다만 Apple Mail은 발신 서버 정보도 요구하기 때문에 SMTP 서버를 제공하지 않는다고 해서 입력 없이 넘어갈 수는 없습니다. 같은 주소를 입력한 뒤 서버 검증 실패 경고를 무시하면 수신 계정만 사용할 수 있습니다. 발신 기능은 기존 메일 서비스가 계속 담당하므로, 이 서버에는 별도의 SMTP 구성을 추가하지 않았습니다.

이 이미지의 기본 인증 방식에는 한 가지 특성이 있습니다. static passdb를 사용하기 때문에 비밀번호가 해시와 일치하면 사용자 이름 자체는 별도로 등록되지 않습니다. 로그인한 사용자 이름이 곧 메일함 이름으로 사용되는 구조입니다.

개인용으로는 간단한 방식이지만, 사용자 이름을 잘못 입력해도 인증 자체는 성공할 수 있다는 점에 주의해야 합니다. 예를 들어 사용자 이름에 오타가 있어도 로그인에 실패하는 대신 새로운 빈 메일함이 조용히 생성될 수 있습니다. 어느 날 메일이 보이지 않는다면 비밀번호뿐 아니라 사용자 이름부터 확인하는 것이 빠릅니다.

삭제 안전망: lazy_expunge

운영 첫날, 메일이 들어 있는 메일함을 실수로 통째로 삭제하는 사고가 있었습니다. Maildir에서는 삭제가 파일 시스템 수준의 unlink로 이어지기 때문에 서버 자체만으로는 되돌릴 방법이 없었고, 결국 해당 메일들과는 그렇게 작별해야 했습니다. 이 경험을 계기로 lazy_expunge 플러그인을 바로 적용했습니다.

설정은 다음과 같습니다.

mail_plugins {
  lazy_expunge = yes
}
lazy_expunge_mailbox = .EXPUNGED
namespace inbox {
  mailbox .EXPUNGED {
    autoexpunge = 30days
  }
}

동작은 단순합니다. 사용자가 메일을 영구 삭제하는 순간, 즉 expunge가 발생하면 실제 삭제 대신 .EXPUNGED 메일함으로 이동시킵니다. 이후 30일이 지나면 autoexpunge에 의해 실제로 삭제됩니다.

flowchart TD
  A["메일 삭제"] --> T["Trash<br/>(삭제 대기실)"]
  T -->|"휴지통 비우기"| E["expunge 발생"]
  B["영구 삭제 /<br/>메일함 통째 삭제"] --> E
  E -->|"lazy_expunge 개입"| X[".EXPUNGED"]
  X -->|"30일 후 autoexpunge"| G["영구 삭제"]
  X -.->|"원래 자리로 드래그"| R["복구"]

따라서 실수로 메일을 영구 삭제했더라도 30일 이내라면 .EXPUNGED에서 메일을 찾아 원래 메일함으로 다시 이동하는 방식으로 복구할 수 있습니다. 일반적인 메일 삭제뿐 아니라 메일함 전체를 삭제하는 상황에서도 마지막 안전망 역할을 합니다.

설정하다 보면 자연스럽게 Trash 메일함을 대상으로 지정하면 되지 않을까 하는 생각이 들지만, Trash와 lazy expunge는 서로 다른 역할을 합니다. Trash는 사용자가 삭제한 메일을 관리하는 삭제 대기 공간이고, lazy expunge는 사용자가 Trash를 비우거나 영구 삭제를 수행한 이후의 마지막 안전망입니다.

무한 루프를 막기 위해 lazy expunge 메일함 자체에서 발생하는 expunge는 실제 삭제로 처리됩니다. 따라서 Trash를 대상으로 지정하면 휴지통을 비우는 동작과 lazy expunge의 안전망 역할이 충돌할 수 있습니다. Trash → .EXPUNGED → 30일 후 실제 삭제라는 두 단계 구조를 유지하는 것이 이 구성에서는 적절합니다.

백업: restic과 S3

삭제 안전망과 별개로 백업도 필요합니다. lazy_expunge는 실수로 삭제한 메일을 일정 기간 복구할 수 있게 해주는 기능이지, 디스크 손상이나 서버 자체의 장애까지 대비하는 백업은 아니기 때문입니다.

Dovecot에는 S3를 직접 메일 저장소로 사용하는 obox 기능이 있지만 Pro 유료 기능입니다. 오픈소스 환경에서는 로컬에 Maildir를 저장하고 파일 수준에서 백업하는 방식이 현실적인 선택입니다.

Maildir와 restic은 비교적 잘 맞는 조합입니다. Maildir는 메일 한 통을 하나의 파일로 저장하고, 한 번 저장된 메일 파일은 일반적으로 수정되지 않기 때문에 restic의 중복 제거와도 궁합이 좋습니다. 새로운 메일이 추가되면 새로운 파일만 백업하면 되므로 반복적인 백업에서도 실제 전송량은 신규 메일의 크기에 가까워집니다.

메일 데이터 자체는 파일 단위로 저장되기 때문에 Dovecot을 중지하지 않고 백업해도 비교적 안전하게 처리할 수 있습니다. Dovecot의 인덱스 파일은 메일 데이터와 달리 재생성이 가능하기 때문입니다. 예를 들어 dovecot.index.cache는 백업 대상에서 제외할 수 있고, 복원 후 인덱스에 문제가 발생하면 doveadm force-resync를 이용해 다시 생성할 수 있습니다.

백업 저장소로는 이미 운영 중인 셀프호스트 S3 호환 스토리지인 Garage를 사용할 계획입니다. 또한 현재 배포에 사용하는 Dockhand에서 restic 기반 백업 지원을 준비하고 있기 때문에, 최종적으로는 별도의 cron 작업을 구성하지 않고 배포 도구의 백업 기능을 활용할 예정입니다.

이 부분은 실제 백업 구성을 적용한 이후 별도의 글에서 자세히 다룰 계획입니다. 메일 데이터 자체뿐 아니라 복원 절차까지 확인한 뒤, 실제 운영 환경에서 어느 정도의 보존 기간과 백업 주기를 가져가는 것이 적절한지도 함께 정리해볼 생각입니다.

마치며

돌아보면 이번 구성에서 중요한 부분은 Dovecot 자체보다 그 주변의 운영 환경이었습니다. 메일을 단순히 저장할 수 있는 서버를 만드는 것은 어렵지 않지만, 실제로 장기간 사용할 수 있는 사서함을 만들기 위해서는 몇 가지 추가적인 요소가 필요했습니다. Docker Compose만으로 재현 가능한 배포 구조, DNS-01을 이용한 TLS 인증서 자동 발급과 갱신, 인증서 갱신 이후 Dovecot에 자동으로 반영하는 구조, 메일 데이터 볼륨의 자동 권한 설정, 실수로 삭제한 메일을 30일 동안 복구할 수 있는 안전망, 그리고 장기 보관을 위한 별도의 백업 계획까지 함께 고려해야 했습니다.

특히 운영 첫날 메일을 실수로 삭제한 경험을 통해, 정상적으로 동작하는 서버와 안심하고 사용할 수 있는 사서함 사이에는 생각보다 큰 차이가 있다는 것을 확인했습니다. 서비스가 실행되는 것만으로는 충분하지 않고, 인증서가 알아서 갱신되고, 재배포해도 권한 문제가 발생하지 않고, 실수로 삭제한 데이터도 복구할 수 있어야 비로소 장기간 사용할 수 있는 시스템이 됩니다.

수신도 발신도 없는 반쪽짜리 메일 서버지만, 개인이 여러 계정에 흩어진 메일을 한곳에 보관하고 필요할 때 검색하고 다시 꺼내 보는 용도라면 이 정도 구성으로도 충분합니다. 언젠가 SMTP까지 직접 운영하고 싶어지는 날이 올 수도 있겠지만, 현재로서는 기존 메일 서비스가 송수신을 담당하고 이 서버는 보관과 검색에 집중하는 구조가 가장 단순하고 관리하기 쉬운 선택이라고 생각합니다.

우선은 여러 서비스에 흩어진 메일을 하나씩 이 사서함으로 옮기는 것부터 시작하려 합니다.