[작업 기록] POP3 메일 체크 스크립트, 계정별 복사 대신 config로 관리하기
배경
회사 메일은 Mailplug를 쓰고 있는데, 이 서비스의 정책 중에 이런 게 있다:
- 사용자 별로 POP3/IMAP 기능을 설정할 수 있습니다.
- 30일간 POP3/IMAP 접속 이력이 없는 사용자는 자동으로 차단됩니다.
- 보안 강화를 위해 30일간 POP3/IMAP 사용 기록이 없는 경우 사용이 해제됩니다.
즉, 특정 계정이 POP3/IMAP으로 30일간 한 번도 접속하지 않으면 관리자가 별도로 조치하지 않아도 해당 기능이 자동으로 꺼져버린다. 평소에는 웹메일이나 다른 클라이언트로만 메일을 확인하고 POP3/IMAP은 특정 용도(예: 자동화 스크립트, 외부 연동)로만 가끔 쓰는 계정이라면, 실사용 빈도와 무관하게 이 정책 때문에 조용히 차단당하는 일이 생긴다.
그래서 나스(Synology) 작업 스케줄러에 계정별로 주기적으로 POP3에 접속만 해주는 스크립트를 등록해서, "30일 이상 미접속" 상태가 되지 않도록 우회하기로 했다.
문제는 계정이 여러 개다 보니, 처음에는 계정마다 스크립트 파일을 따로 두거나 하나의 파일 안에 계정 정보를 하드코딩해서 스케줄을 여러 개 등록하는 식으로 만들었다는 점이다:
- 계정 A용 스크립트, 계정 B용 스크립트… 코드 자체는 완전히 동일한데 계정 정보만 다름
- 로직을 하나라도 고치려면(예: 타임아웃 값 변경, 에러 로깅 추가) 스크립트 개수만큼 반복 수정해야 함
- 새 계정이 추가될 때마다 스크립트를 복사해서 새로 만들어야 함
전형적인 "코드와 데이터가 분리되지 않은" 상황이라, 코드는 한 번만 두고 계정 정보만 별도 설정 파일로 분리하기로 했다.
구조 변경
변경 전
/pop3_check.py ← 계정 정보가 코드 안에 하드코딩되어 있음
변경 후
/pop3_check.py ← 로직만 담당 (수정됨)
/config.py ← 계정 목록 관리 (신규)
/.env ← 실제 비밀번호 등 민감정보 (신규, git 추적 제외)
계정 정보는 환경변수로
비밀번호 같은 민감정보를 config.py에 평문으로 직접 적어두면, 이 파일이 저장소나 백업본에 그대로 남게 된다. 그래서 실제 값은 .env 파일에 두고, config.py는 그 값을 읽어오기만 하는 역할로 분리했다.
.env (예시 — 실제 값은 각자 채워 넣고, .gitignore에 반드시 추가)
ACCOUNT_1_EMAIL=user1@example.com
ACCOUNT_1_PASSWORD=xxxxxxxx
ACCOUNT_2_EMAIL=user2@example.com
ACCOUNT_2_PASSWORD=xxxxxxxx
config.py
# config.py
import os
from dotenv import load_dotenv
load_dotenv() # .env 파일을 읽어 환경변수로 등록
ACCOUNTS = {
'account_1': {
'email': os.environ['ACCOUNT_1_EMAIL'],
'password': os.environ['ACCOUNT_1_PASSWORD'],
},
'account_2': {
'email': os.environ['ACCOUNT_2_EMAIL'],
'password': os.environ['ACCOUNT_2_PASSWORD'],
},
}
pop3_check.py
# pop3_check.py
import poplib
from config import ACCOUNTS
POP3_SERVER = os.environ.get('POP3_SERVER', 'pop3.example.com')
def check_pop3(account_name):
account = ACCOUNTS[account_name]
pop = poplib.POP3_SSL(POP3_SERVER, 995)
pop.user(account['email'])
pop.pass_(account['password'])
pop.stat()
pop.quit()
if __name__ == '__main__':
import sys
# 스케줄 등록 시 인자로 계정 이름을 넘겨받는 방식
check_pop3(sys.argv[1])
나스 작업 스케줄러에서는 스케줄마다 실행 커맨드에 계정 이름만 인자로 다르게 넘기면 된다:
python3 /volume1/scripts/pop3_check.py account_1
python3 /volume1/scripts/pop3_check.py account_2
단, 경로에 띄어쓰기 있다면, ""로 묶어주면 된다.
python3 "/volume1/scripts/pop3_check.py" account_1
python3 "/volume1/scripts/pop3_check.py" account_2
장점
- 로직(POP3 접속, 상태 확인)은 pop3_check.py 한 곳에서만 유지
- 계정 추가/삭제는 .env에 값만 추가하고 config.py의 ACCOUNTS 딕셔너리에 한 줄 추가하면 끝
- 비밀번호 같은 민감정보는 .env로 분리되어 있어 코드 저장소에 실수로 커밋될 위험이 줄어듦
- 스케줄마다 커맨드 인자만 다르게 등록하면 되므로, 스크립트 파일 자체를 복사할 필요가 없음
남은 숙제
- .env 파일이 나스 볼륨에 평문으로 남아있는 건 여전하므로, 파일 권한(600)과 접근 계정을 제한해둘 것
- 계정 수가 더 늘어나면 config.py의 딕셔너리도 반복문으로 .env를 파싱하도록 리팩터링 고려
계정이 늘어나면 config.py도 고쳐야 하는 문제 (추가 개선 방향)
지금 config.py 구조는 이렇다:
ACCOUNTS = {
'account_1': {
'email': os.environ['ACCOUNT_1_EMAIL'],
'password': os.environ['ACCOUNT_1_PASSWORD'],
},
'account_2': {
'email': os.environ['ACCOUNT_2_EMAIL'],
'password': os.environ['ACCOUNT_2_PASSWORD'],
},
}
계정 정보(이메일/비밀번호 값)는 .env로 분리했지만, "계정이 몇 개고 이름이 뭔지"는 여전히 config.py 코드 자체에 하드코딩되어 있다. 계정이 하나 늘어나면 .env에 값을 추가하는 것뿐 아니라, config.py에도 'account_3': {...} 블록을 손으로 추가해야 한다.
더 분리하려면, config.py가 .env를 읽어서 자동으로 계정 목록을 구성하게 하면 된다:
import os
import re
from dotenv import load_dotenv
load_dotenv()
ACCOUNTS = {}
for key in os.environ:
match = re.match(r'^(.+)_EMAIL$', key)
if match:
name = match.group(1).lower()
ACCOUNTS[name] = {
'email': os.environ[f'{name.upper()}_EMAIL'],
'password': os.environ[f'{name.upper()}_PASSWORD'],
}
이렇게 하면 계정이 늘어나든 줄어들든 config.py는 한 글자도 안 건드리고 .env만 수정하면 된다. 즉 "계정 정보가 어디 있냐"의 분리는 이미 끝났고, 다음 단계는 "계정이 몇 개인지를 코드가 알아야 하는가"의 분리다.