TLDR
- 어제 쓴 "A2A 시대의 Human In The Lead" 포스트 발행 직후 두 가지 새 실수가 터졌다. Ghost internal tag(
#blog-col) 를 일반 태그로 오해, 그리고 Ghost + Mailgun 이중 클릭 트래킹으로 뉴스레터 링크가 블로그로 리다이렉되지 않음. - 더 아픈 지점: 내가 AI에게 "재발 방지 룰"을 같이 짜게 했더니 AI가 반대 방향 가드(
#자동 strip)를 제안할 뻔했다. 운영 직감이 없으면 잘못된 자동화를 굳힐 뻔한 순간. - 결과적으로 스킬 4개 + 메모리 2개 + 스케줄링 스크립트 1개를 교정으로 다시 갱신. 고정 규칙을 만들 때는 AI 한 번, 사람 한 번의 왕복 검증이 필요하다.
- 솔로 파운더 체크리스트: 뉴스레터 발송 전에 (a) 테스트 preview, (b) Mailgun 클릭 트래킹 상태, (c) internal tag prefix 형태 세 가지를 직접 본다.
1. 첫 발행의 두 가지 사각지대
어제 "AI가 내 AI를 팩트체크한 날" 포스트를 retn.kr/ghost 에 예약 발행했다. 발행까지는 깔끔했는데, 두 가지가 어긋났다.
블로그 목록 페이지(retn.kr/blog)에 글이 안 떴다. Ghost 관리자에서 확인해 보니 태그는 전부 있는데 나열되지 않았다. 원인: retn.kr/blog 라우트는 #blog-col 이라는 internal tag 로 필터링 중인데, 내가 넣은 태그는 blog-col (해시 없음) 이었다. Ghost에서 #-prefix는 UI 장식이 아니라 "이 태그는 내부용" 이라는 load-bearing 마커다.
뉴스레터에서 본문 링크가 리다이렉 되지 않았다. 구독자들이 이메일에서 블로그 링크를 누르면 404 비슷한 화면으로 튕겼다. 원인: Ghost의 네이티브 클릭 트래킹 + Mailgun의 클릭 트래킹이 같은 링크를 두 번 wrap 하면서 최종 URL이 꼬였다.
2. 실수 1 — # 이 메타가 아니라 본질이었다
Ghost의 tag 시스템에는 두 종류가 있다.
| 형태 | Ghost 저장 | 용도 |
|---|---|---|
{"name": "blog-col"} |
visibility public, slug blog-col |
공개 태그 페이지에 노출 |
{"name": "#blog-col"} |
visibility internal, slug hash-blog-col |
템플릿/라우트 필터링 전용, 공개 페이지엔 미노출 |
retn.kr/blog 의 routes.yaml 은 internal #blog-col 로 컬렉션을 만든다. 내가 API 호출로 {"name": "blog-col"} 를 보내면 Ghost는 이걸 다른 태그(public) 로 저장해 버린다. Slug가 같아 보이지만(blog-col), visibility가 다르고 라우트가 못 잡는다.
더 흥미로운 건 slug 규칙. #blog-col 의 slug 는 Ghost가 hash-blog-col 로 치환한다. # 이 단순 제거되는 게 아니라 hash- 로 바뀌는 것. 이 규칙을 모르면 검증 코드도 틀린다.
3. 실수 2 — 클릭 트래킹은 한 군데서만
이메일 링크 체인이 꼬인 원리는 이렇게 생겼다.
해결: Mailgun 도메인 설정에서 Click Tracking을 OFF. Ghost의 네이티브 트래킹은 남겨 둬서 뉴스레터 내부 분석은 유지. Open tracking은 Mailgun 쪽을 켜도 충돌 없음.
4. 진짜 아픈 지점 — AI 가 제안한 재발 방지가 반대 방향이었다
위 두 문제를 발견한 직후, 나는 내 AI에게 "재발 방지 패턴을 같이 짜자" 고 했다. AI의 첫 제안 중 하나:
"ghost_schedule.py에 태그 sanitize 로직 추가: 입력된 태그 이름 앞에#이 있으면lstrip('#')으로 제거한 뒤 저장. 사용자가 실수로#을 붙여도 방어됨."
이 가드를 심었다면 앞으로 모든 포스트에서 #blog-col 이 blog-col 로 바뀌어 더 이상 /blog 에 안 뜨는 회귀가 됐을 것이다. AI는 "# 은 사용자가 잘못 붙인 것" 이라는 잘못된 전제 위에서 답을 만들었다.
운영에서 실제 화면을 본 사람만이 "아니, # 은 Ghost가 이걸 internal 로 취급하라는 신호" 라고 되돌려 줄 수 있다. 이게 Human In The Lead의 두 번째 층 — 규칙을 만들 때의 검증이다.
5. 실제로 무엇을 고쳤나
어제 세션의 결과를 덮어써서 다음 파일들이 최종 형태가 됐다.
~/.claude/skills/retention-style/SKILL.md— 이미지 전략 섹션을 "최대 3장" 상한으로 명문화. 매 포스트 3장을 강제하지 않음.~/.claude/skills/blog-publisher/SKILL.md— publish checklist에#blog-col(해시 포함, internal tag 마커),email_segment=all,?newsletter=<slug>세 항목 추가.~/.claude/skills/ghost-api-utils/SKILL.md— internal tag 저장 규칙 (#유지, slug는hash-...로 변환) 문서화. "lstrip('#') 하지 말 것" 명시.memory/reference_ghost_blog_publishing.md— Ghost retn.kr 퍼블리싱 체크리스트 전용 메모리./tmp/ghost_schedule.py—EXTRA_TAGS에#blog-col포함 + 응답의visibility=internal검증 주석.
6. 한 번 더 — 테스트 메일은 Post settings 에 없다
이 포스트 자체를 예약 전에 테스트 메일로 확인하려다 또 시간을 잡아먹혔다. Ghost 에서 "Send test email" 버튼은 Post settings (톱니바퀴) 안에 없다. 에디터 상단의 별도 "Preview" 액션에서만 나온다.
올바른 경로:
- 스케줄된 포스트라면 먼저 Unpublish 해서 draft 로 되돌린다 (scheduled 상태에서는 test email 버튼이 비활성화).
- 에디터 상단 오른쪽 "Preview" 를 누르면 모달이 뜨고 Web / Email / Desktop / Mobile / Member tier 탭이 나타난다.
- Email 탭 에서 상단 오른쪽 "Test" 버튼을 눌러 본인 주소로 발송.

메일이 실제로 도착하고 링크가 정상 리다이렉 되는지까지 확인한 뒤 스케줄을 복원한다.
![Gmail 받은편지함에 도착한 Ghost 테스트 뉴스레터 제목 앞에 [TEST] 접두어](https://retn.kr/content/images/2026/04/gmail-test-received.png)
이 순서 하나만 알고 있어도 다음 포스트부터 발행 전 체크에 10 초면 끝난다. 아는 사람한테는 당연하지만 "아는 사람" 이 되는 데 나는 또 한 번 삽질 했다.
7. 메타 교훈 — A2A 피드백 루프에는 두 번째 사람이 필요하다
A2A 시대의 작업 흐름이 이렇게 된다.
- 사람이 의도를 말한다.
- AI가 실행 + 규칙을 같이 짠다.
- 사람이 규칙을 검증한다. ← 이 단계가 빠지면 틀린 자동화가 굳는다.
어제 내가 놓친 건 1번이 아니라 3번이었다. "# 은 실수가 아니라 load-bearing marker" 라는 한 문장을, 내가 직접 화면을 보지 않았으면 AI는 영영 모르고 반대로 굳혔을 것이다.
솔로 파운더가 지켜야 할 최소 루프:
- 뉴스레터 발송 전에 preview email을 내 주소로 받아 실제 링크를 클릭해 본다.
- 스케줄링 스크립트가 발행하는 첫 한 건은 무조건 육안 확인.
- AI가 "자동 방어" 류 코드를 제안할 때, 그 제안이 뭘 안 하게 막는지 까지 읽는다. "무엇을 하는가" 보다 "무엇을 회수하는가" 가 더 중요하다.
📞 AX 도입 상담
이 글처럼 AI를 실제 운영 파이프라인에 태우다가 벌어지는 일들을 정리·설계해 드립니다.