01 / OVERVIEW & OUTCOMES
세 업무 시스템을 구축하고,
실제 영업에 적용했습니다.
고객 유입, 영업 단계, 재고·예약·A/S 처리 공간을 구현했습니다.
광고 리드·상담 이력·구매 수요를 영업 데이터에 연결했습니다.
Actions·Compose 배포와 Redis·Celery 영상 처리 분리를 구현했습니다.
- 고객 홈페이지 — 매물 정보 · 시운전 예약 · A/S 접수
- 영업 CRM — 고객 발굴 · 문의 수집 · 상담 이력 · 판매 단계 · 전환 이벤트
- 사내 통합 관리 프로그램(stock) — 장비 재고 · 예약 · A/S 접수 내역 관리
실제 사용 현장 업무에 사용됐고, 초기 판매로 이어진 사례가 있었습니다.
02 / BACKGROUND & HYPOTHESIS
직접 영업하며 발견한 문제와 가설
대학 졸업 후 프리랜서로 사업 기회를 탐색하며 굴삭기 판매 파트너와 협력했습니다. Meta 광고 문의를 받는 인바운드, 업종별 업체와 온라인 구매 수요를 찾는 아웃바운드를 직접 경험하며 구매자를 발굴해 영업 담당자에게 연결했습니다.
광고 문의는 Meta 광고 관리자에서, 온라인 구매 수요는 게시글에서, 전화·문자 내역은 담당자의 휴대전화에서 확인했습니다. 상담 진행 상황과 판매 결과도 별도로 관리되어, 고객이 어떤 경로로 유입되고 어떤 상담을 거쳐 구매했는지 한눈에 파악하기 어려웠습니다.
흩어진 기록을 연결하고 수집을 자동화하면 담당자의 반복 업무를 줄이고, 판매 결과를 다음 영업과 마케팅에 활용할 수 있겠다고 판단했습니다.
고객을 찾는 순간부터 상담과 판매 결과까지 데이터를 연결하면, 반복 업무를 줄이고 다음 영업 판단을 도울 수 있지 않을까?
이 가설을 바탕으로 업체에 영업·마케팅 통합 플랫폼 구축을 먼저 제안했습니다. 요구사항 정리, Django 백엔드와 관계형 데이터 설계, 외부 API 연동과 운영을 맡았고, 영업 파트너는 현장 상담·계약을 진행했습니다.
03 / SYSTEM OVERVIEW
직접 설계한 영업·마케팅 자동화 구조
흩어져 있던 고객 유입 정보를 영업 CRM에서 관리하고, 담당자가 상담·판매 결과를 다시 기록하는 구조를 설계했습니다. Meta 광고로 유입된 고객의 후속 전환 결과는 Conversions API로 광고 플랫폼에 전달하도록 구현했습니다.
04 / SALES WORKFLOW AUTOMATION
영업 업무를 어떻게 자동화했는가
문의 수집, 잠재 고객 발굴, 고객 관리, 결과 전달의 네 업무를 중심으로 구현했습니다.
인바운드 문의 자동 수집
광고 관리자와 업무용 휴대전화에서 발생한 문의를 각각 확인하고 정리하는 부담을 줄이기 위해, 두 경로의 수집 기능을 개발했습니다.
광고 관리자 확인 + 휴대전화 확인
채널별 문의를 따로 정리
Webhook·앱으로 문의 수집
영업 화면에서 모아 조회
Meta 광고 문의 수집
Meta Lead Ads Webhook으로 문의를 받아 InquirePhoneNumber에 저장했습니다. 원본 Lead ID는 이후 광고 전환 이벤트를 연결할 수 있도록 보존했습니다.
직접 개발한 Android 앱으로 전화·문자 수집
BroadcastReceiver로 수신 이벤트를 감지하고, Foreground Service의 별도 스레드에서 OkHttp POST를 실행했습니다. 전화는 전화번호를, 문자는 전화번호와 메시지를 하나의 Django API로 전달했습니다.
- 저장 메시지 유무로 유형을 구분하고, 기존 발송 대상(Farm)의 반응이면 반응·문자 내용을 기록했습니다. 그 외에는 신규 문의로 저장했습니다.
- 조회 정규화한 전화번호로 과거 문의를 묶어 최신 5건과 전체 건수를 표시했습니다. 고객 간 자동 병합이나 문의 ForeignKey 연결 방식은 아닙니다.
Android 이벤트 수집·저장 상세 구조
전화는 RINGING 상태의 업무용 접두번호를, 문자는 SMS_RECEIVED의 대상 표식을 확인합니다. 전화 수신 감지이며 통화 완료·통화 시간·통화 내용 수집은 아닙니다.
업무 변화 광고 관리자와 업무용 휴대전화에 분산된 문의 기록을 자동 수집해 담당자가 한곳에서 확인할 수 있도록 했습니다.
아웃바운드 잠재 고객 자동 발굴
구매자를 찾기 위해 게시글을 직접 열고, 구매 의향과 장비 조건을 정리하는 작업을 반복했습니다. 게시글 정기 수집과 NLP 분석으로 영업 대상 데이터를 가공했습니다.
게시글 검색·열람
구매 의향과 장비 조건 수동 정리
정기 수집 + 구매 조건 분석
정리된 영업 대상을 담당자가 검토
- 01crontab정기 실행
- 02게시글 수집온라인 구매 수요
- 03NLP 분석구매 의향·장비 조건
- 04영업 데이터 가공Farm에서 관리
- 05담당자 확인대상 검토·후속 연락
제조사·톤수·가격·지역 등의 조건을 구조화했습니다. 공개된 업체·농가 연락처 기반 발굴 데이터도 아웃바운드 대상으로 관리했고, 기존 발송 대상의 전화·문자 응답은 Farm의 반응 정보에 기록했습니다.
NLP 구조화 예시 보기
“전남에서 농사용 소형 굴삭기를 찾습니다.”
구매 의도 · 지역 · 장비 조건 · 사용 목적
구조화 목적을 설명하는 예시이며 실제 모델 출력은 아닙니다. 위 파이프라인은 게시글 수집에 해당하며, 업체·농가 연락처 발굴이 같은 수집기나 주기로 실행됐다는 뜻은 아닙니다.
업무 변화 직접 게시글을 찾아 조건을 정리하던 반복 업무를 줄이고, 구매 의향과 장비 조건이 정리된 데이터로 영업 대상을 검토하도록 했습니다.
고객 데이터 통합 및 영업 관리
수집한 원본과 실제 영업 고객을 구분하고, 고객으로 전환한 뒤에도 처음 유입된 경로를 확인할 수 있도록 Django·MySQL 기반 CRM 모델을 설계했습니다.
유입 출처를 보존하는 CRM 데이터 모델
인바운드·아웃바운드 원본은 따로 저장합니다. 영업 고객으로 전환할 때 Customer를 생성하고 LeadConversion으로 원본 유입 출처를 연결했습니다.
그림을 누르면 확대해 필드와 관계를 확인할 수 있습니다.
- 유입 원본과 CRM 고객 분리
- 공통 고객 모델로 전환
- 연결 테이블로 유입 출처 보존
- 고객별 미팅·관심 장비 관리
고객 전환 규칙과 전화번호 전처리
원본 선택 → Customer 생성 → LeadConversion 생성·ID 연결 순서입니다. 원본 FK 두 개는 NULL 허용, 고객 FK는 필수이며 출처 하나 선택·전환 1회 생성은 애플리케이션 규칙입니다. 이를 강제하는 DB의 CheckConstraint·UniqueConstraint는 없습니다.
전화번호는 하이픈 형식으로 통일했지만 출처 간 중복 고객을 자동 통합하지는 않았습니다. LeadConversion은 내부 CRM 전환 기록이며 Meta 광고 이벤트와는 별개입니다.
담당자는 CRM 화면에서 고객 정보와 상담 이력, 미팅·판매 진행 상태를 확인하고 수정했습니다.
업무 변화 수집 경로가 다른 데이터를 공통 고객 관리 체계로 연결하고, 담당자가 유입 출처와 진행 상태를 확인할 수 있도록 했습니다.
영업 결과의 광고 환류
광고 문의 이후 CRM에 기록된 영업 결과를 Meta 플랫폼에도 전달하기 위해 Conversions API를 연동했습니다. 원본 Lead ID가 있는 Meta 유입 고객에 대해 신규 등록 또는 상태 변경 시 이벤트를 생성·전송했습니다.
최초 광고 문의 조회: Client → Conversion → InquirePhoneNumber → Lead ID
이벤트 전송 조건과 JSON 구성
신규 등록은 Lead ID와 지정된 event_name이 있을 때 전송합니다. 기존 고객은 설문문의 유형이며 원본 Lead ID가 있는 경우, 미팅 확정(status=1)은 Schedule, 구매 완료(status=4)는 Purchase를 전송합니다. 0·2·3은 해당 변경 경로에서 미전송입니다.
전화번호를 8210… 형식으로 변환해 SHA-256 해싱하고, event_name·event_time·user_data.lead_id·user_data.ph·action_source·custom_data를 JSON에 담아 Graph API에 POST했습니다. Purchase 금액은 고정값이며 실제 계약 금액의 실시간 연동은 아닙니다.
실제 Meta 수신·매칭이나 광고 성과 개선을 검증한 결과는 아닙니다. 전송 실패 재시도·중복 제거 기능은 확인되지 않았습니다.
업무 변화 CRM의 영업 상태를 Meta 전환 이벤트로 전달해, 광고 문의 이후의 영업 결과를 광고 플랫폼에서도 활용할 수 있는 데이터 흐름을 구현했습니다.
이후 고객 홈페이지와 사내 업무에서 발생한 추가 요구를 해결하며 개발 범위를 넓혔습니다.
05 / BUSINESS SYSTEM EXPANSION
고객 홈페이지와 사내 업무 시스템 확장
영업에 참여하며 업체의 고객용 홈페이지도 직접 개발했습니다. 고가의 중고 굴삭기는 연식·가동시간 외에 실제 상태를 확인하는 과정이 중요했습니다. 온라인에서 관심을 가진 고객이 시운전 예약과 후속 상담으로 이어지도록 홈페이지 기능을 구성했습니다.
매물 탐색에서 예약·A/S 접수까지
고객이 굴삭기 매물 목록과 상세 정보를 확인하고, 문의나 시운전 예약으로 다음 상담을 이어갈 수 있도록 개발했습니다. 구매 이후에는 A/S 내용을 접수하고 고장 영상을 첨부할 수 있게 했습니다.
매물 목록·상세 확인 → 문의·시운전 예약 / 구매 이후 A/S 접수·영상 첨부
재고 관리 요청에서 사내 업무 통합 제안으로
업체로부터 장비 재고 관리 프로그램 개발 요청을 받아 장비 정보를 조회하고 관리하는 기능을 구현했습니다. 개발 과정에서 홈페이지의 고객 요청도 내부 업무 공간에서 함께 처리할 수 있겠다고 생각했습니다.
장비 재고 관리
내부 직원이 장비 정보를 조회하고 관리하는 프로그램을 개발했습니다.
예약·A/S 접수 내역 통합
홈페이지에서 발생한 시운전 예약과 A/S 접수까지 한곳에서 확인하는 방안을 제안하고 사내 통합 관리 프로그램으로 확장했습니다.
재고 관리 화면은 현재 공개 자료에 없어, 화면 확보 후 추가할 예정입니다.
고객의 접수를 담당자의 후속 업무로 연결
고객 홈페이지에서 접수된 시운전 예약과 A/S 내역을 사내 관리 프로그램(stock)에서 조회·처리하도록 연결했습니다. 고객은 홈페이지를 이용하고, 내부 담당자는 별도의 업무 공간에서 접수 내용을 확인했습니다.
고객의 예약·A/S 접수와 내부 담당자의 처리 업무를 연결한 역할 개념도입니다.
사내 프로그램은 stock.hyunjinkorea.co.kr로 제공했습니다. 담당자가 독립된 주소로 접근하도록 하고, 고객 홈페이지와 내부 업무 프로그램을 구분해 개발·수정·배포를 관리했습니다.
최종적으로 구축한 세 가지 업무 서비스
고객 홈페이지
매물 조회 · 시운전 예약 · A/S 접수
Django · 고객용 웹 앱영업 CRM · board
인바운드·아웃바운드 고객 데이터 · 고객별 상담 이력 · 영업 진행 상태와 판매 결과
Django · MySQL · 영업 보드사내 통합 관리 · stock
장비 재고 관리 · 시운전 예약 내역 · A/S 접수 내역 관리
stock.hyunjinkorea.co.kr · 별도 사내 업무 프로그램역할이 다른 세 가지 서비스를 구축하고, 각 서비스의 개발과 배포를 관리하기 위해 AWS와 Docker 기반 운영 환경을 구성했습니다.
06 / INFRASTRUCTURE ARCHITECTURE
요청 경로와 실행 환경을 구분해 운영했습니다.
단일 AWS EC2의 Ubuntu/Linux 환경에서 Nginx를 HTTPS 종료와 역방향 프록시로 운영했습니다. 고객용 Django 앱과 영업 CRM은 별도 컨테이너로 실행하고, Redis도 컨테이너로 운영했습니다. 사내 관리 프로그램에는 독립 서브도메인과 HTTPS를 구성했습니다.
웹 요청의 진입점과 EC2 안의 실행 단위를 구분한 구성도입니다.
- Nginx가 HTTPS를 처리하고 도메인별 웹 요청을 앱으로 전달합니다.
- 고객용 웹·CRM·Redis는 컨테이너로 운영했습니다. MySQL은 EC2 호스트 프로세스로 확인된 과거 시점을 표시했습니다.
- Celery Worker는 웹 요청에서 무거운 영상 변환을 분리하는 실행 단위입니다.
Redis의 두 용도와 데이터 처리
행동 이벤트 임시 적재
웹사이트의 페이지 탐색과 장비 조회 등 행동 이벤트를 Redis에 임시 적재하고, 주기적 ETL로 MySQL에 옮겼습니다. 적재된 데이터는 고객 행동과 유입 분석에 활용했습니다.
웹 행동 → Redis → 주기적 ETL → MySQL
Celery 메시지 브로커
영상 포맷 변환을 요청 처리에서 분리할 때 기존 Redis를 작업 메시지 전달에 재활용했습니다.
웹 앱 → Redis 브로커 → Celery Worker
Django·MySQL은 관계형 업무 데이터와 관리 기능에, Docker·Compose는 앱 실행 단위와 빌드·기동 관리에 사용했습니다.
07 / TROUBLESHOOTING
기존 Redis로 영상 변환을 요청 밖으로 옮겼습니다.
홈페이지의 A/S 영상 접수 기능을 운영하면서, 업로드 후 동기 포맷 변환이 HTTP 응답을 지연시키는 문제를 해결했습니다.
동기 포맷 변환을 기다리던 요청을 Celery 작업 처리와 분리한 Before / After입니다.
- 01 · 문제
A/S 영상 접수에서 변환이 끝날 때까지 HTTP 응답이 지연됐습니다.
- 02 · 원인
파일 크기와 포맷에 따라 처리 시간이 달라지는 변환 로직이 Django 요청 안에서 동기 실행됐습니다.
- 03 · 고려한 방법
변환을 백그라운드 작업으로 옮기기로 하고, 작업 메시지를 전달할 인프라를 새로 도입할 필요가 있는지 살폈습니다.
- 04 · 선택
이미 운영하던 Redis를 Celery 브로커로 재사용해 추가 인프라 도입 범위를 줄였습니다.
- 05 · 구현
Worker를 Docker 실행 환경에 분리하고 영상 포맷 변환을 백그라운드 작업으로 수행했습니다.
- 06 · 결과
웹 요청이 변환 완료를 직접 기다리던 병목을 완화했습니다. 개선 범위는 HTTP 응답 대기이며 업로드 전송이나 변환 자체의 가속을 뜻하지 않습니다.
08 / DEPLOYMENT AUTOMATION
서버 반영 과정을 자동 배포로 구성했습니다.
개발한 코드를 서버에 반영하고 이미지를 다시 빌드·기동하는 작업을 GitHub Actions와 SSH, Docker Compose로 연결했습니다. 배포 워크플로에서 EC2에 접속해 서버의 소스를 갱신하고 컨테이너를 재빌드·재기동하도록 구성했습니다.
코드 반영을 시작하는 Actions와 EC2에서 실행하는 Compose의 책임을 보여줍니다.
- Actions에서 SSH로 EC2에 접속하고 소스 갱신·Compose 빌드와 재기동을 실행했습니다.
- Compose의 실행 관리와 Nginx의 사용자 요청 처리를 구분했습니다.
GitHub Actions → EC2
서버에 접속해 배포 명령을 실행하는 SSH 키
EC2 → GitHub 저장소
서버가 소스를 갱신하는 저장소 접근용 Deploy Key
과거 board 배포에서 사용한 절차
서버 작업 디렉터리에서 아래 명령으로 소스를 갱신하고 서비스를 재기동했습니다. 내부 경로와 인증 정보는 제외했습니다.
git pull origin main
docker compose down
docker compose up -d --build
자동화한 범위는 서버 소스 갱신과 컨테이너 빌드·재기동입니다. 위 절차는 컨테이너를 내린 뒤 재기동하므로 배포 중 서비스가 중단될 수 있는 방식입니다.
09 / OPERATIONS & REFLECTION
요청·파일·배포가 만나는 지점을 운영했습니다.
도메인 · Nginx · HTTPS
고객용·관리용 서비스의 도메인과 가상호스트를 구성하고 Certbot 인증서를 운영했습니다. 프록시 연결과 인증서 설정 문제를 추적했습니다.
호스트와 컨테이너의 경계
컨테이너 내부 포트와 호스트 매핑을 구분하고 DB 접속·권한 문제를 다뤘습니다. MySQL 접근에 SSH 터널링을 사용한 경험도 있습니다.
Django static · media
정적 파일과 업로드 파일 경로를 구분하고 파일 권한, 이미지 경로와 저장·정렬 문제를 해결했습니다.
배포 실패 진단
Actions 워크플로와 SSH 인증 실패를 조사하고 서버 소스 갱신과 컨테이너 재기동 경로를 확인했습니다.
기능의 다음 단계까지 보는 관점
온라인 문의가 담당자의 후속 업무로 이어지고, 배포한 앱이 실제 요청을 처리하는지까지 살폈습니다. 현장 업무의 맥락을 이해하는 것과 호스트·컨테이너·프록시의 연결을 이해하는 일이 함께 필요했습니다.