GrepBoard/매뉴얼
84 편 ·

이메일 제목 A/B 후보를 변수 하나씩만 바꿔 받기

·고침 ·claude-sonnet-5-5 · Python 3.13 · 2026-10-20 실행·6 단계

제목 후보를 시키면 길이·어조·혜택이 한꺼번에 바뀌어 A/B 결과를 해석할 수 없습니다. 기준 제목과 변수를 정해 표로 받고, 단어 diff 와 글자 수로 검산합니다.

"이메일 제목 A/B 후보 5개 써 줘"라고 하면 후보마다 길이도, 어조도, 혜택 문구도 다릅니다. 열람률이 올라도 무엇 때문인지 알 수 없습니다. 변수 하나만 바꿔야 시험이 읽히니 후보를 받을 때부터 그렇게 주문합니다. 글자 수 검사만 따로 보려면 검색 광고 문구 글자 수 거르기 가 있습니다.

아래 예시는 가상의 온라인 강의 플랫폼이 보내는 가을 할인 메일입니다. 기준 제목(A)은 가을맞이 강의 전 과목 20% 할인 이고, 바꿀 변수는 숫자 유무·질문형·긴급성 세 가지입니다.

[1] 첫 프롬프트입니다. 기준 제목과 변수를 주고 출력을 표 하나로 못 박았습니다.

text
뉴스레터 제목 A/B 시험용 후보를 만듭니다. 기준 제목(A)과 바꿀 변수 목록이 <subject> 와 <variables> 안에 있습니다. 규칙:- 변수마다 후보를 정확히 2개 만듭니다.- 후보는 기준 제목에서 해당 변수 하나만 바꿉니다. 다른 단어·순서·문장부호는 기준 제목과 똑같이 둡니다.- 출력은 Markdown 표 하나입니다. 열은 정확히 이 순서입니다: 번호 | 바꾼 변수 | 제목- 「바꾼 변수」 칸에는 변수 목록의 이름만 그대로 적습니다.- 표 밖의 글은 쓰지 않습니다. <subject>{기준 제목}</subject> <variables>{변수 이름: 어떻게 바꾸는지 한 줄 — 변수마다 한 줄씩}</variables>

[2] 자리에 위 제목과 변수 세 줄(숫자 유무: 제목에 숫자를 넣거나 뺀다 · 질문형: 서술 문장을 질문 문장으로 바꾼다 · 긴급성: 기한을 나타내는 표현을 넣는다)을 넣어 ko.head.txt 로 저장하고 실행합니다. 프롬프트는 표준 입력으로 주고 --tools "" 로 내장 도구를, --strict-mcp-config 로 연결해 둔 MCP 도구를 끄고, --setting-sources "" 로 내 설정·지침 파일(CLAUDE.md 등)을 읽지 않게 합니다.

console
$ claude -p --model claude-sonnet-5-5 --max-turns 1 --tools "" --setting-sources "" --strict-mcp-config < ko.head.txt > out.ko.2.txt$ cat out.ko.2.txt| 번호 | 바꾼 변수 | 제목 ||---|---|---|| 1 | 숫자 유무: 제목에 숫자를 넣거나 뺀다 | 가을맞이 강의 전 과목 할인 || 2 | 숫자 유무: 제목에 숫자를 넣거나 뺀다 | 가을맞이 강의 전 과목 20% 할인 5일 || 3 | 질문형: 서술 문장을 질문 문장으로 바꾼다 | 가을맞이 강의 전 과목 20% 할인? || 4 | 질문형: 서술 문장을 질문 문장으로 바꾼다 | 가을맞이 강의 전 과목 20% 할인 받으셨나요? || 5 | 긴급성: 기한을 나타내는 표현을 넣는다 | 가을맞이 강의 전 과목 20% 할인 오늘 마감 || 6 | 긴급성: 기한을 나타내는 표현을 넣는다 | 가을맞이 강의 전 과목 20% 할인 이번 주말까지 |

제목 칸은 후보마다 한 군데씩만 다릅니다. 이상한 것은 「바꾼 변수」 칸입니다. 규칙에 「변수 목록의 이름만 그대로」 라고 썼는데, 두 번 모두 이름 뒤에 설명 줄까지 통째로 적었습니다. 이름 칸에 설명이 붙어 있으면 시트에서 변수별로 묶거나 열람률을 변수별로 모을 때 줄이 갈라집니다. 2번의 5일 도 기준 제목에 없던 수치를 모델이 정한 것입니다(다른 실행은 5종).

[3] 검사 스크립트입니다. 표의 제목을 기준 제목과 단어 단위로 비교해 바뀐 조각을 보이고, 글자 수·느낌표 개수·중복을 같이 셉니다. 차이는 제목만으로 계산하고, 「바꾼 변수」 칸은 앞 12자만 그대로 찍습니다.

check.py
import sys, difflibbase = sys.argv[1]rows = [[c.strip() for c in l.strip().strip("|").split("|")] for l in open(sys.argv[2], encoding="utf-8") if l.startswith("|")][2:]seen = {}for no, var, subj in rows:    a, b = base.split(), subj.split()    ops = [o for o in difflib.SequenceMatcher(None, a, b).get_opcodes() if o[0] != "equal"]    diff = " ".join(f"[{' '.join(a[i1:i2])}→{' '.join(b[j1:j2])}]" for _, i1, i2, j1, j2 in ops) or "(no change)"    note = "SAME" if subj == base else ("DUP of " + seen[subj] if subj in seen else "")    seen.setdefault(subj, no)    print(f"{no} {var[:12]:<12} {len(subj):>3} chars  !={subj.count('!')}  {len(ops)} edit(s) {diff} {note}")

[4] 돌려 봅니다.

console
$ python3 -I check.py "가을맞이 강의 전 과목 20% 할인" out.ko.2.txt1 숫자 유무: 제목에 숫  15 chars  !=0  1 edit(s) [20%→]2 숫자 유무: 제목에 숫  22 chars  !=0  1 edit(s) [→5일]3 질문형: 서술 문장을   20 chars  !=0  1 edit(s) [할인→할인?]4 질문형: 서술 문장을   26 chars  !=0  1 edit(s) [→받으셨나요?]5 긴급성: 기한을 나타내  25 chars  !=0  1 edit(s) [→오늘 마감]6 긴급성: 기한을 나타내  27 chars  !=0  1 edit(s) [→이번 주말까지]

여섯 행 모두 편집이 1곳이라 한꺼번에 바뀌는 문제는 사라졌습니다. 1행은 숫자를 빼고 2행은 새 숫자를 더하는 후보라 「숫자 유무」 시험이 성립합니다. 변수 칸이 숫자 유무: 제목에 숫 으로 잘려 찍히는 것이 위에서 말한 문제입니다. 스크립트는 판정하지 않고 차이만 드러내며, 변수에 맞는지는 사람이 봅니다.

[5] 같은 프롬프트를 영어 제목으로 돌렸을 때는 후보 두 개가 같은 줄이거나 기준 제목 그대로인 행이 두 번 모두 나왔습니다. 한국어 두 번에서는 없었지만, 그런 행은 시험을 낭비하므로 규칙 두 줄을 한국어에도 둡니다. 「서로 달라야 한다」와 「숫자 후보는 빼기 1개·새 숫자 항목 더하기 1개」입니다. [1] 프롬프트의 규칙 끝에 이렇게 덧붙입니다.

text
- 후보는 기준 제목과 달라야 하고, 서로도 달라야 합니다.- 숫자 유무 후보 2개: 기준 제목의 숫자를 빼는 것 1개, 기준 제목에 없는 새 숫자 항목(날짜·인원·편수 등)을 더하는 것 1개. 기존 숫자의 표기만 바꾸지 않습니다.

두 번 돌려 같은 스크립트로 봅니다. 첫 번째 실행입니다.

console
$ python3 -I check.py "가을맞이 강의 전 과목 20% 할인" fix.ko.1.txt1 숫자 유무         15 chars  !=0  1 edit(s) [20%→]2 숫자 유무         26 chars  !=0  1 edit(s) [→(120강)]3 질문형           26 chars  !=0  1 edit(s) [→받으셨나요?]4 질문형           28 chars  !=0  1 edit(s) [→알고 계신가요?]5 긴급성           24 chars  !=0  1 edit(s) [→오늘까지]6 긴급성           27 chars  !=0  1 edit(s) [→이번 주 마감]

이번에는 변수 칸이 이름만으로 돌아왔고 두 번째 실행도 같았습니다. 규칙을 넣은 것이 이유인지는 두 번으로 알 수 없습니다. 2행은 새 숫자를 더하는 후보이고 두 번째 실행에서는 12종 이었는데, 120강 도 12종 도 모델이 지어낸 숫자입니다. 실제 강의 수와 다르면 보낼 수 없고, 스크립트는 이것을 잡지 못합니다.

[6] 발송 전에는 세 가지를 봅니다. edit(s) 가 후보마다 1 인지, 바뀐 조각이 선언한 변수와 맞는지(사람이 읽기), 새로 들어온 숫자·날짜가 실제 사정과 맞는지입니다. 글자 수와 느낌표는 check.py 출력의 chars 와 != 열입니다. 이번 후보는 15~28자였고 느낌표는 없었습니다. 단어 diff 를 더 자세히 쓰는 법은 AI 수정 단어 diff 검수 에 있습니다.

어느 후보가 이길지는 모델에게 묻지 않습니다. 열람률은 발송 결과로만 나옵니다. 할인율·마감 시각처럼 사실이 걸린 문구는 모델이 채운 값을 쓰지 않고 담당자가 직접 정합니다.

#email-subject#ab-test#newsletter#prompt