- unsub
- Oracle
- AI 플랫폼을 활용한 데이터 분석
- 코딩테스트
- 이것이 취업을 위한 코딩테스트다 with 파이썬
- r
- 프로그래머스
- 오라클
- [멀티잇]데이터 시각화&분석 취업캠프(Python)
- 빅데이터분석기사
- boostcourse
- 코딩테스트 python
- 인공지능기초다지기
- 데이터 분석 기반 에너지 운영 관리자 양성 및 취업과정
- 부스트코스
- python
- 파이썬
- 데이터베이스
- 이기적
- DB
- 정보처리기사
- 빅데이터 분석 기반 에너지 운영 관리자 양성 및 취업과정
- PY4E
- 기초다지기
- 네이버부스트캠프
- Ai
- 빅분기
- Django
- boostcoures
- SQL
- Today
- Total
매일공부
사내용 이메일 캠페인 관리 툴을 Flask로 직접 만든 이유와 구조 본문
이 도구를 왜 독립 프로젝트로 만들기로 했는지는 이전 글에서 다뤘다. 결론만 요약하면 — 1회성 임시 도구이고, 정식 시스템과의 통합/분리 여부는 나중에 논의할 문제라, 지금은 최소한의 안전장치만 갖추고 그 이상은 확장하지 않는 걸 기준으로 삼았다.
배경
그린텍는 친환경 사업(eco_contacts)과 SW 사업(salesdb.customer)이라는 서로 다른 두 고객 데이터가 있었다. 두 쪽 모두에 이메일 캠페인(뉴스레터/공지)을 보내야 하는데, 기존 사내 프로그램에는 "발송 대상을 그룹으로 묶어서 예약 발송하고, 수신거부까지 자동으로 처리하는" 기능이 없었다. 정식 프로그램에 이 기능을 넣기 전, 급하게 필요한 캠페인 발송을 처리하기 위해 1회성으로 급하게 만든 Flask 관리자 웹앱이 unsub_app이다.
"임시로 빨리 만든다"는 전제였지만, 실제 운영 메일을 수백 건씩 내보내는 도구라 안전장치(테스트 승인, 발송 이력, 실패 원인 추적)는 처음부터 필요했다. 이 시리즈는 그 과정에서 마주친 문제와 해결 방식을 정리한 것이다.
무엇을 하는 앱인가
- 로그인: admins 테이블 기반 관리자 인증 (bcrypt)
- 캠페인 관리: 작성(제목/본문 HTML 에디터/예약 발송 시각/대상 그룹/첨부파일) → 테스트 발송 → 승인 → 예약 발송 → 발송 이력 확인
- 구독자(고객) 관리: 친환경/SW 두 데이터 소스를 하나의 구독자 목록으로 통합, 검색·필터·CRUD
- 그룹(회사 유형) 관리: 발송 대상 그룹 CRUD
- SMTP 설정: 발신 서버/계정/공통 서명 관리 (비밀번호는 Fernet 암호화 저장)
- 수신거부 페이지: 토큰 기반 공개 페이지 (로그인 불필요)
기술 스택
- Python 3.12 / Flask 3, 별도 프론트엔드 프레임워크 없이 Jinja2 템플릿
- MySQL(PyMySQL) — 앱 자체 DB(unsub_db) + 외부 참조 스키마(salesdb, 읽기 전용)
- bcrypt(관리자 비밀번호), cryptography/Fernet(SMTP 비밀번호 암호화)
- gunicorn + Docker, Synology NAS 배포
디렉터리 구조
app.py Flask 라우트 (관리자 페이지 전체)
models.py 메일 발송/암호화/HTML 인라인 스타일 공용 로직
setup_admin.py 관리자 계정 생성 CLI
scripts/send_scheduled_campaigns.py 예약 발송 배치 스크립트 (Task Scheduler/cron용)
templates/ Jinja2 템플릿
migrations/ 수동 적용 SQL 마이그레이션
docs/db.md DB 스키마 명세서
docs/deploy.md Synology NAS + Docker 배포 가이드
핵심 설계는 "앱의 테스트 발송 버튼과 예약 발송 배치 스크립트가 완전히 같은 발송 함수(models.py)를 쓰게 만드는 것"이었다. 처음엔 발송 로직이 scripts/send_scheduled_campaigns.py 안에만 있었는데, 이러면 "테스트로 확인한 내용"과 "실제 예약 발송되는 내용"이 코드 경로가 달라서 미묘하게 어긋날 위험이 있다. models.py로 공용화한 뒤로는 두 경로가 함수 단위로 100% 동일해졌다.
이 시리즈의 다른 글
'IT > 작업기록' 카테고리의 다른 글
| Flask 첫 프로젝트에서 마주친 삽질들 (0) | 2026.08.31 |
|---|---|
| 이메일 캠페인 발송 시스템 DB 설계 노트 (0) | 2026.08.31 |
| 작은 사내 도구, 기존 시스템에 붙일까 독립시킬까 — 판단 기준 정리 (0) | 2026.08.31 |
| [NAS 트러블슈팅] Log Center 종료 실패 에러, 원인은 예약된 PowerOff Task였다 (0) | 2026.08.31 |
| [SSH] 처음부터 설정하기 — 키 생성부터 접속까지 (1) | 2026.08.28 |