new-term-radar
신규 AI 용어를 매일 추적, X·Hugging Face·GitHub 등에서 모델·제품·레포 이름을 빠르게 수집
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
크리에이터 핸들 · 금액 · 제품만 입력하면 브랜드 HTML 메일이 완성돼 네이버웍스로 즉시 발송된다. TikTok Shop 크리에이터 섭외용. email-template.js 는 브라우저 미리보기와 서버 발송이 같은 코드를 쓴다. 템플릿을 두 곳에서 만들면 "미리보기와 실제 메일이 다른" 사고가 나므로 여기 한 곳에서만 만든다. 1. Vercel → Add New Project → 이 레포 임포트 2. 프레임워크 프리셋 Other, 빌드 설정 변경 없음 (정적 파일 + 서버리스 함수) 3. 아래 환경변수 등록 → DeployREADME 전문 보기 ↗
크리에이터 핸들 · 금액 · 제품만 입력하면 브랜드 HTML 메일이 완성돼 네이버웍스로 즉시 발송된다. TikTok Shop 크리에이터 섭외용.
dalba-outreach/
├─ index.html ← 발송 화면 (로그인 + 입력 폼 + 실시간 미리보기)
├─ admin.html ← 관리자 화면 (팀 전체 현황 · 중복 시도 · 메일함)
├─ email-template.js ← 메일 템플릿 엔진 (브라우저·서버 공용)
├─ auth.js ← 직원 계정 목록 · 로그인 세션
├─ history.js ← 발송 이력 (팀 공용) · 중복 발송 차단
├─ mail.js ← 네이버웍스 IMAP 읽기
├─ sync.js ← 메일함 → 이력 변환 (버튼·자동 실행 공용)
├─ reminders.js ← 회신 없을 때 자동 팔로업(리마인드) 발송
├─ sheets.js ← 구글시트 읽기 (제품별 매출 상위 영상)
├─ i18n.js ← 화면 번역 (한국어 · English · Tiếng Việt)
├─ api/
│ ├─ login.js ← 로그인 (아이디/비번 → 세션 토큰)
│ ├─ outreach-send.js ← 네이버웍스 SMTP 발송
│ ├─ history.js ← 겹치는 수신자 미리 확인 · 최근 발송 조회
│ ├─ admin.js ← 팀 전체 집계 (관리자 전용)
│ ├─ replies.js ← 받은편지함을 훑어 크리에이터 회신 기록
│ ├─ backfill.js ← 보낸편지함을 이력으로 가져오기
│ ├─ cron.js ← 자동 동기화 (Vercel Cron 이 부른다)
│ ├─ products.js ← TikTok Shop 상품 목록 (썸네일·판매상태)
│ └─ videos.js ← 제품별 매출 상위 영상 (구글시트)
├─ package.json
└─ vercel.json
email-template.js는 브라우저 미리보기와 서버 발송이 같은 코드를 쓴다. 템플릿을 두 곳에서 만들면 "미리보기와 실제 메일이 다른" 사고가 나므로 여기 한 곳에서만 만든다.
| 이름 | 필수 | 설명 |
|---|---|---|
NW_ACCOUNTS | 권장 | 직원 목록. 넣으면 담당자별 로그인 방식으로 동작한다 (아래 참고) |
SESSION_SECRET | 선택 | 세션 토큰 서명 키. 없으면 NW_ACCOUNTS 에서 파생 — 이 경우 직원 목록을 고칠 때마다 전원 재로그인 |
SESSION_DAYS | 선택 | 로그인 유지 기간, 기본 7 일 |
DASHBOARD_PASSWORD | 대체 | 공용 비밀번호. NW_ACCOUNTS 를 넣으면 쓰이지 않는다 (로그인이 대신함) |
NW_DOMAIN | 선택 | 발신 허용 도메인, 콤마 구분. 기본 dalbausa.com,dalba.com |
NW_SMTP_HOST | 선택 | 기본 smtp.worksmobile.com |
NW_SMTP_PORT | 선택 | 기본 465 (SSL). 587 이면 STARTTLS 로 자동 전환 |
NW_BCC | 선택 | 발송 사본을 받을 주소 — 팀 공용 발송 이력 보관용 |
LOGO_URL | 선택 | 메일 헤더 로고 이미지의 공개 절대주소. 없으면 텍스트 워드마크로 그린다 |
KV_REST_API_URLKV_REST_API_TOKEN | 권장 | 발송 이력 저장소. 중복 발송 차단이 여기에 달려 있다 (아래 참고). Vercel 마켓플레이스가 자동 주입 |
HISTORY_DAYS | 선택 | 재발송 차단 기간, 기본 90 일 |
HISTORY_SINCE | 선택 | 메일함에서 이력을 채울 시작일, 기본 2026-05-01 |
HISTORY_LOG_MAX | 선택 | 발송 이력 보관 상한, 기본 100000 건. 담당자가 많아 더 쌓이면 키운다 |
CRON_SECRET | 권장 | 자동 동기화(/api/cron)를 아무나 못 부르게 하는 비밀값. Vercel 이 자동으로 헤더에 실어 보낸다 |
ADMIN_EMAILS | 선택 | 관리자 화면을 볼 수 있는 이메일 여러 명. 콤마·세미콜론·공백·줄바꿈 아무거로 구분해도 된다. 비우면 아무도 관리자가 아니다 |
NW_IMAP_HOST | 선택 | 기본 imap.worksmobile.com |
NW_IMAP_PORT | 선택 | 기본 993 (SSL) |
TikTok Shop 상품 선택기를 쓰려면 TTS_* 네 개가,
매출 상위 영상 드롭다운을 쓰려면 GOOGLE_SERVICE_ACCOUNT 가 추가로 필요하다 (아래 참고).
NW_ACCOUNTS)담당자마다 아이디·비밀번호를 주고, 네이버웍스 앱 비밀번호는 서버에만 둔다. 담당자는 앱 비밀번호를 알 필요가 없고 브라우저에도 남지 않는다.
Vercel 환경변수 NW_ACCOUNTS 에 한 줄에 한 명씩 넣는다:
아이디|로그인비번|표시이름|네이버웍스이메일|앱비밀번호|직함(선택)
hannie|Hann2026!|Hannie|[email protected]|abcd efgh ijkl mnop|Creator Partnerships
quinn|Qu1nnX7!|Quinn|[email protected]|wxyz 1234 5678 9012|Creator Partnerships
# 로 시작하는 줄과 빈 줄은 무시 → 주석·구분선으로 써도 된다| 는 못 쓴다 (로그인 비번에 | 가 필요하면 JSON 형식을 쓸 것 — auth.js 주석 참고)설정 > 보안 > 외부 앱 비밀번호 값이다수정 후 Redeploy 해야 반영된다. 퇴사·교체는 그 줄만 지우면 되고, 줄을 지우는 즉시 그 사람이 갖고 있던 세션 토큰도 무효가 된다.
From 으로 구분되고, 답장도 그 사람에게 간다NW_ACCOUNTS 를 비워두면 예전 방식으로 동작한다 — 담당자가 자기 네이버웍스 주소와
앱 비밀번호를 자기 브라우저에 등록하고, 그 자격증명이 요청마다 함께 온다.
이때는 DASHBOARD_PASSWORD 로 화면을 막고, 발신 주소는 NW_DOMAIN 회사 도메인만 허용한다.
자격증명은 서버에 저장하지 않는다.
현재 기본값: 담당자끼리 중복 차단은 꺼져 있다. 다른 담당자가 이미 보낸 크리에이터여도 그대로 발송된다 (발송 화면엔 "ℹ️ 다른 담당자 발송 있음" 참고 표시만). 계속 막히는 것은 🤝 협업 중(관리자·본인 발송 이력 제외), ⛔ 반송 주소 · 🚫 수신 거부, 같은 담당자가 30분 안에 같은 크리에이터에게 다시 보내기(시간 초과 뒤 재시도 중복 방지). 아래의 담당자끼리 차단을 다시 켜려면 Vercel 환경변수
DEDUP_ACROSS_STAFF=1.
담당자가 10명이면 명단이 겹치는 건 시간 문제다. 같은 크리에이터가 d'Alba 메일을 두세 번 받으면 브랜드 인상에 바로 흠이 나므로, 발송 직전에 팀 공용 이력을 확인하고 이미 보낸 사람은 그 건만 보류한다.
HISTORY_DAYS 로 변경)[email protected] 은 [email protected] 과 같은 편지함으로 본다force 를 무시한다.두 명이 같은 사람에게 동시에 누르는 경합은 조회로는 못 막는다(둘 다 "없음"을 본다). 그래서 조회가 아니라
SET NX(없을 때만 쓰기)로 자리를 먼저 잡고 보낸다.
KV_REST_API_URL / KV_REST_API_TOKEN 이 자동으로 들어간다무료 티어로 충분하다. 발송 1건당 명령 23개, 미리 확인은 명단 1명당 12개다.
이 도구를 쓰기 전에 나간 메일도 중복 판정에 들어와야 한다. 안 그러면 툴을 켠 날 이전에 접촉한 크리에이터에게 그대로 다시 나간다. 관리자 화면의 [📥 지난 발송 가져오기] 탭에서 담당자별로 보낸편지함을 읽어 넣는다.
Collab)messageId 로 판별). 여러 번 돌려도 안전하다⚠️ 저장소를 아예 설정하지 않으면 이 기능은 조용히 꺼지고 발송은 예전처럼 된다. 반대로 설정은 했는데 저장소가 죽으면 중복인지 알 수 없으므로 보내지 않는다. 이때는 강제 발송이 유일한 탈출구다 — 켜 놓고 조용히 중복이 나가는 게 가장 나쁜 결과라서. 관리자가 아닌 담당자는 이 경우 보내지 못하고 보류로 남는다.
/admin.html)ADMIN_EMAILS 에 등록된 사람만 볼 수 있다 — 여러 명을 콤마·세미콜론·공백·줄바꿈
아무거로 구분해 넣으면 된다 (예: [email protected], [email protected]).
발송 화면과 같은 로그인을 쓰므로 두 번 로그인하지 않아도 되고,
관리자에게만 발송 화면 헤더에 바로가기가 뜬다.
관리자 본인은 집계에서 빠진다. 관리자는 감시하는 쪽이라 담당자 실적 대상이 아니다.
ADMIN_EMAILS에 있는 사람은 담당자별·일별·발송 이력·대화·명단 어디에도 나오지 않는다 (관리자이자 담당자로 등록된 사람도 마찬가지). 실적을 봐야 하는 담당자는NW_ACCOUNTS에는 있고ADMIN_EMAILS에는 없어야 한다.
| 탭 | 보이는 것 |
|---|---|
| 담당자별 | 누가 몇 건 보냈는지 · 회신·회신율 · 중복으로 막힌 횟수 · 마지막 발송 |
| 📈 일별 | 날짜별 발송·회신 건수 (막대) + 담당자별 내역. 담당자·기간으로 거르기 |
| 🗂 내 파이프라인 | 개인용 칸반. 내가 접촉한 크리에이터를 단계별로: 첫 발송 → 1차 리마인드 → 2차+ 리마인드 → 1차 회신 → 2차+ 회신. 카드를 누르면 대화가 열린다 |
| 💬 대화 | 담당자별로 회신한 크리에이터 목록 → 누르면 협상 스레드를 본문까지 (아래 참고) |
| 발송 이력 | 시각·수신자·핸들·캠페인·담당자. 담당자/기간/검색어로 거르기 |
| ⏸ 중복 시도 | 보내려다 막힌 시도. 누가 누구에게 시도했고 원래 누가 보냈는지 |
내가 접촉한 크리에이터를 단계별로 칸반으로 보여준다: 첫 발송(회신 대기) → 1차 리마인드 보냄 → 2차+ 리마인드 보냄 → 1차 회신 → 2차+ 회신(협상). 카드를 누르면 그 사람과의 대화가 열린다.
관리자가 담당자 ↔ 크리에이터 대화를 본문까지 열어 본다. 목록은 회신한 크리에이터만 뜨고(협상은 회신이 있어야 성립), 한 명을 누르면 그 담당자 메일함에서 주고받은 메일을 시간순 채팅으로 보여준다 — 담당자가 보낸 것(오른쪽)과 크리에이터 회신(왼쪽) 양쪽 다.
지켜지는 선:
ADMIN_EMAILS 밖의 담당자는 이 경로로도 남의 메일함을 못 본다. 접근 통제는 이 하나다.mailparser 를 쓴다.GET /api/thread?staff=<담당자>&peer=<크리에이터> (관리자 전용).서버리스에는 상주 프로세스가 없어 회신이 자동으로 세어지지 않는다. 담당자별 화면의 [📥 회신 수집] 을 누르면 전원의 받은편지함을 훑어 우리가 보낸 적 있는 주소에서 온 메일만 기록한다. 화면을 열 때마다 IMAP 을 뒤지면 담당자가 10명일 때 못 견디므로, 세어서 남겨 두고 집계는 그 기록만 읽는다.
messageId 로 판별) — 여러 번 눌러도 안전하다POST /api/replies 를 부르면 된다기록의 시각은 UTC 다. 한국에서 아침 8시에 보낸 건 UTC 로는 전날 23시라,
그대로 날짜를 자르면 하루씩 밀린다. 그래서 브라우저가 tz(분 단위 시차)를 보내고
서버가 그만큼 옮겨서 자른다. 발송이 없던 날도 0 으로 채운다 — 빈 날을 빼면
막대가 다닥다닥 붙어 "매일 꾸준히 보낸 것" 처럼 보이기 때문이다.
중복 시도 탭이 이 화면의 핵심이다. 같은 크리에이터가 자주 걸린다면 명단 배분을 고쳐야 한다는 신호이고, 그 신호는 다른 곳에서는 보이지 않는다.
발송 이력은 최대 10만 건(HISTORY_LOG_MAX)까지 보관한다. 담당자가 10명이면
몇 달치가 쉽게 만 단위를 넘는데, 집계(담당자별·일별·요약)는 저장된 전부를 읽어 센다.
저장소에서 값을 내려받을 때는 큰 리스트가 REST 응답을 터뜨리지 않도록 청크로 나눠
받고, 실제 쌓인 만큼만(LLEN) 읽어 멈춘다. 발송 이력·중복·회신 목록 탭은 화면 표시만
개수로 끊고(limit, 기본 1,000), 상한에 걸리면 화면에 "잘렸음" 을 알린다 —
집계 숫자는 목록 표시 개수와 무관하게 항상 전체 기준이다.
ADMIN_EMAILS 가 비어 있으면 아무도 관리자가 아니다(화면이 열리지 않는다).
관리자 판정을 코드에 숨겨 두면 나중에 이 배포를 보는 사람이 권한 범위를 알 수 없기 때문에,
일부러 "설정하지 않으면 닫힘" 으로 만들었다.
담당자별 화면의 [🔄 메일함에서 동기화] 하나로 두 가지를 채운다.
2026-05-01 이후 보낸 메일을 이력에 넣는다발송에 쓰는 그 외부 앱 비밀번호가 IMAP 에도 그대로 통한다. 새로 등록할 것은 없다.
envelope 만 fetch)messageId 로 판별). 여러 번 눌러도 안전하다Sent / 보낸메일함 …) IMAP 의 용도 플래그(\Sent)
로 찾고, 없을 때만 이름으로 추측한다 — 이름 목록에 의존하면 어느 계정에서 조용히 빈 목록이 된다그대로 밀어 넣으면 동료·거래처 주소가 크리에이터로 잡히고, 그 주소로는 앞으로 발송이 막힌다.
그래서 사내 도메인(NW_DOMAIN) 수신자와 수신자 6명 이상(공지·회람)은 자동으로 거른다.
그래도 섞이는 게 있으면 POST /api/backfill 에 subject 를 주어 좁힐 수 있다.
⚠️ 관리자센터에서 IMAP/POP3 사용 허용이 켜져 있어야 한다 (SMTP 와 같은 설정 화면).
vercel.json 의 crons 가 15분마다 /api/cron 을 부른다 (Pro 플랜 기준 — Hobby 는
하루 1회로 제한되므로 그때는 0 22 * * * 같은 하루 1회 스케줄로 바꿔야 한다).
매 실행은 ① 예약 발송(기한이 된 것 — 시각 약속이라 가장 먼저) ② 메일함 동기화
③ 자동 리마인드 순서로 처리한다.
CRON_SECRET 을 설정하면 그 값을 가진 요청만 부를 수 있다 (Vercel 이 자동으로 실어 보낸다).
로그인한 관리자도 /api/cron 을 직접 열어 상태를 확인할 수 있다발송 화면 상단 [🔎 중복 검사] 탭에서 크리에이터 핸들 또는 이메일을 넣으면, 전체 담당자 (관리자 포함) 의 발송 이력에서 그 사람에게 보낸 적이 있는지 · 누가 · 언제 보냈는지를 찾아 준다.
api/lookup.js 가 공용 발송 로그(+회신 로그)를 훑어 그 크리에이터에게 간 모든 발송을 모은다
(예약 키와 달리 "누가누가 보냈나" 를 전부 보여준다). 관리자 발송도 빼지 않는다.발송 화면에서 [나중에 보내기] 를 켜면 시간대·날짜·시각을 골라 예약한다. 미국 크리에이터에게는 미국이 활발한 시간, 영국은 영국 시간에 맞춰 보내기 위한 것 — 고른 시간대의 현지 시각 기준이다.
api/schedule.js — Intl 시간대 규칙 사용)send-core.js)라 중복 차단·자동 리마인드·CC·첨부가 똑같이 적용된다발송 화면에서 [회신 없으면 자동 리마인드] 를 켜면, 그 발송에 대해 며칠마다·최대 몇 번 팔로업을 보낼지 담당자가 직접 정한다(기본 3일마다·최대 2번). 회신이 오면 즉시 멈춘다.
outreach:reminders HASH — 담당자+크리에이터 한 쌍당 하나)./api/cron)가 메일함을 훑어 회신을 갱신한 뒤 기한이 된 예약을 처리한다.
· 회신했으면 → 멈춤 · 정한 횟수를 다 채웠으면 → 멈춤 · 담당자가 명단에서 빠졌으면 → 멈춤Re:). 긴 제안을 다시 보내지
않는다 — 짧은 넛지가 회신율이 더 높고 스팸처럼 보이지 않는다. 헤더 로고·서명·"no thanks 면 그만"
안내는 그대로 붙는다.크론이 15분마다 돌므로 "3일마다" 는 예약 시각 이후 첫 크론(최대 15분 안)에 나간다. Hobby 플랜으로 내리면 크론이 하루 1회라 리마인드도 하루 한 번만 처리된다.
POP3/IMAP은 메일을 받는 프로토콜이고, 프로그램이 보내려면 SMTP 인증이 필요하다. Gmail 연동·Gmail API는 불필요 — 웍스 SMTP 위에 한 겹 더 얹는 것이라 단계만 늘어난다.
| 항목 | 값 |
|---|---|
| SMTP 서버 | smtp.worksmobile.com |
| 포트 | 465 (SSL) — 587(STARTTLS)도 지원 |
| 아이디 | 네이버웍스 이메일 전체 (예: [email protected]) |
| 비밀번호 | 로그인 비번 ❌ → 외부 앱 비밀번호 ⭕ |
설정 > 보안 > 외부 앱 비밀번호 에서 발급NW_ACCOUNTS 에 등록 → 담당자는 아이디/비번으로 로그인만 하면 끝
(로그인을 안 쓰는 배포라면 화면 오른쪽 위 [내 발신 계정] 에 직접 입력)계정당 24시간 20,000 수신자, 분당 60회. To/Cc/Bcc 모두 카운트되고 PC웹·모바일·SMTP·API 가 합산된다. 초과 시 각각 최대 1시간 / 1분 차단.
사람마다 환경변수를 따로 두면 입사할 때마다 Vercel 설정 + 재배포가 필요해 확장이 안 된다.
그래서 NW_ACCOUNTS 한 개에 줄만 추가하는 방식을 쓴다 (위 담당자별 로그인).
Reply-To 동일)오픈 릴레이 방지 3겹 — ① SMTP 호스트/포트는 서버 고정(요청으로 변경 불가)
② 발신자는 로그인 세션에서 확정 (BYO 배포에서는 NW_DOMAIN 회사 도메인만 허용)
③ 로그인 없이는 발송 API 자체가 401
크리에이터는 대부분 폰으로 메일을 연다. 그래서 폭을 두 겹으로 맞춘다.
width:100% + max-width:600px 다.
예전에는 width="600" 이 박혀 있어서 좁은 화면에서 잘렸다.
표의 width 속성은 max-width 를 모르는 클라이언트에서 그대로 이겨 버린다.Outlook 데스크톱(Word 엔진)은 max-width 를 무시해 본문이 창 너비만큼 늘어난다.
그래서 MSO 전용 주석으로 600px 고정 표를 하나 더 감쌌다(고스트 테이블).
검증: 320 / 360 / 393 / 768 / 1280px 에서 가로 넘침 0, <style> 를 통째로 지운 상태에서도 320px 통과.
화면 오른쪽 미리보기 위의 [📱 모바일] 을 누르면 390px 폭으로 좁혀 보인다. 메일 안의 미디어 쿼리가 iframe 폭에 반응하므로 실제 폰에서 보이는 그대로다.
헤더(달바 로고·캠페인 배지·제목) → 인사 → 브랜드 소개 → 개인화 한 줄 → 💰금액 / 📈커미션 → 📦협업 상세 → 🧴제품(이미지·설명·링크) → 🔥바이럴 영상 최대 4개 → 📋Next steps → CTA 버튼 → 서명
브랜드 컬러 — 헤더는 배너와 같은 노랑→크림 그라데이션에 검정 로고다. 그라데이션을 못 읽는 클라이언트(Outlook 등)는 단색 노랑으로 떨어져도 브랜드에 맞다. 본문은 흰 카드 + 따뜻한 크림 박스, 강조는 앰버, CTA 는 검정 버튼.
로고 — 기본은 실제 로고를 본뜬 세리프 d'Alba + piedmont **워드마크(텍스트)**라
어떤 클라이언트에서도 반드시 보인다.
실제 이미지 로고는 레포의 logo-black.png(검정 워드마크·투명배경)를 쓴다.
메일 안에 인라인(cid)으로 박아 넣는다 — 외부 URL 로 불러오면 배포 보호·리다이렉트·
전파 지연 등으로 그 요청이 실패해 로고가 깨질 때가 있는데, 파일을 메일에 담으면
외부 요청이 아예 없어 깨지지 않는다(제품 이미지 첨부와 같은 방식). 미리보기도 같은
파일을 data URL 로 그려 외부 요청이 없다. 이미지를 아예 꺼둔 수신자에게는 alt 로
브랜드명이 뜬다. 다른 도메인·CDN 을 쓰려면 LOGO_URL 에 공개 절대주소를 넣으면
인라인 대신 그 URL 을 쓴다.
협업 유형 3종: 유상 / 어필리에이트 / 유상+어필리에이트(합산 문구 자동 추가). 후불제 — "선불(up front)" 이 아니라 "콘텐츠 게시·승인 후 지급" 으로 문구를 맞췄다
제품 이미지 — URL 직링크를 넣거나, 상품이 TikTok Shop 목록에 없으면 파일을 첨부한다. 첨부한 이미지는 브라우저에서 최대 1000px 로 줄여 JPEG 로 다시 인코딩(요청 본문·발송 속도 보호)한 뒤 메일에 인라인(cid) 첨부로 담긴다 — 외부 링크가 아니라 메일 안에 들어가므로 링크 없이도 보인다. URL 과 첨부가 둘 다 있으면 첨부가 우선
Next steps — 크리에이터가 답장에 무엇을 담아야 하는지 번호로 제시. 4줄까지 화면에서 편집하고 빈 칸은 자동으로 빠진다. 전부 비우면 섹션째 사라짐. 기본값은 ①관심 회신 ②WhatsApp/선호 연락처
개인화 변수 — 제목·본문 어디서나
{{name}} {{handle}} {{brand}} {{campaign}} {{product}} {{amount}} {{commission}}
<table> + 인라인 CSS — Gmail·네이버웍스 웹에서 레이아웃이 깨지지 않음.
플레인 텍스트 대체본을 함께 만들어 스팸 점수를 낮춘다
통화는 기본 USD. KRW·GBP·EUR·JPY 선택 가능
(통화 기호 대신 USD 500 처럼 코드로 표기 — ₩ 의 가로 이중선이 숫자에 취소선처럼 겹쳐 보임)
제품 칸 위의 [TikTok Shop에서 선택] 버튼을 누르면 썸네일 그리드가 뜨고, 상품을 클릭하면 제품명 · 이미지 URL · 제품 링크가 한 번에 채워진다.
ACTIVATE)만 기본 표시. 체크박스로 전체 보기 전환.webp → .jpeg 로 바꿔 내보낸다 (Outlook 등이 webp 를 렌더링하지 못함)| 이름 | 어디서 |
|---|---|
TTS_APP_KEY | Partner Center → Manage apps → App key |
TTS_APP_SECRET | 같은 화면 → App secret |
TTS_SHOP_CIPHER | 샵별 식별자 |
TTS_REFRESH_TOKEN | 샵 Authorize 후 /api/v2/token/get 으로 발급 |
액세스 토큰은 저장하지 않는다. 서버리스라 디스크에 쓸 수 없으므로 refresh token 으로
필요할 때 발급받아 메모리에만 두고, 만료 5분 전 선제 갱신 + 105002 응답 시 1회 재시도한다.
⚠️ refresh token 을
dalba-check와 공유하면, TikTok 이 갱신 때마다 새 refresh token 을 발급하는 정책일 경우 한쪽이 갱신했을 때 다른 쪽이 만료될 수 있다. 인증 오류가 반복되면 앱을 하나 더 만들어 토큰을 분리하는 것이 정석이다.
GET /api/products?debug=1 — 원본 응답의 필드 구조를 함께 반환GET /api/products?all=1 — 판매중이 아닌 상품까지 포함GET /api/products?id=<상품ID> — 그 상품 하나만 (목록을 거치지 않는다)GET /api/products?fresh=1 — 10분 캐시 무시⚠️ 페이지네이션 주의 —
products/search의page_token은 쿼리 파라미터다. 본문에 넣으면 API 가 조용히 무시하고 매번 1페이지를 돌려주므로, 같은 상품이 두 번 나오고 100번째 뒤 상품은 영영 안 보인다. 그래서 응답의total이 실제 상품 수와 맞는지가 이 엔드포인트의 첫 번째 점검 항목이다 (id기준 중복 제거도 함께 걸어 뒀다).
왜 CRUVA 가 아니라 TikTok Shop 원본 API 인가: CRUVA 응답에는 상품 이미지 필드가 없고, 상태도 문서화되지 않은 숫자 코드(1/3/4/6)로만 온다. 상품 페이지에서 og:image 를 긁는 우회로는 연속 요청 시 CAPTCHA 가 떠서 쓸 수 없다. 원본 API 는
main_images와 문자열status를 함께 주므로 두 문제가 한 번에 풀린다.
상품을 고르면 그 제품ID(pid)로 실제 매출이 난 영상이 매출순 드롭다운으로 뜬다.
누르면 바이럴 영상 칸에 URL 과 @핸들 · GMV $12,345 라벨이 함께 채워진다.
출처는 TikTok Shop API 가 아니라 구글시트다 — 그 집계는 이미
영상성과_신API테스트 탭에 매일 쌓이고 있어서, 같은 API 를 두 번 부를 이유가 없다.
읽는 열은 F(핸들) · H(GMV) · M(영상ID) · O(제품ID) 네 개뿐이다.
F:O 범위만 받아온다 (통째로 받으면 함수 시간·메모리를 넘긴다)| 이름 | 기본값 / 설명 |
|---|---|
GOOGLE_SERVICE_ACCOUNT | 서비스 계정 JSON 통째로. 시트를 그 계정 이메일에 뷰어로 공유해야 한다 |
SHEET_ID | 1_qkd6LZ1wFoihhJSuYdabQ4iRbx-jsFYVxeGIoEb-_g |
SHEET_TAB | 영상성과_신API테스트 |
SHEET_CSV_URL | (대안) 웹에 게시한 CSV 주소 |
⚠️
SHEET_CSV_URL은 설정이 제일 쉽지만 주소를 아는 사람은 누구나 볼 수 있다. 크리에이터 핸들과 매출이 담긴 표라서, 시트를 비공개로 두는 서비스 계정 쪽을 권장한다. 서비스 계정 JSON 에는 개인키가 들어 있다 — Vercel 환경변수에만 넣고 레포에 커밋하지 말 것.
GET /api/videos?probe=1 로 설정 여부만 먼저 확인할 수 있다.
헤더 오른쪽 KO / EN / VI 버튼으로 바꾼다. 선택은 브라우저에 저장되고, 처음 여는 사람은 브라우저 언어를 따라 자동으로 맞춰진다.
번역은 i18n.js 가 그려진 뒤의 DOM 을 훑어 바꾸는 방식이다. 화면을 그리는 코드는
하나도 건드리지 않았고, MutationObserver 가 붙어 있어 나중에 추가되는 문구도 자동으로 잡힌다.
사전의 키는 한국어 원문 그대로다 — 번역이 없으면 한국어가 그대로 남지, 화면이 비지는 않는다.
주의할 점 세 가지 (전부 실제로 밟았던 함정):
{n} {e} 로 가려 두고 번역 후 되돌린다. 안 그러면 사전 키가 값마다 달라진다setAttribute 는 값이 같아도 변경 기록을 남긴다 → 옵저버가 다시 돌아 무한 루프.
모든 쓰기 앞에 같은 값인지 비교를 건다.langs). 안 그러면 EN 화면에서 "한국어" 버튼이 사라진다메일 본문은 번역되지 않는다. 화면 UI 만이다. 크리에이터에게 나가는 메일 문구는 담당자가 직접 쓰는 값이라 자동 번역 대상이 아니다.
폼에 입력한 캠페인·제품·영상·Next steps 는 그 사람 브라우저에 자동 저장되어 다음에 열면 그대로 남아 있다. 한 번만 세팅하면 이후엔 크리에이터 정보만 넣으면 된다.
반면 수신자별 값(이름·핸들·이메일·개인화 한 줄)은 일부러 저장하지 않는다 — 앞사람 내용이 다음 사람에게 잘못 나가는 것이 이 툴에서 가장 위험한 사고이기 때문.
구글시트에서 헤더 포함 복사 → 붙여넣기. 탭(시트)·콤마(CSV) 모두 인식.
| 인식 열 | 의미 |
|---|---|
email (필수) | 수신 주소 |
name · handle | 이름 / 틱톡 핸들 |
amount · commission | 있으면 공통 조건보다 우선 (사람마다 다른 단가) |
whyYou | 개인화 한 줄 |
E-Mail, Email Address, Creator Name, TikTok Handle, 한글 헤더 등)| 요청 | 동작 |
|---|---|
GET /api/login | 이 배포의 인증 방식(accounts / byo)·세션 기간·등록 인원 수. 아이디 목록은 알려주지 않음 |
POST /api/login | { id, pw } → { token, user }. 실패 메시지는 아이디·비번을 구분하지 않음 |
GET /api/outreach-send | 설정 상태 조회 (SMTP 호스트·허용 도메인·배치 상한·로그인한 사람). 비밀번호류는 반환하지 않음 |
POST /api/outreach-send | 발송. { campaign, recipients[] } — 로그인 방식에서는 auth 불필요 |
POST + force: true | 이미 보낸 사람에게도 발송 (중복 차단 무시). 관리자만 유효 — 아니면 서버가 무시 |
POST + dryRun: true | 발송 없이 렌더 결과(제목·HTML)만 반환 |
GET /api/history | 중복 차단이 켜져 있는지 · 차단 기간 |
GET /api/history?recent=200 | 최근 발송 기록 (누가·언제·누구에게) |
POST /api/history | { recipients[] } 중 겹치는 사람 미리 확인 |
GET /api/admin | 담당자별 집계 (관리자 전용) |
POST /api/replies | 받은편지함을 훑어 회신 기록. all:true 는 관리자만(전원) |
GET /api/admin?view=replies | 회신 기록 (담당자·검색어로 거르기) |
GET /api/admin?view=daily&tz=-540 | 일별 발송 건수. tz 는 브라우저 시차(분) — 없으면 UTC 기준이라 날짜가 밀린다 |
GET /api/admin?view=people|sent|blocked | 크리에이터별 / 발송 이력 / 중복 시도. by q days limit 로 거르기 |
POST /api/backfill | 보낸편지함 → 발송 이력. since 기본 2026-05-01, dryRun:true(기본)면 미리보기만 |
GET /api/products | 판매중 상품 목록 (썸네일·가격·재고) |
GET /api/products?probe=1 | 자격증명 설정 여부만 확인 (상품 조회 안 함) |
GET /api/videos?pid=<상품ID> | 그 제품의 매출 상위 영상 (핸들·GMV·영상ID·URL). limit 기본 20 |
GET /api/videos?probe=1 |
/api/login 을 뺀 나머지는 인증이 필요하다 —
로그인 방식은 Authorization: Bearer <token>, BYO 방식은 x-dashboard-password: <비번>.
발송 화면의 ✉️ 발송은 d'Alba(노랑), 🌿 Veganery 발송은 비거너리 by d'Alba(연두~초록) 메일이다.
logo-black.png ↔ logo-veganery.png)와 색상이 바뀐다 — 미리보기·실제 발송·자동 리마인드·예약 발송 모두 같은 테마email-template.js 의 THEMES, 로고는 logos.js 한 곳에서 관리한다 (새 브랜드도 같은 방식으로 추가)실제 네이버웍스·Upstash 에 붙지 않고, 가짜 저장소(메모리)와 가짜 메일 서버로 돈다. 비밀값도 필요 없다.
npm test # 서버 테스트 (test/*.test.js) — 설치 없이 바로 돈다
npm run test:ui # 화면 테스트 (test/ui/*.test.mjs) — playwright + chromium 필요
npm install --no-save playwright && npx playwright install chromium 뒤에 돌린다.
Chromium 을 따로 깔아 뒀으면 CHROMIUM_PATH=<경로> 로 지정할 수 있다..github/workflows/test.yml). 실패하면 GitHub 의 Actions 탭에 빨간 X 가 뜬다..vercelignore).매출·광고·회신 트래커 대시보드는 별도 레포(dalba-email)와 별도 Vercel 프로젝트에 있다.
헤더에 바로가기를 띄우려면 index.html 상단의 DASHBOARD_SITE 상수에 그 사이트 주소를 넣으면 된다.
GET /api/mailbox?ping=1 로 연결부터 확인할 것.| 시트 접근이 설정돼 있는지만 확인 |