Root Cause Analysis · 앱린다(RINDA)

린다 이메일 발신자/수신거부 Footer가
한국어로 붙은 원인 — 분석 & 모든 시나리오 통과 개선안

“해외 바이어 영어 메일 하단에 한국어 발신자 정보·수신거부가 붙는다”는 제보에 대한 코드·beta DB 교차 분석 결과와 근본 해결안.

작성: 2026-06-30 · 대상: aeroway / 스틸브로 워크스페이스 · 분석 범위: elysia-server + admin + beta DB

03줄 결론 (TL;DR)

① 제보된 한국어 footer는 “테스트 발송(미리보기)” 한정입니다. 실제 subject는 [테스트] ActUEarn Co. Ltd. fan range, 발신=수신=aeroway@hi.rinda.ai — 마케터가 본인에게 받아본 메일이며, 해외 바이어에게 실제 나가지 않았습니다.
② 그러나 footer 언어 로직 자체가 깨져 있습니다. footer 언어가 수신자(본문) 언어가 아니라 마케터의 관리자 UI 언어(한국어)를 따라갑니다. 영어 본문 + 한국어 footer 불일치의 직접 원인.
③ 더 큰 문제 — 실제 발송에는 compliance footer가 아예 누락됩니다. 실발송(Unipile/Gmail) 메일에는 발신자 등록주소 footer가 붙지 않고 본문 자체의 영어 unsubscribe 블록만 존재 → CAN-SPAM 물리주소 고지 누락 리스크.

1무슨 일이 일어났나 — 3중 불일치

동일한 “발신자/수신거부 footer”가 발송 경로마다 다르게 동작합니다. footer 언어의 SSOT(단일 기준)가 없기 때문입니다.

발송 경로footer 언어 결정 기준실제 결과상태
테스트 발송
(시퀀스 편집 → 내 메일로 받기)
마케터 관리자 UI 언어
i18n.language = ko
영어 본문 + 한국어 compliance footer
(= 제보된 화면)
불일치
실발송 (Unipile/Gmail)
시퀀스 워커
전달 안 함 → 코드상 en 기본
그런데 footer 자체가 안 붙음
compliance footer 없음 +
본문 자체 영어 unsubscribe 블록만
누락
본문 자체 unsubscribe 블록 시퀀스 콘텐츠 생성 언어
(본문과 동일)
영어 (영어 캠페인 기준 정상) 정상
핵심: footer는 “수신자가 읽는 법적 고지”인데, 언어를 정하는 기준이 경로마다 (UI 언어 / 무조건 영어 / 콘텐츠 언어)로 제각각입니다. 이것이 모든 증상의 공통 뿌리입니다.

2한국어 footer가 만들어진 정확한 코드 경로

테스트 발송 클릭 → 한국어 footer 렌더까지의 호출 체인.

1
프론트가 UI 언어를 footer 언어로 전송admin/src/pages/sequences/components/TestSendDialog.tsx:157
complianceLocale: toSupportedLanguage(i18n.language) ← 한국 사용자는 "ko"
2
라우트가 그대로 서비스로 전달 — routes/sequences.routes.ts:967, 984
testSendEmail(..., body.complianceLocale)
3
testSendEmail이 sendEmail로 전달 — services/sequence-preview.service.ts:217
sendEmail({ complianceLocale })
4
locale 확정 — services/email.service.ts:702
locale: normalizeLocale(data.complianceLocale)"ko"
5
한국어 라벨로 footer 렌더 — services/email-footer.service.ts:144, LABELS.ko:63
“발신자”, “이 이메일은 B2B 비즈니스 제안…”, “수신거부 | 내 데이터 관리”
6
본문→서명→footer 순으로 주입 — services/email.service.ts:856

대비: 실발송 워커는 정반대로 깨짐

// workers/.../sequence-email-worker/steps/send-email.ts:770
sendResult = await emailService.sendEmail({
  bodyHtml: content.emailBodyHtml,
  workspaceId,
  // complianceLocale 전달 자체가 없음 → normalizeLocale(undefined) = "en"
  // 게다가 beta DB 확인 결과 compliance footer가 실제로 붙지 않음(누락)
})

3beta DB 실측 근거

확인 항목결과
제보 메일 (019f16de…)subject=[테스트] ActUEarn…, from=to=aeroway@hi.rinda.ai, provider=SES, footer=한국어 buildComplianceFooter
실발송 12건 (Smart Home IoT 캠페인)has_compliance_footer = false 전부 — 발신자 주소 footer 없음, 본문 자체 영어 unsubscribe 블록만
실발송 providerUnipile(Gmail, @mail.gmail.com) — workspace legal_address는 설정돼 있음에도 footer 미주입
workspaces 테이블language/locale 컬럼 자체가 없음 → 워크스페이스 발송 언어 SSOT 부재

4누가 도입했나

항목커밋(PR)작성자날짜
Compliance footer 자체 도입 (CAN-SPAM, 다국어 LABELS)69ba8eb43 (#7601)Gyudong Kim2026-05-19
미리보기·테스트 발송에 complianceLocale 배선
+ 프론트가 i18n.language 전송 ← 한국어 누수 시작점
f7c59543b (#7775)lsuminl2026-05-21

책임 추궁이 아니라, footer 언어 SSOT가 정의되지 않은 채 “테스트에도 footer를 붙이자”는 패치가 들어가며 UI 언어가 새어든 구조적 누락입니다.

5개선안 — 모든 시나리오를 통과하는 단일 기준

제1원리: footer는 “수신자가 읽는 법적 고지”다 → 언어 = 발송 본문(=리드) 언어. UI 언어도, 무조건 영어도 아니다.

핵심 설계: footer 언어의 SSOT를 “발송 본문 언어” 한 곳으로 고정한다. 이미 시스템이 본문 언어를 알고 있습니다 — 시퀀스 personalizationConfig.language, 워커의 languageGuardResult.expectedLanguage, lead.country. 새 인프라 없이 “이미 계산된 값”을 footer에 연결만 하면 됩니다.

4가지 변경 (우선순위순)

#변경위치효과
P0 실발송 워커가 본문 언어를 footer로 전달
complianceLocale: languageGuardResult.expectedLanguage
send-email.ts:770 실발송 footer 언어 = 본문 언어 보장
P0 실발송 compliance footer 누락 자체 수정 — Unipile/Gmail 경로에서 footer가 실제 append되는지 보장(현 DB상 미주입) email.service.ts sendEmailViaUnipile 경로 / workspaceId 분기 점검 CAN-SPAM 발신자 주소 고지 복구
P1 테스트 발송이 UI 언어를 보내지 않게 — 클라 값 제거, 서버가 stepId로 시퀀스 언어 조회해 결정 TestSendDialog.tsx:157 삭제 +
sequence-preview.service.ts 서버 결정
“테스트에서 본 모습 = 바이어가 받는 모습” 일치
P1 footer 언어 자동 결정 fallback — caller 미전달 시 sendEmail이 본문 텍스트 언어를 감지(이미 outboundLanguageGuard가 감지 중)해 locale 결정 email.service.ts:702 향후 어떤 caller가 추가돼도 drift 불가
P2 footer 이중화 정리 — 본문 자체 unsubscribe 블록 + compliance footer 중복. 역할을 명확히 분리하거나 하나로 통합 시퀀스 콘텐츠 생성 + email-footer.service 중복 노출 제거, 일관성

권장 구현 — locale 결정 단일화

// email.service.ts:702 — caller 신뢰 대신 본문 언어로 자동 수렴
locale: normalizeLocale(
  data.complianceLocale                       // 명시 전달(서버가 시퀀스 언어로 세팅)
    ?? data.outboundLanguageGuard?.expectedLanguage  // 워커가 이미 계산
    ?? detectLanguageFromBody(data.bodyText)   // 최후 자동 감지 fallback
),

6시나리오 매트릭스 — 개선 후 전수 통과

발송 유형본문 언어기대 footer현재개선 후
테스트 발송영어영어한국어영어
테스트 발송한국어한국어한국어한국어
실발송 SES영어영어누락영어
실발송 Unipile/Gmail영어영어누락영어
실발송한국어한국어누락한국어
실발송일본어/인니어 등해당 언어미검증해당 언어

검증 방법 (회귀 방지)

7당장 할 일 (실행 체크리스트)

즉시 (안심 메시지)

  • 제보 건은 테스트 미리보기였음 — 실제 바이어에게 한국어 footer가 간 게 아님을 공유
  • 단, 실발송 footer 누락은 별개 이슈로 트래킹

이번 주 (P0/P1 수정)

  • 워커 footer 언어 전달 + 실발송 footer 누락 수정
  • TestSendDialog UI 언어 전달 제거 → 서버 결정
  • 매트릭스 E2E 추가 후 alpha 머지
의사결정 필요 실발송 footer “누락”이 의도된 것인지(본문 자체 unsubscribe로 대체) vs 버그인지 먼저 확정해야 P0 방향이 갈립니다. CAN-SPAM 준수를 위해서는 발신자 등록 물리주소가 어떤 형태로든 반드시 노출돼야 합니다.