“해외 바이어 영어 메일 하단에 한국어 발신자 정보·수신거부가 붙는다”는 제보에 대한 코드·beta DB 교차 분석 결과와 근본 해결안.
[테스트] ActUEarn Co. Ltd. fan range, 발신=수신=aeroway@hi.rinda.ai — 마케터가 본인에게 받아본 메일이며, 해외 바이어에게 실제 나가지 않았습니다.
동일한 “발신자/수신거부 footer”가 발송 경로마다 다르게 동작합니다. footer 언어의 SSOT(단일 기준)가 없기 때문입니다.
| 발송 경로 | footer 언어 결정 기준 | 실제 결과 | 상태 |
|---|---|---|---|
| 테스트 발송 (시퀀스 편집 → 내 메일로 받기) |
마케터 관리자 UI 언어i18n.language = ko |
영어 본문 + 한국어 compliance footer (= 제보된 화면) |
불일치 |
| 실발송 (Unipile/Gmail) 시퀀스 워커 |
전달 안 함 → 코드상 en 기본그런데 footer 자체가 안 붙음 |
compliance footer 없음 + 본문 자체 영어 unsubscribe 블록만 |
누락 |
| 본문 자체 unsubscribe 블록 | 시퀀스 콘텐츠 생성 언어 (본문과 동일) |
영어 (영어 캠페인 기준 정상) | 정상 |
테스트 발송 클릭 → 한국어 footer 렌더까지의 호출 체인.
complianceLocale: toSupportedLanguage(i18n.language) ← 한국 사용자는 "ko"testSendEmail(..., body.complianceLocale)testSendEmail이 sendEmail로 전달 — services/sequence-preview.service.ts:217sendEmail({ complianceLocale })locale: normalizeLocale(data.complianceLocale) → "ko"// 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가 실제로 붙지 않음(누락) })
| 확인 항목 | 결과 |
|---|---|
| 제보 메일 (019f16de…) | subject=[테스트] ActUEarn…, from=to=aeroway@hi.rinda.ai, provider=SES, footer=한국어 buildComplianceFooter |
| 실발송 12건 (Smart Home IoT 캠페인) | has_compliance_footer = false 전부 — 발신자 주소 footer 없음, 본문 자체 영어 unsubscribe 블록만 |
| 실발송 provider | Unipile(Gmail, @mail.gmail.com) — workspace legal_address는 설정돼 있음에도 footer 미주입 |
| workspaces 테이블 | language/locale 컬럼 자체가 없음 → 워크스페이스 발송 언어 SSOT 부재 |
| 항목 | 커밋(PR) | 작성자 | 날짜 |
|---|---|---|---|
| Compliance footer 자체 도입 (CAN-SPAM, 다국어 LABELS) | 69ba8eb43 (#7601) | Gyudong Kim | 2026-05-19 |
미리보기·테스트 발송에 complianceLocale 배선+ 프론트가 i18n.language 전송 ← 한국어 누수 시작점 | f7c59543b (#7775) | lsuminl | 2026-05-21 |
책임 추궁이 아니라, footer 언어 SSOT가 정의되지 않은 채 “테스트에도 footer를 붙이자”는 패치가 들어가며 UI 언어가 새어든 구조적 누락입니다.
제1원리: footer는 “수신자가 읽는 법적 고지”다 → 언어 = 발송 본문(=리드) 언어. UI 언어도, 무조건 영어도 아니다.
personalizationConfig.language, 워커의 languageGuardResult.expectedLanguage, lead.country. 새 인프라 없이 “이미 계산된 값”을 footer에 연결만 하면 됩니다.
| # | 변경 | 위치 | 효과 |
|---|---|---|---|
| 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 | 중복 노출 제거, 일관성 |
// email.service.ts:702 — caller 신뢰 대신 본문 언어로 자동 수렴 locale: normalizeLocale( data.complianceLocale // 명시 전달(서버가 시퀀스 언어로 세팅) ?? data.outboundLanguageGuard?.expectedLanguage // 워커가 이미 계산 ?? detectLanguageFromBody(data.bodyText) // 최후 자동 감지 fallback ),
| 발송 유형 | 본문 언어 | 기대 footer | 현재 | 개선 후 |
|---|---|---|---|---|
| 테스트 발송 | 영어 | 영어 | 한국어 | 영어 |
| 테스트 발송 | 한국어 | 한국어 | 한국어 | 한국어 |
| 실발송 SES | 영어 | 영어 | 누락 | 영어 |
| 실발송 Unipile/Gmail | 영어 | 영어 | 누락 | 영어 |
| 실발송 | 한국어 | 한국어 | 누락 | 한국어 |
| 실발송 | 일본어/인니어 등 | 해당 언어 | 미검증 | 해당 언어 |
buildComplianceFooter locale 분기 + detectLanguageFromBody 경계값.