Files
arasystem/docs/design/webmail
ttipo 19dbb2babb
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 15m17s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 14m10s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m13s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m54s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 14m55s
CI / Frontend (lint · type-check · test · build) (push) Successful in 13m43s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 2m2s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m39s
docs: 메일 16단계(개인 설정)를 계약 · 디자인 README · 스킬 · 로드맵에 적는다
계약 2절(서명 · 부재중 — 원본 판 3 과 되돌리기 계획) · 3절(가져오기 · 내보내기) · 5절(휴대폰 바꾸기),
디자인 README 16단계 절과 구현하며 정한 보완, nestjs · vue3 스킬 표, 로드맵의 추후 후보(16단계에서 미룬 것).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 11:44:43 +09:00
..

웹메일 디자인 원본 (1~6단계)

확정된 디자인 캔버스의 원본이다. 화면 구현의 기준으로 쓴다.

  • 캔버스: https://claude.ai/artifact/WJvVsKvXepxn3p75CGJAr2 (확정본: 2026-10-28, 버전 31)
  • 화면: Login(로그인) · Main(분할 보기: 메일함·목록·본문) · MainList(목록 보기: 게시판형 표) · MainListRead(목록 보기에서 연 메일) · Compose(메일 쓰기 — 서식 편집기) · ComposeEditor(메일 쓰기 · 편집기 상태: 포커스·툴바·메뉴·팝오버·인용·이미지 첨부) · States(오류·빈 상태·피드백 모음) · 2단계: Settings(설정 · 메일 서명) · ComposeAssist(메일 쓰기 · 임시 저장 · 받는 사람 자동 완성 · 서명) — 아래 "2단계" · 3단계: Search(메일 검색 · 분할 보기) · BulkSelect(여러 통 선택 · 목록 보기) · ListStates(새 메일 · 검색 · 여러 통 처리 상태) — 아래 "3단계" · 4단계: SettingsAccount(설정 · 비밀번호) · SettingsVacation(설정 · 부재중 자동 응답) · MailboxManage(메일함 관리 · 휴지통 비우기) · MailActions4(스팸 · 원문 · 오래 걸리는 처리) — 아래 "4단계" · 5단계: SettingsNotify(설정 · 알림) · MailRealtime(실시간 새 메일 · 바탕화면 알림 상태) — 아래 "5단계" · 6단계: SettingsFilter(설정 · 필터 — 고치기 창) · MailFilterStates(메일 필터 상태 · 메일에서 바로 만들기) — 아래 "6단계"
  • 파일 형식은 캔버스 전용(<x-dc> 마크업 + DCLogic 클래스)이다. 그대로 쓰지 말고 구조·간격·색·문구의 기준으로만 본다. Compose 의 툴바·메뉴는 눌러 볼 수 있는 시안이다 — 버튼 상태만 바뀌고 본문 글자에 서식이 입혀지지는 않는다.

캔버스 안의 자산 주소

캔버스는 자산을 /_blob/<id> 로 참조한다. 저장소에서는 아래 파일에 대응한다.

캔버스 주소 저장소 파일 용도
/_blob/16067b9aa5137abf808e3dbef30c42e0 Pretendard Variable (npm pretendard 패키지로 자체 호스팅) 전체 글꼴
/_blob/2b9ef4349333debc969432c859eaf1bf frontend/src/assets/brand/logo-ara-white.png 왼쪽 앱 레일 로고(가로 48px)
/_blob/3a2f4115a8f90207faa7d3a51cd9aec2 frontend/src/assets/brand/logo-arasystem-white.png 로그인 브랜드 패널 로고(가로 240px)

로고 원본은 docs/brand/logo_arasystem_white.png 다. 흰 로고라 어두운 브랜드 바탕(#0B3640) 위에만 둔다.

탭 아이콘(favicon)과 홈 화면 아이콘은 바탕화면 알림 아이콘(5단계 notification-icon.png)과 같은 모양이다 — #0B3640 둥근 사각 가운데 흰 ARA 마크. 원본 로고의 위 덩이(ARA)를 크기마다 따로 줄였다(2026-11-04).

  • frontend/src/assets/brand/favicon-16.png · favicon-32.png: 마크 폭 86% · 모서리 16%. 작은 크기에서 가는 획이 사라지지 않게 획을 조금 굵게 했다.
  • frontend/public/favicon.ico: 16 · 32 · 48 세 장(48 은 굵게 하지 않았다) — /favicon.ico 를 직접 묻는 클라이언트용.
  • frontend/src/assets/brand/apple-touch-icon.png: 180 불투명 정사각 · 마크 폭 77% — iOS 가 모서리를 깎는다.
  • 모바일 브라우저 주소 표시줄 색(theme-color)은 #0B3640.

디자인 원칙 (사용자 확정)

  • 안 읽음 점·개수 알약·코드 태그 같은 뱃지를 쓰지 않는다 — 굵은 글자와 평범한 숫자로 구분한다.
  • 왼쪽 전역 앱 레일(메일·캘린더·파일·주소록·설정)에 모듈을 계속 붙인다. 지금은 메일 · 주소록 · 설정이 동작하고 캘린더 · 파일은 "준비 중"이다. ERP 는 2026-11-04 사용자 요청으로 레일과 로그인 소개 글에서 뺐다 — 그 전에 그린 캔버스(*.dc.html)에는 남아 있다.
  • 정보 밀도가 높은 데스크톱 화면 — 기본 13px. 목록에 본문 미리보기를 넣지 않는다.
    • 분할 보기 행: 2줄(보낸 사람·시간 / 제목+첨부 클립), 약 50px
    • 목록 보기 행: 1줄 표, 38px. 칸 = 별 32 · 보낸 사람 168 · 제목(나머지) · 날짜 84 · 크기 64 (px, 칸 사이 12). 폭 1100px 미만에서 크기 칸 숨김, 600px 미만에서 보낸 사람·날짜 축소, 더 좁으면 가로 스크롤. 캔버스는 파서 제약 때문에 grid 로 그렸지만 구현은 실제 <table> 로 한다.
  • 클릭 영역은 44px 이 기본이다. 아래만 밀도를 위한 의도된 예외다(사용자 확정).
    • 목록 표의 별 버튼(폭 32px) · "분할 | 목록" 전환(바깥 36px)
    • 메일 쓰기 편집기(2026-10-27): 툴바 아이콘 버튼 40×44(새로고침·이전/다음 버튼과 같은 크기) · 메뉴 항목 높이 40(메일함 항목과 같다) · 색 견본 칸 40×40 · 표 고르기 격자 칸 22(간격 3, 키보드는 화살표)
  • 보기 방식: 목록 머리의 "분할 | 목록" 전환. 선택은 사용자별로 기억한다.
  • 콘텐츠 열(읽기·쓰기 공통): 최대 폭 800px, 좌우 여백 32px, 위 20px·아래 36px, 본문 글줄 최대 680px, 영역 안 왼쪽 정렬. 분할 보기 읽기·목록 보기 읽기·쓰기 세 곳이 같은 값을 쓰고, 툴바 첫 아이콘도 같은 32px 선에서 시작한다.
  • 브랜드 컬러 "바다 청록": 바탕 #0B3640, 강조 #0E7C86. 파생 색은 각 화면 원본의 인라인 스타일을 따른다.

화면 문구 — 사용자에게 말하듯 (2026-11-05 · 11-07 사용자 요청)

화면 · 알림 · 화면 낭독기(aria-label · title) · 칸 안내(placeholder)의 글은 설계 문서가 아니라 쓰는 사람에게 하는 말이다. 원본은 화면 상수(frontend/src/shared/constants/)이고 이 문서는 옮겨 적는다.

  1. 무엇이 되는지 · 무엇을 하면 되는지를 쓴다. 만든 쪽의 사정(메일 서버 · 엔진 · 계정 · 판정 · 적용 · 동작 · 규칙 · 대상)은 빼거나 쉬운 말로 바꾼다.
    • ✗ 메일 서버에 계정이 있는 직원입니다 — 이름과 주소만 보입니다. → ○ 회사 메일을 쓰는 직원입니다. 사내 목록에는 이름과 메일 주소만 있습니다.
  2. 줄표(—)로 잇지 않는다. 두 문장으로 나누거나 "~해서 · ~니 · ~지만"으로 잇는다. 짧은 상태 표시도 줄표 대신 한 구절로(✗ 실패 — 비밀번호 틀림 → ○ 비밀번호가 틀려 실패). 결과 칸이 따로 있는 표라면 까닭만 써도 된다.
  3. 「」 를 쓰지 않는다. 화면의 단추 · 메뉴 · 칸 이름은 [대괄호](직원 공지와 같다 — [설정] → [2단계 인증]), 사용자가 넣은 값(이름 · 그룹 이름 · 파일 이름)은 지금처럼 ‘ ’.
  4. 규칙을 정의하듯 쓰지 않는다.
    • ✗ 이 주소에서 오는 메일은 스팸메일함으로 갑니다. 부재중 자동 응답도 보내지 않습니다. 「@도메인」은 그 도메인에서 오는 메일 모두입니다.
    • ○ 차단한 주소에서 온 메일은 스팸메일함으로 옮겨지고, 부재중 자동 응답도 보내지 않습니다. @spam.example 처럼 @로 시작해 넣으면 그 도메인에서 오는 메일을 모두 막습니다.
  5. 가운뎃점(·) 나열은 쉼표로, 괄호 속 보충은 문장으로 풀거나 뺀다(꼭 필요한 예시만 괄호). 입력 칸 안내(placeholder)는 짧게.
    • ✗ 이름 · 주소 · 회사 · 전화번호 — 초성도 됩니다 (ㅂㅈㅎ) → ○ 이름, 주소, 회사, 전화번호, 초성(ㅂㅈㅎ)으로 찾기
  6. 말끝은 지금처럼 존댓말 "~합니다 · ~해 주세요"(해요체로 바꾸지 않는다). 딱딱한 명령("~하세요" · "~하지 마세요")은 "~해 주세요 · ~하지 않으셔도 됩니다"처럼 부드럽게. 단추 · 칸 이름 · 짧은 상태 꼬리표는 명사형 그대로.
  7. 위험 · 보안 문구는 겁주지 않되 분명하게 — 무엇이 막혔고 무엇을 하면 되는지를 함께.
  8. 뜻은 그대로 — 한도 · 날짜 · 개수 · 조건 같은 정보를 빼지 않는다. 문장만 바꾼다. 영어 문구(공개 내려받기 화면)는 자연스러운 영어로만 다듬는다.
  9. 화면이 하는 일을 설명하지 않는다(2026-11-07) — "여기에 표시됩니다 · 보입니다 · 나타납니다 · 펼쳐집니다 · 이어집니다"처럼 화면을 주어로 한 설명 대신, 내 메일이 어떻게 되는지 · 무엇을 하면 되는지를 쓴다.
    • ✗ 삭제한 메일이 여기에 표시됩니다. → ○ 지운 메일은 여기에 30일 동안 남아 있다가 저절로 지워집니다.
    • ✗ 목록에서 메일을 고르면 내용이 여기에 표시됩니다. → ○ 읽을 메일을 목록에서 골라 주세요.
  10. 번역투 · 설계 말을 쓰지 않는다(2026-11-07) — 목적어 없는 "맞는", "해당 · 대상 · 항목 · 처리 · 적용 · 분류 · 기준 · ~에 대한 · ~를 통해 · ~하는 것이 좋습니다 · 지원하지 않습니다", 명사 나열 대신 평소에 쓰는 말로.
    • ✗ 아직 맞는 메일을 찾지 못했습니다 → ○ 아직 찾은 메일이 없습니다
    • ✗ 일시적인 시스템 오류가 발생했습니다. → ○ 문제가 생겨 하던 일을 마치지 못했습니다.

적용하며 정한 것(2026-11-05):

  • 한 줄에 여러 정보를 늘어놓는 짧은 표시의 가운뎃점은 나열이 아니라 칸 구분이라 그대로 둔다 — 파일 줄 "1.2 GB · 3회 받음 · 11월 29일까지", "‘회의’ · 6통", 첨부 안내 "파일당 최대 20MB · 합계 25MB · 최대 20개", 탭 제목 "메일 · ARA SYSTEM". 메뉴 · 화면 이름("보내기 · 읽기")도 이름이라 그대로다.
  • 이 문서의 설명 글은 지금처럼 「」 로 화면 요소를 가리킨다 — 위 규칙은 화면에 보이는 글에만 쓴다.
  • 디자인 보드(*.dc.html)의 문구는 11-05 전의 옛 글이다 — 문구는 이 문서와 화면 상수가 기준이다.
  • 서버가 만드는 글(에러 코드 사전 · 받은편지함 알림 메일 · 본문의 대용량 첨부 덩이)도 같은 규칙으로 맞췄다(2026-11-05) — 이 문서에 옮겨 적은 그 글(13단계 알림 메일 등)도 새 글이다. 알림 메일의 메뉴 경로도 화면 글처럼 "웹메일의 [설정] → [앱 비밀번호]" 꼴로 적는다.

적용하며 정한 것(2026-11-07 — 규칙 9 · 10, 사용자가 "자연스러운 문구가 아니라 시스템, 설계 문구 형식"이라고 짚은 글):

  • 웹 75곳 · 오류 사전 18곳 · 앱 18곳(+ 앱 검색 계획 6곳)을 고쳤다. 정할 것 여섯은 모두 추천안이다 — 읽기 칸이 비었을 때 제목 "고른 메일이 없습니다" + 안내 "읽을 메일을 목록에서 골라 주세요."(주소록의 빈 칸도 같은 꼴), REQ_001 "너무 여러 번 시도했거나 같은 일이 이미 진행 중입니다."(추천안 "요청이 한꺼번에 몰렸습니다"는 가장 자주 보는 로그인 · 코드 실패 한도와 맞지 않아 바꿨다 — MAIL_003 은 휴지통도 비우라고, FILE_001 은 전달 원본 첨부에도 맞게 함께 다듬었다), 필터 창의 칸 이름 [받은편지함에 이미 있는 메일도 정리하기], 본문을 다 보이지 못할 때 할 일(원문 내려받기)까지, 로그인 소개 글 "…같은 계정으로 쓸 수 있게 됩니다.", 이 규칙 9 · 10.
  • 그대로 둔 것: 내 메일 · 계정에 생기는 일을 말하는 "지워집니다 · 저장됩니다 · 로그아웃됩니다", "조건에 맞는 메일"처럼 평소에 쓰는 말, 다른 프로그램에서도 쓰는 단추 이름("실행 취소" · "원문 내려받기 (.eml)"), 받은편지함 알림 메일 · 본문의 대용량 첨부 덩이(이미 사람 말에 가깝다), 운영 도구의 터미널 글(운영자용).
  • 디자인 보드(*.dc.html)의 글은 이번에도 고치지 않는다(위 11-05 줄과 같다 — 기준은 화면 상수와 이 문서).
  • 조사는 받침에 맞게 고른다(frontend/src/shared/utils/korean.ts — 짝: 앱 core/utils/korean_text.dart). 한글 음절에 더해 호환 자모(초성 검색 — 자음은 받침 있음, ㄹ 은 "로")와 숫자(영 · 일 · 이 … 로 읽는다 — "2026"은 "으로")도 고르고, 라틴 글자 · 기호로 끝나면 "이(가)" · "(으)로" 꼴이다.
  • 본문을 다 보이지 못한다는 안내는 읽기 화면만 할 일(원문 내려받기)까지 적고, 같은 대화 칸의 펼친 줄은 앞 문장만 둔다(MAIL_BODY_NOTICES + MAIL_BODY_NOTICE_ACTIONS) — 그 칸의 ⋯ 는 위에 연 메일의 것이라 다른 메일을 받게 된다. 원문은 [이 메일만 열기]로 연 읽기 화면에서 받는다.

메일 쓰기 (2026-10-27 확정 — Compose · ComposeEditor)

위에서부터 머리(제목·닫기) → 받는 사람(→ 참조·숨은참조) → 제목 → 본문 툴바 → 본문 → 첨부 → 보내기 줄이다. 모두 콘텐츠 열 안에 있다. 첨부 안내문은 "파일당 최대 20MB · 합계 25MB · 최대 20개 · 문서, 이미지, 압축 파일" 이다(용량은 서버 값, 개수는 계약 상수).

글자 입력칸 포커스 — 한 가지 규칙

항목 값
대상 받는 사람 · 참조 · 숨은참조 · 제목 · 본문 편집기
위치 그 칸의 아래 hairline 자리. 본문은 편집 영역 아래(첨부 영역 위)
두께 · 색 2px --color-brand(#0E7C86). 1px 선의 색을 바꾸고 안쪽 그림자 1px 를 더한다 — 높이가 변하지 않는다
모서리 없음(곧은 선)
범위 행 전체 폭 — 원래 hairline 과 같은 길이(콘텐츠 열, 최대 800px). 이름표 아래까지 이어진다
언제 글자 입력 요소(input·편집 영역)에 포커스가 있을 때. 마우스로 눌러도 보인다(:focus)
대비 흰 바탕 4.9:1 · 원래 선 #EEF0F3 4.3:1 · 오류 띠 #FEF3F2 4.5:1 (모두 3:1 이상)
  • 버튼 고리와의 관계: 같은 색·같은 2px 이고 모양만 다르다. 누르는 것(버튼·링크)은 전역 :focus-visible 고리(바깥 2px, 버튼 모서리를 따른다), 쓰는 곳은 선이다. 행 안의 버튼(받는 사람 토큰 ✕ · "참조 · 숨은참조")에 포커스가 가면 고리만 보이고 행의 선은 회색 그대로다 — 한 번에 하나만 보인다.

  • 테두리가 있는 입력칸(링크 팝오버·로그인)은 상자가 있으므로 버튼처럼 고리를 쓴다.

  • 구현(tokens.css 변수):

    .compose-field:has(:is(input, .ProseMirror):focus) {
      border-bottom-color: var(--color-brand);
      box-shadow: inset 0 calc(var(--border-width) * -1) 0 var(--color-brand);
    }
    .compose-field :is(input, .ProseMirror):focus-visible { outline: none; }
    

    .compose-field 는 받는 사람·참조·숨은참조 행, 제목 행, 본문 편집 영역에 붙인다. 편집 영역이 아래 hairline 을 가지므로 첨부 영역의 위 선은 없앤다.

본문 서식 편집기 (TipTap)

  • 툴바: 한 줄 · 높이 52(위아래 4) · 왼쪽 여백 20 + 버튼 안 12 → 첫 아이콘이 32px 선. 버튼 사이 2, 묶음 사이 세로선 1×20 #E6E8EC(좌우 4).

    • 아이콘 버튼 40×44 · 모서리 8 · 아이콘 16(선 1.75) · #344054. 글꼴 108×44 · 글자 크기 56×44(글자 + ▾).
    • 상태: 올림 #F2F4F7 · 켜짐(aria-pressed) #E3F2F3 + 안쪽 1px #0E7C86 + 아이콘 #0A5F67(옅은 바탕만으로는 3:1 이 안 돼 선을 둔다) · 비활성 50% · 포커스는 전역 고리 · 메뉴 열림(aria-expanded)은 올림과 같은 바탕.
    • role="toolbar" — Tab 한 번에 들어가 화살표로 옮긴다. 본문에서 Alt+F10 → 툴바, Esc → 본문.
  • 툴바 기능(800px): 실행 취소 · 다시 실행 | 글꼴 ▾ · 글자 크기 ▾ | 굵게 · 기울임 · 밑줄 | 글자 색 · 배경 색 | 글머리 목록 · 번호 목록 | 링크 | 더보기

  • 더보기(늘): 취소선 | 왼쪽 · 가운데 · 오른쪽 정렬 | 내어쓰기 · 들여쓰기 | 인용 · 표 넣기 › · 가로줄 | 서식 지우기

    • 항목 높이 40 · 아이콘 16 · 이름 · 단축키(12px #667085) · 켜진 것은 오른쪽 ✓. 값을 고르는 것(글꼴·크기·색·표)은 › 로 한 단계 더 연다.
    • 단축키: 굵게 Ctrl+B · 기울임 Ctrl+I · 밑줄 Ctrl+U · 취소선 Ctrl+Shift+S · 글머리 Ctrl+Shift+8 · 번호 Ctrl+Shift+7 · 정렬 Ctrl+Shift+L/E/R · 내어쓰기 Ctrl+[ · 들여쓰기 Ctrl+] · 인용 Ctrl+Shift+B · 링크 Ctrl+K · 서식 지우기 Ctrl+\ · 실행 취소 Ctrl+Z · 다시 실행 Ctrl+Y
    • 맥은 Ctrl 대신 ⌘ 이고 표기도 기호로 보인다(⌘B · ⇧⌘S). 다시 실행은 맥 관례대로 ⇧⌘Z 로 적는다(⌘Y 도 된다). 내어쓰기 · 들여쓰기 · 서식 지우기는 할 일이 없어도 키를 삼킨다 — 맥의 ⌘[ 가 브라우저 "뒤로"가 되어 쓰던 메일을 잃지 않게 (2026-10-27 구현하며 정함).
  • 좁은 폭: 툴바는 줄을 바꾸지 않는다. 묶음째 아래 순서로 더보기에 옮기고, 숨은 묶음은 더보기 맨 위에 툴바 순서대로 들어간다. 더보기 단추에 숫자를 달지 않는다.

    1. 실행 취소 · 다시 실행 — 쓰기 칸 734px 미만 (Ctrl+Z · Ctrl+Y 는 그대로)
    2. 글꼴 · 글자 크기 — 639px 미만
    3. 글자 색 · 배경 색 — 460px 미만
    4. 링크 — 365px 미만 (주소를 붙여 넣으면 저절로 링크가 된다)
    5. 글머리 · 번호 목록 — 312px 미만

    굵게 · 기울임 · 밑줄과 더보기는 늘 남는다. 폭은 지금 크기로 셈한 참고값이다 — 구현은 툴바의 실제 폭을 재어(ResizeObserver) 이 순서로 넘긴다. Compose 보드의 좁은 폭 메뉴는 색 · 목록을 취소선 뒤에 그렸지만, 이 문서의 순서(숨은 묶음 전부를 맨 위에)가 기준이다 — 구현도 이 순서다. 휴대폰에서는 더보기 메뉴가 화면 높이에 맞춰 안에서 스크롤된다.

  • 글꼴: 맑은 고딕(기본) · 굴림 · 돋움 · 바탕 | Arial · Verdana · Georgia · Times New Roman · Courier New. 목록의 이름은 그 글꼴로 보인다.

    • 본문 기본 글꼴은 맑은 고딕 스택 'Malgun Gothic', '맑은 고딕', 'Apple SD Gothic Neo', sans-serif 다 — 편집기 안 모양을 받는 사람이 보는 모양과 같게 한다. 화면 UI 는 Pretendard 그대로다. 맥에서는 Apple SD 산돌고딕 Neo 로 보인다.
    • "맑은 고딕(기본)"을 고르면 글꼴 지정을 지운다.
  • 글자 크기(px): 12 · 13 · 14(기본) · 16 · 18 · 20 · 24 · 32. 제목 스타일(제목 1·2·3)은 두지 않는다. 고른 글자의 크기가 섞이면 "–".

  • 색 팔레트(8색씩 · 견본 칸 40×40, 보이는 색 26 · 고른 색에 ✓ · 고른 색 이름을 글자로도 보인다):

    • 글자 색: 기본 #1F2733 · 회색 #667085 · 빨강 #B42318 · 주황 #B54708 · 초록 #067647 · 청록 #0E7C86 · 파랑 #175CD3 · 보라 #6941C6 — 모두 흰 바탕 4.5:1 이상. "기본"은 글자 색을 지운다.
    • 배경 색: 없음 · 노랑 #FFF1A8 · 주황 #FDE4CC · 분홍 #FBDCE3 · 초록 #D9F2DF · 하늘 #D6E8FB · 보라 #E9E0FA · 회색 #E9EBEF — 본문 글자(#1F2733)가 10:1 이상 남는다. "없음"은 배경 색을 지운다.
  • 붙여 넣은 글 · 원문 인용의 서식: 보낼 수 있는 형식(계약 2절 "서식 본문")만 받고 나머지는 버린다 — 색 이름(red) · Calibri 같은 서식은 기본 모양으로 보인다. 편집기에 보이는 모양이 곧 받는 사람이 보는 모양이다 (2026-10-27 구현하며 정함). 보이지 않을 만큼 작은 글자(8px 미만)와 거의 투명한 글자 색(알파 0.3 미만)도 그 서식을 버린다 — 그렇게 숨긴 글은 드러난다. 바탕과 같은 색(흰 바탕 위 흰 글자 등)은 바탕을 알 수 없어 막지 않는다 (2026-10-27 사용자 확정).

  • 링크 팝오버(폭 300): 주소 · 표시할 글자(비우면 주소). 새 링크는 [취소] [넣기], 이미 있는 링크는 [링크 빼기] … [취소] [적용]. Enter = 적용, Esc = 닫고 본문으로. 주소는 http(s) · mailto 만 받는다.

  • 표 넣기(더보기 ›): 8열 × 6행 격자에서 고른다("3행 × 4열"). 표 안의 행·열 추가·삭제는 다음 단계.

  • 메뉴·팝오버 공통: 흰 바탕 · 테두리 #E6E8EC · 모서리 10 · 그림자 --shadow-toast · 툴바 아래 선 바로 밑에서 연다. Esc 로 닫는다.

  • 빈 본문: 안내 문구 "내용을 입력해 주세요" · #667085(4.9:1). 커서는 문구 앞에 선다.

  • 문단: 문단 사이 여백은 0 — Enter 한 번이 한 줄이다(지금 textarea 와 같다). 줄 간격은 1.75 이고, 보내는 본문도 같은 값이다 (감싸는 div 의 line-height — 받는 사람이 편집기와 같은 모양으로 본다, 2026-10-27 구현하며 정함). 여러 칸 띄어쓰기 · 줄 앞 공백도 받는 쪽에서 그대로 보이게 보낸다. 탭은 다음 8칸 자리까지의 공백이 된다(편집 영역의 탭 간격과 같다, 2026-10-27).

답장 · 전달의 원문 인용

  • 원문은 인용 막대(왼쪽 2px #D0D5DD · 막대와 글 사이 14) 안에 둔다. 원문 글은 #4A5363, 내 글과 24 띄운다.
  • 머리는 답장 · 전달 모두 "원본 메일"(12.5px 굵게)이고, 그 아래에 항목 이름(#667085) · 값 표를 둔다.
    • 답장: 보낸 사람 · 날짜 · 제목 — 커서는 맨 위 첫 줄
    • 전달: 보낸 사람 · 날짜 · 받는 사람 · 참조 · 제목 — 커서는 받는 사람 칸
  • 막대는 회색이다 — 2px 브랜드색 선은 포커스 표시라 겹치지 않게 한다. 툴바의 "인용"도 같은 막대를 머리 없이 쓴다.
  • 초안이 한도(제목 255자 · 본문 텍스트 100,000자 · 서식 본문 500,000자 — 계약 2절)를 넘으면 인용을 줄이고 인용 끝에 "(원문이 길어 이하 생략)"을 둔다(2026-10-26 구현하며 정함 — 캔버스에는 그리지 않았다).
    • 처음 맞출 때 내 글 몫으로 한도의 5%를 남긴다. 보내기 직전에 다시 재서 넘으면 인용을 더 줄이고, 편집기에도 줄인 모양을 보인다(실행 취소로 되돌릴 수 있다).
    • 내 글만으로 넘으면 보내지 않는다 — 본문 아래(첨부 위)에 받는 사람 오류와 같은 빨간 띠로 알리고 본문에 초점을 준다. 제목이 255자를 넘으면 제목 아래에 같은 띠를 둔다 (2026-10-27 구현하며 정함).
    • 본문 띠의 문구는 전용 코드 MAIL_007("본문이 너무 길어 보낼 수 없습니다. 내용을 줄인 뒤 다시 보내 주세요.")다 — 서버가 같은 코드를 돌려줘도 같은 띠를 보인다. 제목 띠는 VAL_001 그대로다 (제목 칸은 255자에서 더 입력되지 않는다, 2026-10-27 사용자 확정).
  • 원문이 서버의 읽기 한도에서 잘렸으면(isBodyTruncated) 클라이언트가 줄이지 않았어도 인용 끝에 같은 생략 표시를 둔다.
  • 인용할 원문은 읽기 화면과 같은 규칙으로 고른다 — HTML 이 없거나 공백뿐이면 글자 본문(textBody)이다. 인용은 그림을 빼므로, 그림을 빼고 남는 글자가 없으면(그림만 있는 뉴스레터 — 그림을 감싼 레이아웃 표 포함) 그때도 글자 본문으로 인용한다 (2026-10-27 리뷰 반영).
  • 내가 보낸 메일에 답장하면 받는 사람은 그 메일의 받는 사람이다 — 답장은 원래 받는 사람, 전체 답장은 원래 받는 사람과 참조(둘 다 나는 뺀다). 나에게만 보낸 메일이면 나에게 답장한다 (2026-10-27 사용자 확정 — 전에는 받는 사람 칸이 비었다). 이때 회신 주소(Reply-To)는 보지 않는다. 받는 사람 없이 참조만 있던 메일이면 답장은 나에게, 전체 답장은 나에게 + 원래 참조로 간다.

나가기 전 확인 (2026-10-27 사용자 확정 — 캔버스에는 아직 없다)

  • 쓰기 화면을 연 뒤 받는 사람 · 제목 · 본문 · 첨부를 바꿨으면, 화면을 떠나기 전에 묻는다 — 답장 · 전달로 처음 채운 내용은 "바꿈"이 아니다.
    • 앱 안 이동(앱 레일 · 메일함 · 뒤로/앞으로 · 쓰기 화면의 취소 · 닫기 ✕)은 확인 대화상자를 띄운다 — 세션 만료 대화상자와 같은 모양이다. 제목 "보내지 않은 메일이 있습니다", 글 "이 화면을 나가면 쓰던 내용이 사라집니다.", 단추 [계속 쓰기](처음 초점 · Esc 와 같다) [나가기]. 단추는 오른쪽 정렬이고, 계속 쓰기는 채운 기본 단추 · 나가기는 "취소"와 같은 보조 단추다. 계속 쓰기를 고르면 주소와 방문 기록이 그대로다.
    • "바꿈"에는 아직 주소 칩이 되지 않은 받는 사람 입력 글자, 첨부 목록의 모든 변화(사전 검사에 걸린 파일 포함)도 든다. 지운 · 실행 취소로 처음 모양에 돌아오면 바꾸지 않은 것으로 본다. 방문 기록(뒤로 · 앞으로)으로 쓰기 화면끼리 옮겨 갈 때도 묻는다 (쓰기 화면에서는 메일함 패널의 "메일 쓰기"가 "메일 쓰는 중"으로 바뀌어 눌리지 않는다).
    • 대화상자가 떠 있는 동안 세션이 끝나면 대화상자를 닫고 그 이동을 취소한다(세션 만료 대화상자가 그 뒤에 뜬다).
    • 알려진 한계(vue-router): 뒤로 가기로 연 확인 창이 떠 있는 동안 주소창에는 가려던 주소가 보인다(계속 쓰기를 고르면 돌아온다). 그 사이 뒤로 가기를 두 번 누르면 방문 기록이 한 칸 어긋날 수 있다.
    • 탭 닫기 · 새로고침 · 다른 사이트로 가기는 브라우저 기본 확인창을 띄운다(문구는 브라우저가 정한다).
  • 보낸 뒤와 세션이 끝났을 때(로그아웃 · 만료 · 다른 탭의 로그아웃)는 묻지 않는다 — 세션 만료 대화상자와 겹치지 않는다.
  • 보내는 중에도 묻지 않는다 — 이미 보내기를 눌렀기 때문이다(Gmail 과 같다). 보내는 중에 화면을 떠나면 결과는 알림으로만 알린다: 성공은 평소의 성공 알림, 실패는 그 오류의 사전 문구 알림이다(받는 사람 · 첨부 · 본문 길이 오류처럼 평소 칸 아래에 보이던 것도). 떠난 화면이나 새로 연 쓰기 화면은 그 결과로 바뀌지 않는다 (2026-10-27 리뷰 반영). 단 보내는 중에 탭을 닫거나 새로고침하면 브라우저 기본 확인창은 뜬다 — 요청이 끊길 수 있고 결과를 알릴 화면이 남지 않기 때문이다.
  • 맥의 ⌘[ 를 본문 밖에서 눌러 브라우저 "뒤로"가 되어도 이 확인에 걸린다.
  • 2단계(임시 저장)부터는 저장된 채로는 묻지 않는다 — 저장하지 못한 내용이 있을 때만 이 확인을 띄우고 제목 · 글이 바뀐다 (아래 "2단계 › 임시 저장"). 단추 · 키 · 묻지 않는 경우는 그대로다.

본문 이미지 → 첨부 파일

  • 본문에는 이미지를 넣지 않는다(사용자 확정). 툴바에 이미지 버튼이 없다. 서명 그림만 예외다 (아래 "2단계 › 서명").
  • 본문에 붙여 넣거나(Ctrl+V) 끌어다 놓은 이미지는 첨부 파일로 올린다. 끌어다 놓은 다른 파일도 첨부로 올린다(쓰기 화면 어디에 놓아도 같다). 한도는 첨부와 같다 — 넘치면 첨부 목록에 실패로 보인다(States 의 "첨부 업로드").
    • 클립보드에 글이 함께 있으면 글을 붙여 넣는다 — 워드 · 엑셀에서 복사하면 고른 부분의 그림이 글과 함께 따라오기 때문이다. 글 없이 파일만 있을 때 첨부로 돌린다 (2026-10-27 구현하며 정함 — 캔버스 시안은 그림이 있으면 늘 첨부로 돌린다).
    • 이름 없이 붙여 넣은 그림은 "붙여넣은 이미지 N.확장자"로 올린다. 원래 이름이 있는 파일은 그 이름 그대로다.
  • 끌어다 놓는 동안 본문 위에 점선 덮개(2px #0E7C86 점선 · 바탕 #F3F8F8)와 "놓으면 첨부 파일로 올립니다"를 보인다. 쓰기 화면이 떠 있으면 파일은 창 어디(앱 레일 위 포함)에 놓아도 첨부로 받고, 덮개도 파일이 창 위에 있는 동안 보인다 — 받지 않는 곳에 놓으면 브라우저가 그 파일을 열어 쓰던 메일을 잃기 때문이다 (2026-10-27 구현하며 정함).
  • 이미지를 첨부로 돌렸을 때만 성공 알림 "이미지는 본문 대신 첨부 파일로 올렸습니다."를 띄운다(성공 알림과 같은 모양 · 5초).

2단계 — 임시 저장 · 서명 · 받는 사람 자동 완성 (2026-10-27 확정 — Settings · ComposeAssist)

임시 저장

  • 자동 저장: 바꾼 내용이 있으면 입력이 3초 멈출 때(10초에 한 번까지)와 화면을 떠날 때 임시보관함에 저장한다. 아무것도 쓰지 않은 쓰기 화면은 저장하지 않는다. 저장은 한 번에 하나씩이고, 보내기 전에는 진행 중인 저장이 끝나기를 기다린다.
  • 아래 단추 줄: [보내기] [임시 저장] 뒤에 상태 글(12px) — "저장하는 중…"(#667085) · "오후 3:12에 임시 저장됨"(✓ #0E7C86) · 실패 "임시 저장하지 못했습니다"(#B42318) + [다시 시도]. 「취소」는 없앤다 — 닫기(✕)가 저장한 채 닫는다.
    • 자동 저장이 실패하면 알림 없이 그 자리에만 보이고 조금 뒤 다시 시도한다. 「임시 저장」을 눌러 실패하면 사전 문구 알림도 띄운다.
    • 같은 저장본을 다른 창 · 기기에서 먼저 고쳤거나 지웠으면(MAIL_008, 휴지통으로 옮겼으면 PUT 의 404 RES_001) 막지 않고 지금 내용을 새 저장본으로 남긴다 — 상태 글 "다른 곳에서 바뀐 저장본이 있어 새로 저장했습니다 · 오후 3:14"(정보 아이콘, #7A5200). 임시보관함에 두 통이 남는다.
  • 삭제: 쓰기 화면 머리의 휴지통 단추(44 · "이 메일 삭제") — 임시 저장본을 휴지통으로 옮기고 닫는다. 알림 "임시 저장한 메일을 휴지통으로 옮겼습니다."
    • [되돌리기](임시보관함으로 되돌린다). 저장본이 아직 없으면 그냥 닫는다.
  • 임시보관함 목록: 보낸 사람 자리에 받는 사람(없으면 "(받는 사람 없음)"), 제목이 없으면 "(제목 없음)" — 둘 다 #667085. 시각은 마지막으로 저장한 때. 누르면 읽기 화면이 아니라 쓰기 화면에서 이어 쓴다. 보내면 임시보관함에서 사라진다.
  • 나가기 확인: 저장된 채로는 묻지 않는다. 떠나기 전에 남은 변경을 먼저 저장하고, 저장하지 못한 내용이 있을 때만 제목 "저장하지 못한 내용이 있습니다", 글 "이 화면을 나가면 마지막으로 바꾼 내용이 사라집니다."로 묻는다.

서명

  • 설정 › 메일 › 서명 — 앱 레일의 "설정"에서 연다. 왼쪽 패널(208 · #F8F9FB)의 "메일" 묶음에 "서명", 오른쪽은 콘텐츠 열(최대 800).
  • 서명 칸: 굵게 · 기울임 · 밑줄 · 글자 색 · 링크 · 그림. 글꼴은 쓰기 화면 본문과 같은 맑은 고딕 14px · 줄 간격 1.75.
  • 그림: 그림 단추 · 붙여 넣기 · 끌어다 놓기로 넣는다 — PNG · JPG · GIF, 한 장 300KB 이하, 3장까지. 그림을 고르면 칸 아래에 크기 줄(작게 96px · 보통 144px · 원본 — 최대 600px · 그림 빼기). 메일 안에 함께 실어 보내므로 받는 쪽에서 외부 이미지처럼 막히지 않는다. 지금 있는 흰 로고는 흰 메일 바탕에서 보이지 않으니 서명에는 색이 있는 로고를 쓴다.
  • 서명을 넣을 메일: 새 메일 · 답장 · 전달(인용한 원문 위) — 둘 다 기본으로 켠다. 미리 보기에서 [새 메일 | 답장]을 바꿔 본다.
  • [저장] → "저장했습니다. 다음에 메일을 쓸 때부터 들어갑니다."(#067647).
  • 쓰기 화면을 열 때 서명을 보통 글로 넣어 둔다 — 새 메일은 본문 끝(빈 줄 하나 뒤), 답장 · 전달은 인용 위. 그 메일에서 고치거나 지울 수 있고 임시 저장에도 남는다. 임시 저장본을 이어 쓸 때는 서명을 다시 넣지 않는다. 본문에 붙여 넣은 다른 그림은 지금처럼 첨부로 올린다.
  • 서명 그림처럼 본문 안에 그림으로 보인 부분은 첨부로 보이지 않는다 — 읽기 화면의 첨부 목록과 목록의 첨부 클립에서 뺀다 (받은 메일의 본문 그림도 같다). 전달할 때는 원문 그림을 잃지 않게 원본 첨부로 함께 붙는다 (인용 본문에서는 그림이 빠지기 때문이다).

받는 사람 자동 완성

  • 받는 사람 · 참조 · 숨은참조에서 한 글자 이상 치면 칸 바로 아래에 추천 목록(최대 8개) — "사내"(웹메일에 로그인한 적 있는 직원) · "최근 주고받은 주소" 두 묶음(묶음 이름 12px #667085 굵게). 줄은 높이 40 · 이름(맞는 글자 굵게) + 주소(12px #4A5363), 고른 줄은 #E3F2F3.
  • 이미 넣은 주소는 빼고, 초성으로도 찾는다(ㄷㅇ → 도윤). 목록 아래 안내 "↑↓ 고르기 · Enter · Tab 넣기 · Esc 닫기".
  • 친 글자가 완전한 메일 주소면 추천을 미리 고르지 않는다 — Enter 는 친 주소를 그대로 넣고("Enter를 누르면 친 주소를 그대로 넣습니다"), 화살표로 고른 뒤에만 추천이 들어간다. 엉뚱한 사람이 받는 사람에 들어가지 않게 하기 위해서다.
  • 쓰기 화면을 열 때 추천 목록을 한 번 받아 두고 치는 대로 바로 거른다 — 모자랄 때만 서버에 다시 묻는다.

3단계 — 새 메일 · 검색 · 여러 통 처리 (2026-10-27 확정 — Search · BulkSelect · ListStates)

API 와 서버 규칙은 계약 2절 "새 메일 확인" · "검색" · "여러 통 한꺼번에".

새 메일

  • 화면이 보이는 동안 1분마다(탭으로 돌아오면 바로 한 번) 바뀐 것이 있는지 묻고, 바뀌었으면 메일함 숫자와 보이는 목록을 맞춘다 — 고른 메일 · 열린 메일 · 스크롤 위치는 그대로다. 검색 결과는 저절로 바꾸지 않는다. 확인이 실패해도 알리지 않는다.
  • 맨 위를 보고 있으면 새 메일이 그냥 위에 생긴다(굵은 글자 = 안 읽음). 목록을 내려 본 채면 머리 아래에 알림 줄 "새 메일 N통 · 맨 위로"(높이 40 · 바탕 #F3F8F8 · #0A5F67 굵게 · 위 화살표)를 띄운다 — 누르면 맨 위로 가고 줄은 사라진다.
  • 탭 제목: 받은편지함에 안 읽은 메일이 있으면 앞에 "(N) " — "(3) 메일 · ARA SYSTEM". 소리 · 데스크톱 알림은 하지 않는다.

검색

  • 상단 검색 칸이 메일 화면에서는 "메일 검색"이다. Enter 로 찾고, 치는 동안에는 찾지 않는다. 검색 중인 칸은 테두리가 강조색(1px 테두리 + 1px 그림자)이고 끝에 ✕(40 · "검색어 지우기" — Esc 와 같다).
  • 결과는 보던 보기 방식(분할 · 목록) 그대로 목록 자리에 보인다.
    • 머리(52): [←](40×44 · "검색 닫기") · "검색 결과"(15px 굵게) · "‘회의’ · 6통"(12px #667085) · 보기 전환.
    • 그 아래 범위 줄(48 · 바탕 #FBFBFC): "찾을 곳" + [그 메일함 | 전체 메일함](36 — 설정 화면의 전환과 같은 밀도 예외)
      • 전체일 때 "휴지통과 스팸메일함은 빼고 찾습니다"(12px #667085).
  • 전체 메일함에서 찾으면 행에 메일함을 보인다 — 분할 보기는 첫 줄 시각 앞에 "보낸편지함 ·"(12px #667085), 목록 보기 표는 보낸 사람 뒤에 메일함 칸(96). 보낸편지함 · 임시보관함의 메일은 보낸 사람 자리에 "받는 사람: 이름".
  • 검색 결과에서 연 메일의 읽기 화면은 받는 사람 아래에 "메일함 받은편지함" 한 줄(12.5px #667085)을 더한다.
  • 결과 없음: 검색 아이콘 · "‘…’이/가 들어간 메일이 없습니다" · 한계 한 줄 "낱말 단위로 찾습니다. ‘서버이전’의 ‘서버’처럼 붙여 쓴 말의 일부나, 제목 맨 앞의 [공지]나 Re: 같은 말머리로는 찾지 못합니다." · 한 메일함에서 찾았으면 [전체 메일함에서 찾기].
  • 찾는 동안은 지금의 목록 뼈대를 보인다.

여러 통 처리

  • 목록 보기 표: 맨 앞에 고르기 칸(32 — 별 칸과 같은 밀도 예외). 상자 16px — 비었을 때 테두리 #8A94A3 1.5px, 고르면 #0E7C86 채움에 흰 체크. 머리의 상자는 불러온 메일 모두 고르기 · 풀기(일부만 골랐으면 – 표시). 고른 행 바탕 #E8F4F5(마우스를 올리면 #DDEFF0). 칸 = 고르기 32 · 별 32 · 보낸 사람 168 · 제목(나머지) · 날짜 84 · 크기 64 (칸 사이 12). 좁아질 때는 지금 규칙 그대로(1100 미만 크기 칸 숨김 · 600 미만 보낸 사람 112 · 날짜 72).
  • 분할 보기: 행에 마우스를 올리거나 초점이 가면 왼쪽 36px 에 고르기 상자가 나타나고, 하나라도 고르면 모든 행에 보인다. 행을 누르면 지금처럼 메일을 연다. Ctrl(⌘)+클릭은 하나씩, Shift+클릭은 범위, Esc 는 선택 풀기 (두 보기 공통). 여러 통을 고르는 동안 본문 칸은 열어 둔 메일을 그대로 보인다.
  • 도구 줄: 하나라도 고르면 목록 머리(52)가 도구 줄로 바뀐다(바탕 #F3F8F8) — "N통 선택됨"(14px 굵게) · 읽음 표시 · 안 읽음 표시 · 중요 표시(고른 것이 모두 중요면 "중요 해제") | 이동 ▾ · 삭제(휴지통에서는 "영구 삭제", #B42318) … 오른쪽 끝 "선택 해제"(✕). 단추는 높이 44 · 아이콘 + 이름이고, 폭 1100 미만이거나 분할 보기의 목록 칸에서는 아이콘만(40×44 — 이름은 툴팁 · aria-label).
  • 이동 메뉴(항목 높이 40 — 메뉴 항목 밀도 예외): 지금 메일함 · 임시보관함 · 휴지통을 뺀 메일함.
  • 알림(오른쪽 아래 · 되돌리기가 있으면 10초): "메일 N통을 보낸편지함으로 옮겼습니다." [되돌리기] · "메일 N통을 휴지통으로 옮겼습니다." [되돌리기] · "메일 N통을 영구 삭제했습니다." · 일부만 처리했으면 뒤에 "1통은 그사이 옮겨졌거나 지워져 그대로 두었습니다." (메일함 이름의 받침에 맞춰 '으로/로'). 읽음 · 중요 표시는 목록이 바로 바뀌므로 알리지 않는다. [되돌리기] 뒤 알림은 한 메일함으로 되돌렸으면 "메일 N통을 받은편지함으로 되돌렸습니다.", 여러 곳이면 "메일 N통을 되돌렸습니다."(2026-11-07 — 잇달아 되돌린 알림을 가를 수 있게).
  • 영구 삭제 확인: 휴지통에서만. 제목 "메일 N통을 영구 삭제할까요?"(대화로 묶은 휴지통에서 줄을 골랐으면 "휴지통에 있는 메일 N통을 영구 삭제할까요?" — 14단계) · 글 "휴지통에서 지운 메일은 되돌릴 수 없습니다." · [영구 삭제](#B42318 채움) [취소] — 열리면 「취소」에 초점이 간다.
  • 메일함을 바꾸거나 검색하면 선택을 푼다.

4단계 — 계정 · 메일 설정 (2026-10-28 확정 — SettingsAccount · SettingsVacation · MailboxManage · MailActions4)

API 와 서버 규칙은 계약 1절 "PUT /api/auth/password", 2절 "메일함 관리" · "부재중 자동 응답" · "스팸 · 원문".

설정 메뉴

  • 왼쪽 메뉴를 두 묶음으로: 계정 › 비밀번호 / 메일 › 서명 · 부재중 자동 응답. 묶음 이름은 12px #667085 굵게, 항목은 지금 서명 항목과 같은 모양(40 · 아이콘 16).

비밀번호

  • 칸 셋(최대 폭 420): 지금 비밀번호 · 새 비밀번호 · 새 비밀번호 확인. 칸 높이 44, 앞의 두 칸 끝에 보기/숨기기 단추(44 · 눈 아이콘, aria-pressed).
  • 새 비밀번호 아래 안내 "8자 이상이고 지금 비밀번호와 달라야 합니다. 영문, 숫자, 기호를 섞으면 더 안전합니다." (12.5px #667085).
  • 오류는 그 칸 아래 빨간 글(#B42318, 아이콘 alert-circle) · 칸 테두리도 빨강 — "지금 비밀번호가 올바르지 않습니다." · "새 비밀번호가 서로 다릅니다."
  • [비밀번호 바꾸기] → "비밀번호를 바꿨습니다. 다른 기기와 브라우저에서는 로그아웃했습니다."(#067647), 세 칸을 비운다.
  • 아래 안내 상자(바탕 #F3F8F8): 다른 곳의 로그인(웹메일 · ARA SYSTEM 앱)이 끊기는 것과 메일 프로그램(IMAP)에 새 비밀번호를 다시 넣어야 하는 것.

부재중 자동 응답

  • 부재중 자동 응답 사용(44). 끄면 아래 칸을 흐리게(쓴 글은 남긴다).
  • 기간: (•) 끌 때까지 계속 / ( ) 기간 정하기 → 시작 · 끝 날짜 칸(44). "시작하는 날 0시부터 끝나는 날 밤 12시까지 답합니다. 시각은 이 PC의 시간으로 셉니다."
  • 제목(44, 최대 폭 520) · 내용(여러 줄, 글자만 — "서식과 그림 없이 글자만 보내고, 서명은 붙지 않습니다."). — 16단계부터 내용 칸은 서식 편집기(아래 16단계 절).
  • [저장] → "저장했습니다. 10월 30일(금)부터 11월 3일(화)까지 자동으로 답합니다." / "끌 때까지 자동으로 답합니다." / 끄면 "자동 응답을 껐고, 써 둔 글은 그대로 남아 있습니다."
  • 미리 보기 "보낸 사람이 받는 답장"(보낸 사람 · 제목 · 내용) + "같은 사람에게는 매번 보내지 않습니다."
  • 켜져 있는 동안 메일 화면의 메일함 패널 아래(사용량 위)에 안내(바탕 #FFF7E6 · #7A5200, 높이 44 이상): "부재중 자동 응답 켜짐" / "11월 3일까지 · 설정" — 누르면 이 설정으로.

메일함 관리 (메일함 패널)

  • 기본 메일함 아래에 "내 메일함"(12px #667085 굵게) 묶음 — 하위 메일함은 16px 씩 들여 쓴다(지금 규칙). 묶음 끝에 + 새 메일함.
  • 각 메일함 행 오른쪽에 ⋯ 단추(32×40 — 별 칸과 같은 밀도 예외). 마우스를 올리거나 초점이 가면 보이고, 메뉴가 열린 동안은 계속 보인다. 숫자(안 읽음)는 ⋯ 앞에 그대로.
  • ⋯ 메뉴(항목 40): 모두 읽음으로 표시(안 읽은 메일이 있을 때) · 하위 메일함 만들기(받은편지함 · 내 메일함) · 이름 바꾸기 · 지우기(빨강 — 내 메일함만) · 휴지통 비우기 / 스팸메일함 비우기(빨강 — 그 메일함에서만).
  • 대화상자(공용 모양): 새 메일함 · "‘프로젝트’ 안에 새 메일함" · 메일함 이름 바꾸기 — 이름 칸(44, 64자) + 안내 "64자까지 쓸 수 있고, 같은 자리에 같은 이름은 쓸 수 없습니다." (같은 이름이면 그 자리에 빨강 "같은 이름의 메일함이 이미 있습니다."). 지우기 — "‘거래처’ 메일함을 지울까요?" "안의 메일 12통은 휴지통으로 옮깁니다. 지운 메일함은 되돌릴 수 없습니다." [지우기](빨강 채움) [취소]. 하위 메일함이 있으면 "‘프로젝트’ 메일함을 지울 수 없습니다" "하위 메일함이 있습니다. 하위 메일함을 먼저 지워 주세요." [확인].
  • 휴지통 · 스팸메일함 목록 머리 오른쪽에 [휴지통 비우기] / [스팸메일함 비우기](빨강 글자 · 44). 확인 "휴지통의 메일 21통을 모두 영구 삭제할까요?" "지운 메일은 되돌릴 수 없습니다." [영구 삭제] [취소].
  • 비우기 · 지우기 대화상자는 열리면 「취소」에 초점이 간다.

스팸 · 원문 · 오래 걸리는 처리

  • 읽기 화면 도구 줄: 답장 · 전체 답장 · 전달 | 스팸 신고 · 삭제 · ⋯(더 보기 40×44) — ⋯ 메뉴에 "원문 내려받기 (.eml)". 스팸메일함에서는 「스팸 신고」 자리에 「스팸 아님」(#0A5F67 굵게, 받은편지함 아이콘).
  • 여러 통 도구 줄: 이동 ▾ 과 삭제 사이에 「스팸 신고」(스팸메일함에서는 「스팸 아님」).
  • 오래 걸리는 처리(모두 읽음 · 비우기 · 메일함 지우기): 알림이 "받은편지함의 메일을 읽음으로 표시하는 중 · 600 / 1,234통"(돌아가는 아이콘)으로 남았다가 끝나면 "받은편지함의 메일 1,234통을 읽음으로 표시했습니다." 처리하는 동안 다른 일을 해도 된다.
  • 알림 문구: "메일 2통을 스팸메일함으로 옮겼습니다." [되돌리기] · "메일 1통을 스팸이 아닌 메일로 표시해 받은편지함으로 옮겼습니다." [되돌리기] · "‘협력사’ 메일함을 만들었습니다." · "‘거래처’ 메일함을 지웠습니다. 안의 메일 12통은 휴지통에 있습니다." · "메일 21통을 지워 휴지통을 비웠습니다." (비우기 · 지우기는 되돌리기 없음)

5단계 — 실시간 새 메일 · 바탕화면 알림 (2026-10-28 확정 — SettingsNotify · MailRealtime)

API 와 서버 규칙은 계약 2절 "실시간 알림".

설정 › 메일 › 알림

  • 메뉴의 "메일" 묶음 끝에 「알림」(종 아이콘).
  • 새 메일이 오면 바탕화면 알림 보내기(44). 켜면 브라우저가 허락을 묻는다. 칸 아래 안내(12.5px): 묻기 전 "켜면 브라우저가 알림을 허락할지 한 번 묻습니다. [허용]을 눌러 주세요." · 허용됨 "이 브라우저에서 알림이 허용되어 있습니다."(#067647, 체크 아이콘) · 막힘(칸을 끄고 빨강 #B42318) "브라우저가 이 사이트의 알림을 막고 있습니다. 주소창 왼쪽의 사이트 정보(자물쇠)를 눌러 [알림]을 [허용]으로 바꾼 뒤 이 화면을 새로 고쳐 주세요." · 지원하지 않음 "이 브라우저에서는 바탕화면 알림을 쓸 수 없습니다. 새 메일은 그래도 목록에서 바로 볼 수 있습니다."
  • 알림 내용: [x] 메일 제목도 보이기 — "끄면 알림에 보낸 사람 이름만 나옵니다. 다른 사람과 화면을 함께 보는 자리라면 꺼 두시기를 권합니다." (알림을 끄면 흐리게)
  • [시험 알림 보내기](보조 단추 44 · 종 아이콘) — 알림 "ARA 웹메일" / "알림이 잘 옵니다. 새 메일이 오면 이렇게 알려 드립니다."
  • 실시간 연결: ● 초록 "연결되어 있어 새 메일이 오면 바로 알 수 있습니다." / 끊겼을 때 "연결할 수 없어 1분마다 확인합니다." (저절로 다시 잇는다)
  • 아래 안내 상자(바탕 #F3F8F8): 이 브라우저에만 적용 · 웹메일 탭이 하나라도 열려 있어야 · 웹메일 화면을 보고 있을 때는 보내지 않음.

목록 위에서 한 번 권하기

  • 알림을 켜지도 닫지도 않은 사람에게 메일 화면의 목록 머리 아래에 40 줄(바탕 #F3F8F8 — 새 메일 알림 줄과 같은 높이, 밀도 예외): 종 아이콘 + "바탕화면 알림을 켜 두면 다른 창을 보는 동안에도 새 메일을 알려 드립니다." [알림 켜기](강조 글자 굵게) [✕ 닫기].
  • [알림 켜기] → 브라우저가 묻는다 → 허용: 알림 "바탕화면 알림을 켰습니다." / 막힘: 오류 알림 "브라우저가 알림을 막고 있습니다. 푸는 방법은 [설정] → [알림]에서 볼 수 있습니다." [설정 열기].
  • [✕]는 다시 보이지 않는다(브라우저 · 사용자마다). 브라우저가 이미 막았거나 지원하지 않으면 보이지 않는다.

바탕화면 알림

  • 모양은 운영체제 · 브라우저가 그린다 — 정하는 것은 제목 · 내용 · 아이콘(ARA 마크, 바탕 #0B3640 둥근 사각)뿐이다.
  • 한 통: 제목 = 보낸 사람(이름, 없으면 주소), 내용 = 메일 제목. 여러 통: "새 메일 3통" / "박서준 · 이도윤 외 1명". 제목 숨김: 내용 "새 메일이 왔습니다".
  • 새 알림은 앞의 알림을 바꿔 쓴다(쌓이지 않는다). 누르면 웹메일 탭으로 와서 그 메일을 연다(여러 통이면 받은편지함).
  • 받은편지함에 새로 배달된 안 읽은 메일만(내가 보낸 메일 · 임시 저장 · 다른 메일함으로 옮겨진 메일은 아님), 웹메일을 보고 있지 않을 때만(보이고 초점이 있는 탭이 없을 때), 탭이 여러 개여도 한 번.

실시간 반영

  • 화면 모양은 그대로다 — 새 메일이 1초 안에 목록 · 메일함 숫자 · 탭 제목 "(N)" 에 나타난다(지금은 최대 1분). 목록을 내려 보고 있으면 지금처럼 "새 메일 N통 · 맨 위로" 줄.
  • 다른 탭을 보는 동안에도 탭 제목의 "(N)" 이 바로 바뀐다.
  • 실시간 연결이 끊기면 화면에는 알리지 않고 1분 확인으로 돌아간다(설정 › 알림의 연결 상태로 볼 수 있다).

6단계 — 메일 필터 (2026-10-28 확정 — SettingsFilter · MailFilterStates)

받은 메일을 조건에 따라 메일함으로 옮기고, 읽음 · 중요로 표시하고, 동료에게 전달한다. 규칙과 엔진 쪽 사실은 계약 2절 "메일 필터".

설정 › 메일 › 필터

  • 메뉴는 메일 묶음의 서명 · 부재중 자동 응답 · 필터 · 알림 (깔때기 아이콘). 머리는 "필터" + 설명 한 줄 + 오른쪽 [새 필터](브랜드 색).
  • 목록은 테두리 상자 하나에 행 60 — 순서 번호 · 켜기 스위치(36×20) · 이름(굵게)과 요약 한 줄 · ▲ ▼(32×40, 밀도 예외 — 메일함 ⋯ 와 같다). 행을 누르면 고친다. 요약은 "보낸 사람에 ‘@hanbit-partners.co.kr’ 포함 → 옮기기: 거래처" — 조건은 " 그리고 " / " 또는 ", 할 일은 " · ". 이름이 없으면 조건이 이름 자리에 온다. 꺼 둔 규칙은 글자를 흐리게. 옮길 메일함이 지워진 규칙은 빨간 안내 한 줄 "옮길 메일함이 지워져 메일이 받은편지함으로 들어옵니다. 눌러서 고쳐 주세요."
  • 목록 아래 "필터 N개를 만들었고, 50개까지 만들 수 있습니다." 와 안내 상자(차례로 적용 · 옮기면 멈춤 / 스팸 제외 / 사내 전달만 · 한 곳 · 사본 남김 / 부재중과 함께 동작).

고치기 창 (모달 620)

  • 이름(선택) → "어떤 메일에"(조건이 모두 · 하나라도 · 모든 메일) → 조건 줄(어디에서 116 · 포함/포함하지 않음 132 · 글 · ✕ 36, 5개까지 [조건 더하기]) → "할 일"(메일함으로 옮기기 + 메일함 고르기 · 읽음으로 표시 · 중요 표시 · 동료에게 전달 + 사내 주소) → 안내 띠 → [이 필터 지우기](고칠 때만, 왼쪽) · [저장] [취소].
  • 메일함 고르기에는 받은편지함 · 내 메일함 · 스팸메일함 · 휴지통(보낸편지함 · 임시보관함 없음 — 보관함은 구현하며 더했다, 아래 "6단계 구현하며 정한 보완"). 지워진 메일함이면 "지워진 메일함이니 다른 메일함을 골라 주세요"가 골라진 채로.
  • 잘못은 [저장]을 누를 때 칸 아래 빨간 글로만 — 빈 조건 · ${ · 바깥 주소 전달 · 할 일 없음 (MailFilterStates 의 모음).

상태 · 들어오는 길 (MailFilterStates)

  • 필터가 없을 때 · 다른 곳에서 먼저 바꿨을 때(목록 위 노란 줄 + [다시 불러오기]) · 동작하지 않을 때(+ [다시 켜기]) · 50개를 다 만들었을 때 · 알림 문구.
  • 읽기 화면 ⋯ 메뉴에 「이런 메일로 필터 만들기」(원문 내려받기 아래) — 설정 › 필터의 새 필터 창이 보낸 사람의 주소로 채워져 열린다.
  • 켜기 · 끄기 · 순서 바꾸기는 알리지 않는다(목록이 바로 바뀐다). 저장 "필터를 저장했습니다.", 지우기 "‘거래처 메일’ 필터를 지웠습니다. [되돌리기]".

7단계 — 이미 받은 메일에 필터 적용 (2026-10-29 확정 — MailFilterApply · MailFilterApplyStates)

필터를 저장할 때 받은편지함에 이미 있는 메일에도 그 필터 하나를 적용한다. 6단계의 두 한계(고치기 창의 나가기 확인 없음 · 부재중 화면이 꺼진 규칙을 모름)도 함께 고친다. 규칙은 계약 2절 "이미 받은 메일에 적용".

고치기 창 — 「받은편지함에 이미 있는 메일도 정리하기」

  • 할 일 아래, 6단계의 안내 띠 자리에 회색 상자 하나: 체크 칸(이름 굵게) + 칸 아래 설명(12.5). 상자 아래 한 줄 "스팸 메일은 필터를 거치지 않습니다."
  • 창을 열 때마다 꺼져 있다 — 필터의 설정이 아니라 이번 저장에 한 번 쓴다(읽기 화면에서 만드는 새 필터도 같다). 목록의 행에는 단추를 더하지 않는다 — 있는 필터는 창을 열어 고르고 저장한다.
  • 설명은 "저장하면 받은편지함에서 조건에 맞는 메일을 찾아 바로 정리합니다." — 전달이 있는 필터면 " 전달은 새로 오는 메일에만 합니다."를 더한다.
  • 고를 수 없는 두 경우(칸을 끄고 이름을 흐리게, 설명이 까닭): 전달만 하는 필터 "전달은 새로 오는 메일에만 해서, 전달만 하는 필터는 이미 받은 메일에 할 일이 없습니다." · 이 탭에서 다른 필터를 적용하는 중 "다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤에 고를 수 있습니다."
  • 목록 머리의 설명과 안내 상자의 "새로 오는 메일에만"을 바꾼다(MailFilterApply 보드의 글 — 이미 받은 메일 줄을 더하고, 전달 줄에 "새로 오는 메일만").

저장한 뒤 — 진행 · 결과 알림

  • "필터를 저장했습니다." 대신 4단계의 진행 알림: "‘거래처 메일’ 필터로 받은편지함을 정리하는 중 · 4,200 / 8,412통 확인"(확인한 수 / 받은편지함 메일 수 — 최근 메일부터). 끝나면 그 자리에서 "‘거래처 메일’ 필터로 받은편지함의 메일 37통을 정리했습니다." + 옮기는 필터면 [거래처 보기](그 메일함으로) — 읽음 · 중요만 하는 필터는 단추 없음, 맞는 메일이 없으면 "받은편지함에는 ‘거래처 메일’ 필터에 걸리는 메일이 없습니다."
  • 멈추면 "‘거래처 메일’ 필터로 8,412통 가운데 4,200통까지 확인하고 멈췄습니다. 그동안 12통을 정리했습니다. 다시 시도해 주세요." + [다시 시도](남은 메일부터 이어서). 처음 요청부터 실패하면 "필터는 저장했지만 받은편지함은 정리하지 못했습니다. 다시 시도해 주세요." + [다시 시도]. 다른 창 · PC 에서 적용 중이면 "다른 창에서 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요."
  • 그동안 다른 화면으로 가서 메일을 읽고 써도 이어 한다. 탭을 닫으면 멈춘다(이어 하지 않는다). 세션이 바뀌면 멈추고 알림을 닫는다.

저장하지 않고 닫을 때 (6단계 한계를 고친다)

  • 창을 연 뒤 바꾼 것이 있으면 [취소] · Esc · 다른 화면으로 가기에 확인 — 서명 · 부재중 설정의 나가기 확인과 같은 모양: "저장하지 않은 필터가 있습니다" / "창을 닫으면 고친 내용이 사라집니다." [계속 고치기](채운 단추 · 처음 초점 · Esc) [닫기]. 다른 화면으로 가려다 물으면 "이 화면을 나가면 고친 내용이 사라집니다." [나가기]. 탭 닫기 · 새로고침은 브라우저 확인창.
  • 읽기 화면에서 채워 연 새 필터는 채운 그대로면 묻지 않는다(바꾼 것은 창을 연 때와 견준다).

부재중 자동 응답 화면 — 엔진에서 규칙이 꺼졌을 때 (6단계 한계를 고친다)

  • 자동 응답을 켜 두었는데(저장한 값) 엔진에서 우리 규칙이 꺼져 있으면(isActive: false) 머리 아래 노란 줄(필터 화면의 줄과 같은 모양): "다른 곳에서 메일 설정이 바뀌어 켜 둔 자동 응답이 지금은 나가지 않습니다. 아래 [저장]을 누르면 다시 켜집니다."
  • 단추는 없다 — 저장 단추는 늘 누를 수 있다. 꺼 두었으면 보이지 않는다. 저장하면 필터도 함께 다시 켜진다.

8단계 — 주소록 (2026-10-29 확정 — Contacts · ContactsEdit · ContactsMail)

레일의 주소록 모듈을 켠다(app-modules.ts 의 contacts). 내 주소록(연락처 · 그룹)과 사내 목록(직원 · 공용 주소)이 있고, 메일 쓰기 · 읽기 화면과 이어진다. 규칙은 계약 3절 "주소록". 셸 · 색 · 단추 · 창 · 알림은 메일 모듈과 같다.

주소록 화면 (Contacts)

  • 자리: 레일 · 상단 바 · 모듈 패널(208) · 목록(384) · 상세 — 메일의 분할 보기와 같다. 상단 바의 찾기 칸은 지금 보는 목록 안에서 찾는다 ("이름, 주소, 회사, 전화번호, 초성(ㅂㅈㅎ)으로 찾기").
  • 패널: [연락처 추가](채운 단추) → 「내 주소록」 전체 연락처(수) → 「그룹」 그룹마다 한 줄(수 · ⋯ 32×40 — 메일함 ⋯ 와 같은 밀도 예외) + [새 그룹] → 「사내」 직원(수) · 공용 주소(수).
  • 목록 머리: 제목 · "N명"(공용 주소는 "N개", 찾으면 "찾은 결과 N명"). 그룹이면 오른쪽에 [구성원 넣기] [그룹에 메일 쓰기]. 사내면 머리 아래 회색 줄 — 직원 "회사 메일을 쓰는 직원입니다. 사내 목록에는 이름과 메일 주소만 있습니다." · 공용 주소 "여러 사람이 함께 받는 사내 주소입니다."
  • 목록 줄(56): 아바타 32(이름 첫 글자 — 공용 주소는 회색) · 이름(굵게) + 회사 · 부서(작게) / 대표 주소. 고른 줄은 청록 바탕. 이름 차례(한국어), 100명씩 받고 끝에 [더 보기].
  • 상세 머리 줄: [메일 쓰기] [고치기] … [⋯] — 사내는 [메일 쓰기] [내 주소록에 추가]. 아래로 아바타 48 · 이름(20) · "회사 · 부서 · 직책"(없으면 "소속 없음", 사내는 "사내 직원" · "공용 주소").
  • 상세 칸(왼쪽 이름 88): 이메일(주소마다 링크 — 누르면 그 주소로 새 메일, 옆에 종류) · 전화(번호 · 종류) · 소속 · 직책 · 그룹(누르면 그 그룹의 목록) · 메모(줄바꿈 그대로). 빈 칸은 줄째 뺀다. 사내 상세 아래: "사내 목록에는 이름과 주소만 있고, 여기서는 고칠 수 없습니다. 전화번호나 메모를 적어 두려면 내 주소록에 추가해 주세요."
  • 빈 상태: 찾은 것이 없으면 "‘…’에 맞는 연락처가 없습니다"(사내는 "주소가") + "이름, 주소, 회사, 전화번호의 일부나 이름의 초성으로 찾을 수 있습니다." · 고른 것이 없으면 "고른 연락처가 없습니다".

연락처 추가 · 고치기 (ContactsEdit)

  • 모달 620 — 필터 고치기 창과 같은 틀이고 나가기 확인도 같다("저장하지 않은 연락처가 있습니다" / [계속 고치기] [닫기]).
  • 칸: 이름(100자) · 이메일(종류 회사 · 개인 · 기타 + 주소 + ✕, [이메일 더하기] 5개까지 — "받는 사람 칸에는 맨 위 주소가 먼저 나옵니다") · 전화번호(휴대전화 · 회사 · 집 · 기타 + 번호 + ✕, 5개까지) · 회사 · 부서 · 직책(한 줄에 셋) · 메모(1,000자 — 셈 "38 / 1,000") · 그룹(체크 칸). 맨 아래 "이름이나 이메일 가운데 하나는 넣어 주세요." · [저장] [취소], 고치기만 왼쪽에 [이 연락처 지우기](빨간 글).
  • 잘못은 [저장]을 누를 때 칸 아래 빨간 글로만(첫 잘못된 칸에 초점, 알림 없음) — "이메일 형식이 올바르지 않습니다." 등.
  • 같은 주소: 이메일 칸을 떠날 때 내 주소록에 같은 주소(다른 연락처)가 있으면 칸 아래 갈색 글 "주소록에 있는 강서윤(거래처)의 주소와 같습니다. 그래도 저장할 수 있습니다. [그 연락처 보기]" — 막지 않는다.
  • 지우기는 확인 없이: 창을 닫고 "‘박지훈’ 연락처를 지웠습니다." + 되돌리기.

그룹 (ContactsEdit)

  • 새 그룹 · 이름 바꾸기: 모달 400, 칸 하나(50자) · [만들기] / [저장] [취소]. 같은 이름이면 칸 아래 "같은 이름의 그룹이 있습니다. 다른 이름을 써 주세요."(CONTACT_001 사전 문구 — 알림 없이. 보드는 앞 문장만). 그룹은 100개까지.
  • 그룹 ⋯: 이름 바꾸기 · 그룹에 메일 쓰기 · 그룹 지우기(빨간 글). 지우기는 확인 없이 "‘거래처’ 그룹을 지웠습니다. 그룹에 있던 연락처 4명은 그대로 남아 있습니다." + [되돌리기].
  • 연락처 ⋯(상세): 주소 복사 · 연락처 지우기.
  • 구성원 넣기: 모달 620 — 「내 연락처 | 사내 직원」 전환(36 — .segmented 밀도 예외) + 찾기(초성), 체크 줄 48(이름 · 회사 · 주소). 이미 구성원인 연락처는 보이지 않는다. "N명 고름" · [넣기] [취소]. 사내 직원을 고르면 내 주소록에도 이름 · 주소로 추가한다(같은 주소의 연락처가 이미 있으면 그 연락처를 넣는다). 끝나면 "2명을 ‘거래처’ 그룹에 넣었습니다."
  • 빈 그룹: "‘…’ 그룹에 아직 아무도 없습니다" · "구성원을 넣으면 그룹 이름으로 한 번에 메일을 보낼 수 있습니다." + [구성원 넣기].
  • 그룹에 넣기 · 빼기는 연락처 고치기 창의 「그룹」으로, 여러 사람을 한꺼번에는 [구성원 넣기]로.

상태 (ContactsEdit)

  • 처음(연락처 없음): "아직 연락처가 없습니다" · "자주 메일을 주고받는 사람을 넣어 두면 받는 사람 칸에서 바로 찾을 수 있습니다." · [연락처 추가] · "메일을 읽다가 [⋯] → [보낸 사람을 주소록에 추가]로도 넣을 수 있습니다."
  • 사내 목록을 받지 못함: "사내 목록을 불러오지 못했습니다" · "잠시 후 다시 시도해 주세요. 내 주소록은 그대로 쓸 수 있습니다." · [다시 시도].
  • 한도: 연락처가 3,000개면 [연락처 추가]를 끄고(aria-disabled) 아래에 "연락처는 3,000개까지 둘 수 있습니다. 쓰지 않는 연락처를 지운 뒤 추가해 주세요."
  • 알림: "연락처를 저장했습니다." · "‘…’ 연락처를 지웠습니다." · "‘…’ 그룹을 지웠습니다. 그룹에 있던 연락처 N명은 그대로 남아 있습니다." · "N명을 ‘…’ 그룹에 넣었습니다." (단추가 있으면 10초)

메일과 함께 (ContactsMail)

  • 받는 사람 추천: 묶음은 주소록 → 그룹 → 사내 → 최근 주고받은 주소(모두 합쳐 최대 8개). 주소록은 주소마다 한 줄 — 대표 주소는 "주소 · 회사", 그 밖의 주소는 "주소 · 개인"처럼 종류. 사내는 이제 로그인한 적 있는 직원이 아니라 메일 서버에 계정이 있는 직원 · 공용 주소 모두다. 여러 묶음에 있는 주소는 앞 묶음에 한 번만.
  • 그룹 줄: 아이콘 + 그룹 이름(맞는 글자 굵게) + "고르면 N명을 한꺼번에 넣습니다". 고르면 구성원의 대표 주소를 칩으로 넣고 칸 아래 "‘거래처’ 그룹의 4명을 받는 사람에 넣었습니다." 이메일이 없는 구성원 · 이미 넣은 주소는 빼고, 뺀 것이 있으면 "주소가 없는 1명은 뺐습니다"를 덧붙인다.
  • 주소록에서 메일 쓰기: 연락처 · 그룹 · 사내의 [메일 쓰기]와 주소 링크 → 새 쓰기 화면, 받는 사람을 채워서(그룹은 구성원 모두 — 받는 사람 칸). 쓰다 만 메일은 임시 저장 규칙대로 남는다.
  • 읽기 화면 ⋯: 둘째 묶음(「이런 메일로 필터 만들기」 위)에 「보낸 사람을 주소록에 추가」 → 이름 · 주소를 채운 연락처 추가 창. 이미 내 주소록에 있으면 「주소록에서 보기」(그 연락처를 고른 주소록). 사내 직원의 주소도 「보낸 사람을 주소록에 추가」. 보낸 사람 주소가 없는 메일은 항목이 없고, 연락처가 3,000개면 항목을 끄고 이름 아래에 까닭 한 줄.

9단계 — 보내기 취소 · 예약 발송 · 외부 이미지 · 인쇄 · 보낸 사람 차단 (2026-10-29 확정 — MailReadTools · MailPrint · MailSendLater · SettingsSendRead)

작고 자주 쓰는 다섯 가지를 한 번에 더한다. 계약은 docs/mail/api-contract.md 2절의 9단계 절들. 새 아이콘: 인쇄(printer) · 시계(clock) · 차단(원 + 빗금).

읽기 화면 — 외부 이미지 · ⋯ 메뉴 · 차단 (MailReadTools)

  • 외부 이미지 줄: 지금의 노란 안내 줄("외부 이미지를 차단했습니다. …") 오른쪽에 [이미지 보기](36 — 밀도 예외 --size-notice-action). 누르면 그 메일만 이미지를 보이고 안내 줄은 사라진다. 다시 열면 설정대로다. 받지 못한 이미지 자리는 비어 있다. 메일 안의 배경 그림은 보이지 않는다(알려진 한계).
  • 설정에 따라(설정 › 메일 › 보내기 · 읽기 › 외부 이미지): 늘 묻기(처음 값) · 주소록과 사내 직원은 늘 보기 · 늘 보기. 처음부터 보이면 안내 줄이 없다. 스팸메일함의 메일은 늘 묻는다.
  • ⋯ 메뉴: 「인쇄」(Ctrl+P 표시) → 「원문 내려받기 (.eml)」 → 구분선 → 「보낸 사람을 주소록에 추가」/「주소록에서 보기」 → 「이런 메일로 필터 만들기」 → 「보낸 사람 차단」(빨간 글). 이미 차단한 보낸 사람이면 마지막 항목이 「보낸 사람 차단 풀기」. 사내 직원 · 내 주소에는 차단 항목이 없다. 차단한 주소가 500개면 항목을 끄고 이름 아래에 까닭.
  • 차단 확인 창(모달 400): 제목 "보낸 사람 차단", 설명 "앞으로 오는 메일은 스팸메일함으로 옮겨지고, 부재중 자동 응답도 보내지 않습니다." 고르기 둘 — 「이 주소만」(주소) · 「이 도메인 전체」(@도메인) — 여러 사람이 쓰는 메일 서비스(gmail.com · naver.com 등)는 도메인을 고를 수 없게 끄고 그 아래에 까닭. 체크 「이 메일도 스팸메일함으로 옮기기」(처음 켜짐), 안내 "차단은 [설정] → [차단한 주소]에서 풀 수 있습니다." — [차단](처음 초점 — 되돌릴 수 있는 일) [취소].
  • 차단한 뒤: 알림 "‘주소’ 을 차단하고 이 메일을 스팸메일함으로 옮겼습니다." + [되돌리기](차단을 풀고 메일을 받은편지함으로). 스팸메일함에서 차단한 보낸 사람의 메일을 열면 회색 안내 줄 "차단한 보낸 사람의 메일입니다." + [차단 풀기].

인쇄 화면 (MailPrint)

  • ⋯ › 인쇄(또는 읽기 화면에서 Ctrl+P)는 새 탭에 인쇄 화면을 열고 브라우저의 인쇄 창을 바로 띄운다. 앱의 레일 · 메일함 · 목록 · 도구 줄은 인쇄하지 않는다.
  • 화면에만 보이는 위 줄: "인쇄 화면" + [인쇄](다시 띄우기) [닫기](탭 닫기). 인쇄 창을 닫아도 탭은 남는다.
  • 인쇄하는 것: 머리 줄(「ARA SYSTEM 메일」 · 인쇄한 직원의 이름 <주소>) → 제목 → 보낸 사람 · 받는 사람 · 참조 · 날짜(읽기 화면에 보이는 것 — 숨은참조는 넣지 않는다) → 첨부(이름 · 크기만) → 가는 선 → 본문(읽기 화면과 같은 정화). 쪽 번호 · 날짜는 브라우저의 머리글 · 바닥글 설정을 따른다.
  • 외부 이미지는 읽기 화면에서 보이던 대로. 본문은 인쇄 폭(A4)에 맞춰 높이를 다시 잰 뒤 인쇄한다 — 긴 메일은 여러 쪽으로 이어지고 끝에 빈 쪽이 생기지 않는다 (2026-10-29 확인: 지금 화면을 그대로 인쇄하면 앱 틀까지 찍히고, 화면 폭에서 잰 본문 높이 때문에 끝에 빈 쪽이 10장 생겼다).
  • 못 열 때: 메일을 찾을 수 없으면 "메일을 찾을 수 없습니다" + [닫기], 받지 못하면 [다시 시도] — 인쇄 창은 띄우지 않는다. 세션이 끝났으면 로그인 화면으로.

보내기 취소 · 예약 발송 (MailSendLater)

  • [보내기 ▾]: 쓰기 화면의 [보내기]를 둘로 나눈다 — 왼쪽은 지금의 [보내기], 오른쪽 ▾(44 — 보드는 40, 밀도 예외를 새로 만들지 않는다)는 메뉴 「예약 발송…」(시계 아이콘). 보내기 단축키는 없다(쓰기 화면에 Ctrl+Enter 묶음이 없다 — 2026-10-30 확인). 보내는 중 · 저장 중 · 첨부를 올리는 중에는 두 단추를 함께 끈다(지금의 [보내기]와 같은 규칙).
  • 예약 창(모달 400): 제목 "예약 발송". 고르기 넷 — 「내일 오전」 9:00 · 「내일 오후」 1:00 · 「다음 주 월요일」 9:00 · 「직접 고르기」(날짜 칸 + 30분 단위 시각) — 줄마다 오른쪽에 날짜 · 요일 · 시각. 오늘 오후가 남았으면 「오늘 오후」 6:00 이 첫 줄. 안내 "이 PC의 시간(한국 표준시)으로 보내고, 7일 안에서 고를 수 있습니다." [예약] [취소]. 칸 아래 오류: "5분 뒤보다 이른 시각은 고를 수 없습니다. 더 늦은 시각을 골라 주세요." · "7일 안의 시각만 고를 수 있습니다." 받는 사람 확인은 지금처럼 보내는 순간(없는 주소면 받는 사람 칸의 띠).
  • 보낸 뒤 알림(오른쪽 아래): "메일을 보냈습니다." + [보내기 취소] + 아래 남은 시간 막대(설정한 초 — 마우스를 올려도 멈추지 않는다) → 시간이 지나면 저절로 닫힌다. 일부가 받지 못하면 빨간 알림 "보냈지만 1명은 받지 못합니다. 주소 (까닭)" + [보내기 취소](주소를 고쳐 다시 보낼 수 있게). [보내기 취소] → "보내기를 취소했습니다. 쓰던 메일로 돌아왔습니다." + 그 메일로 쓰기 화면(받는 사람 · 제목 · 본문 · 첨부 그대로). 늦게 누르면 "이미 보낸 메일이라 취소할 수 없습니다." 예약 → "10월 30일 (금) 오전 9:00에 보냅니다." + [예약됨 보기]. 로그아웃 · 다른 계정 로그인 때 알림은 닫는다 — 메일은 그대로 보내진다.
  • 「예약됨」: 메일함 패널의 보낸편지함 아래(예약한 메일이 있을 때만, 시계 아이콘, 오른쪽에 통 수). 목록은 받는 사람 · 제목 · 보낼 시각(청록 글 — "10월 30일 (금) 오전 9:00") 차례, 고르는 칸 · 여러 통 처리는 없다. 보낸 메일은 저절로 빠지고 보낸편지함에 남는다.
  • 예약한 메일을 열면: 머리 아래 청록 안내 줄 "10월 30일 (금) 오전 9:00에 보낼 메일입니다." + [고치기](예약을 취소하고 쓰기 화면으로) [예약 취소](보내지 않고 임시보관함으로 — 알림 "예약을 취소하고 임시보관함으로 옮겼습니다." + [고치기]). 답장 · 전달 · 스팸 신고 단추는 없다. [삭제]는 예약을 취소한 뒤 휴지통으로(확인 창).
  • 보낸편지함: 아직 보내지 않은 메일은 날짜 칸이 "예약 · 10월 30일 오전 9:00"(청록) — 보내기 취소를 기다리는 동안은 "보내는 중".

설정 · 보내기 · 읽기 · 차단한 주소 (SettingsSendRead)

  • 메뉴: 메일 묶음에 「보내기 · 읽기」(필터 아래) · 「차단한 주소」를 더한다 — 서명 · 부재중 자동 응답 · 필터 · 보내기 · 읽기 · 차단한 주소 · 알림.
  • 보내기 · 읽기: 고르는 대로 저장한다(저장 단추 없음 — 머리 오른쪽에 "저장했습니다"). 서버에 저장되어 어느 PC 에서나 같다.
    • 「보내기 취소」 — 눌림 전환(36 — .segmented) 끔 · 5초 · 10초(처음) · 20초 · 30초 + "[보내기]를 누른 뒤 이 시간 동안 [보내기 취소]로 되돌릴 수 있습니다. 그사이 창을 닫아도 시간이 지나면 메일은 그대로 보내집니다."
    • 「외부 이미지」 — 라디오 셋(늘 묻기 · 주소록과 사내 직원의 메일은 늘 보기 · 늘 보기, 줄마다 아래 설명) + 회색 상자 "외부 이미지는 회사 쪽에서 대신 받아 오기 때문에 보낸 사람은 이 PC의 주소를 알 수 없습니다. 다만 이미지를 불러오면 보낸 사람이 내가 메일을 열어 본 것을 알 수 있습니다."
  • 차단한 주소: 머리 "차단한 주소" + "3 / 500" + 설명 "차단한 주소에서 온 메일은 스팸메일함으로 옮겨지고, 부재중 자동 응답도 보내지 않습니다. @spam.example 처럼 @로 시작해 넣으면 그 도메인에서 오는 메일을 모두 막습니다."
    • 더하기 줄: 칸 "차단할 주소"(placeholder "kim@example.com 또는 @example.com") + [차단]. 칸 아래 오류: "메일 주소나 @도메인 형식으로 써 주세요." · "이미 차단한 주소입니다." · "내 주소와 사내 도메인은 차단할 수 없습니다." · "gmail.com처럼 여러 사람이 쓰는 메일 서비스는 도메인 전체를 막을 수 없습니다. 주소를 하나씩 넣어 막아 주세요." · "500개까지 차단할 수 있습니다."
    • 목록: 줄마다 주소(굵게) + 아래 작은 글 "10월 29일 차단"(도메인은 "도메인 전체 · …") + 차단 풀기. 누르면 바로 풀고 알림 "‘주소’ 차단을 풀었습니다." + [되돌리기]. 이미 받은 메일은 옮기지 않는다. 비었으면 "차단한 주소가 없습니다. 메일을 읽다가 [⋯] → [보낸 사람 차단]으로도 막을 수 있습니다."

11단계 — 대용량 첨부 (2026-10-30 확정 — ComposeLargeFiles · LargeFileBlock · MailReadLargeFiles · SharePage · SettingsLargeFiles)

메일에 담지 못하는 큰 파일을 링크로 보낸다. 파일은 회사 서버에 조각으로 올리고(끊겨도 이어서), 보낼 때 서버가 본문에 내려받기 안내(덩이)를 넣는다. 받는 사람은 로그인 없이 내려받기 화면 files.arasystem.it.kr/s/{토큰} 에서 받는다. 보관은 보낸 때(예약 발송은 보낼 시각)부터 30일째 날 23:59:59(한국 시간)까지 — 받는 사람 모두 같은 날이다. 한도는 파일당 2GB · 메일 한 통에 링크 파일 10개 · 직원마다 동시 보관 10GB(올리는 중 · 임시 저장 포함) — 모두 운영 설정이다. 제품 · 기술 결정은 2026-10-30 확정본과 같은 날 정한 보완([링크 복사]는 읽기 화면에서만 · 보내지 않은 파일 지우기 · 전달은 인용 속 링크로 · 첨부처럼 올리는 규칙 — 아래 각 절)을 따른다. 데이터 · 규칙은 계약 4절(docs/mail/api-contract.md "대용량 첨부 — 11단계")이 정하고, 아래 문구가 새 에러 코드(FILE_003~FILE_008 · MAIL_016 — 계약 4절 "에러 코드")의 사전 문구 초안이다. 새 아이콘: 멈춤(pause) · 이어 올리기(play) · 경고 삼각형(alert-triangle). 새 토큰은 없다 — 밀도 예외도 있는 값(40 · 36)을 다시 쓴다.

쓰기 화면 — 첨부 칸 (ComposeLargeFiles)

  • 링크로 바뀌는 때: 파일 하나가 20MB 를 넘을 때, 메일에 담은 파일의 합계를 25MB 넘게 만들 때(고른 차례대로 그 파일부터), 메일에 담지 못하는 형식(mp4 · mov · dwg · dxf · psd · ai · 7z)일 때 저절로 링크로 보낸다. 묻는 창은 없다 — 합계 때문에 바뀌면 칸 아래 ⓘ 한 줄 "‘회의자료_전체.pptx’(9.4 MB)는 메일에 담으면 합계가 25MB를 넘어 링크로 보냅니다."(role=status). 받는 사람이 사내뿐이어도 같은 규칙이다. 메일에 담은 파일도 줄의 링크 단추(40 · 「링크로 보내기」 — 마우스를 올리면 같은 글)로 바꿀 수 있다 — 이 쓰기 화면에서 고른 파일에만 있다(전달 원본 · 저장본에서 이어 온 첨부에는 없다 — 처음 판). 링크 → 메일에 담기 단추는 없다(빼고 다시 고르면 한도 안에서 담긴다 — 아래 "처음 판에서 미룬 것").

  • 안내문: "파일당 최대 20MB · 합계 25MB · 최대 20개 · 더 큰 파일은 파일당 2GB, 10개까지 링크로 보냅니다".

  • 두 묶음: 링크 파일이 있으면 칸을 「메일에 담는 파일 N개」(+ "합계 18.1 MB / 25 MB") · 「링크로 보내는 파일 N개」(+ "받는 사람은 보낸 날부터 30일 동안 내려받을 수 있습니다. 오늘 보내면 11월 29일(일)까지입니다.")로 나눈다(12.5 굵게 + 12 회색). 일반 첨부만 있으면 지금처럼 묶음 이름 없이 — 링크 파일만 있어도 「링크로 보내는 파일 N개」 머리는 둔다(기간 안내 · 보관 경고의 자리, 보드대로). 보관 사용량이 80%를 넘으면 링크 묶음 머리 오른쪽에 "보관 8.4 / 10 GB"(갈색 굵게).

  • 링크 파일 줄: 아이콘이 링크(청록 #0A5F67), 두 줄 — 이름 · 단추(40) / 크기 · 상태(12px, 아이콘 선만큼 26px 들여), 올리는 동안 3px 막대. ✕ 는 「빼기」 — 올리던 것을 멈추고 서버의 조각도 지운다.

    상태 둘째 줄 단추 · 모양
    올리는 중 "올리는 중 · 420 MB / 1.2 GB" + 오른쪽 "약 3분 남음"(처음 몇 초는 "시간 계산 중") [멈춤] ✕ · 청록 막대
    멈춤 "멈춤 · 420 MB / 1.2 GB 올림" [이어 올리기] ✕ · 회색 막대 — 그 파일만 멈춘다
    잠깐 끊김 "연결이 끊겨 다시 잇는 중… · 420 MB / 1.2 GB 올림"(갈색) 누를 것 없음 — 2 · 5 · 15 · 30초 간격으로 저절로, 받은 조각은 그대로
    오래 끊김 "올리지 못하고 멈췄습니다. 지금까지 올린 420 MB는 남아 있습니다."(빨강) 빨간 줄 · 다시 시도 ✕ — 저절로 다시 잇기를 1분 넘게 못 했을 때
    다 올림 · 검사 중 "612 MB · 보안 검사 중이지만 보낼 수 있습니다" ✕
    검사 끝 깨끗하면 크기만 "1.2 GB" · 못 했으면 "860 MB · 암호가 걸린 압축 파일처럼 검사하지 못한 파일"(까닭은 응답에 없다 — 암호 압축 · 한도 밖 · 검사기 없음을 함께 쓴다) ✕
    위험 "위험한 내용이 발견되어 보낼 수 없습니다. 이 파일을 빼 주세요."(경고 삼각형) 빨간 줄 · ✕
  • 올리기 전 검사(지금 첨부 사전 검사와 같은 결 — 넘으면 올리지 않고 빨간 줄, 그 파일만 빠지고 보내기는 막지 않는다): "파일 하나는 2GB까지 보낼 수 있습니다." · "링크로 보내는 파일은 메일 한 통에 10개까지입니다." · "대용량 첨부는 10GB까지 보관할 수 있어 더 올릴 수 없습니다. 보내지 않은 파일을 지우거나 링크를 끊으면 바로 공간이 생깁니다. [대용량 첨부 보기]" · "지금은 서버에 공간이 모자라 올릴 수 없습니다. 잠시 후 다시 시도해 주세요." · 형식은 FILE_001 사전 문구. 링크로 보낼 수 있는 형식 = 일반 첨부 목록 + mp4 · mov · dwg · dxf · psd · ai · 7z. 실행 파일(exe · msi · bat · cmd · js · vbs · scr 등)과 디스크 이미지(iso · img · vhd)는 막는다. 파일 고르기 창은 두 목록을 합친 형식만.

  • 다시 열 때: 브라우저는 고른 파일을 스스로 다시 읽지 못한다. 새로고침 · 창을 닫은 뒤 저장본을 열면 노란 줄 "올리다 멈춤 · 420 MB / 1.2 GB 올림 · 같은 파일을 다시 고르면 이어서 올립니다" + 파일 고르기 ✕. 다른 파일을 고르면 그 줄 안에 "고른 파일의 이름이나 크기가 멈춘 파일과 다릅니다. 같은 파일을 골라 주세요."(이름 · 크기 · 고친 시각으로 맞춘다 — 서버는 마지막 조각 뒤 24시간 기다린다). 다 올린 파일은 저장본에 붙어 "1.2 GB · 이 임시 저장본에 11월 29일까지 남습니다"(올린 날부터 30일), 지나면 빨간 줄 "기간이 지나 지워진 파일입니다. 빼고 다시 올려 주세요." 설정 › 대용량 첨부에서 지웠으면 같은 모양에 글만 "[설정] → [대용량 첨부]에서 지운 파일입니다. 빼고 다시 올려 주세요."

  • 다 올릴 때까지 보내기를 막는다: [보내기]와 ▾ 를 disabled 가 아니라 aria-disabled 로 끄고(초점은 받는다) [임시 저장] 오른쪽에 까닭 글(ⓘ 12.5 — 두 단추의 aria-describedby). 누르면 보내지 않고 까닭을 읽어 준다(role=status — 보내기 단축키는 앱에 없다). 「예약 발송…」도 같다. 임시 저장은 그대로 된다. 까닭 글 — 올리는 중 · 다시 잇는 중 "파일 2개를 올리는 중입니다. 다 올린 뒤에 보낼 수 있습니다." · 멈춤 · 오래 끊김 · 다시 열어 멈춘 파일 "다 올리지 못한 파일이 있습니다. 이어 올리거나 빼 주세요." · 위험 · 지운 파일(기간 지남 · 설정에서 지움) "보낼 수 없는 파일이 있습니다. 빨간 줄의 파일을 빼 주세요."

  • 나가기 확인: 올리는 중이면 "올리는 중인 파일이 있습니다" / "이 화면을 나가면 올리기가 멈춥니다. 임시보관함에서 이 메일을 다시 열고 같은 파일을 고르면 이어서 올립니다." [계속 쓰기](처음 초점 · Esc) [나가기]. 떠나기 전 임시 저장은 지금처럼 먼저 하고, 올리는 중인 파일은 저장본에 "올리다 멈춘 파일"로 남긴다. 다 올린 뒤에는 이 확인이 없다.

본문 덩이 — 바깥 메일 프로그램에서 (LargeFileBlock)

  • 자리: 내 글 · 서명 아래, 인용 위(쓰기 화면이 알려 준 경계). 인용이 없으면 맨 끝. 서버가 정화 · 서명 그림 · 중복 확인을 마친 뒤 넣는다 — 쓰기 화면의 편집기에는 보이지 않는다.
  • 내용(위에서부터): "대용량 첨부 2개 (합계 1.8 GB)"(14 굵게) → "한국 시간으로 2026년 11월 29일(일)까지 내려받을 수 있습니다." → 파일마다 이름 · 크기(회색, 오른쪽) → "내려받기 https://files.arasystem.it.kr/s/…"(주소를 글자 그대로 보이는 링크 — 단축 주소 · 단추 모양 없음. 파일 이름은 링크가 아니다 — 메일 한 통에 링크 하나) → "내려받을 때 로그인이나 비밀번호를 묻지 않습니다."(12 회색) → "Large files: download by Nov 29, 2026 (KST). No login or password needed."(12 회색). (2026-11-05 날짜 줄 · 영어 줄의 말투를 고쳤다 — 그 전에 붙잡아 둔 발송을 보내기 취소로 되돌려도 텍스트 덩이를 떼도록 서버가 예전 글도 함께 찾는다.)
  • 모양: 테두리 1px #D0D5DD 표 + 글자, 안 여백 12 · 16, 맑은 고딕 14 · 13 · 12. 보내는 서식의 허용 목록 안에서만(table · td colspan · a, 테두리 · 여백 · 색 · 크기 · 굵기 · 정렬 · 줄바꿈 막기) — 둥근 모서리 · 그림 · 아이콘 · 뱃지 없음. 폭은 내용에 맞춘다(주소 줄이 가장 길다 — 좁은 화면에서는 받는 프로그램이 줄을 끊는다).
  • 텍스트 대체 본문: 같은 자리에 대시 줄로 가른 같은 내용 — 파일은 "- 이름 (크기)", 주소는 "내려받기: 주소".
  • 보이지 않는 표시 <!--ara-large-files:v1--> … <!--/ara-large-files--> — 보내는 정화가 주석을 지우므로 사용자가 흉내 낼 수 없다. 보내기 취소로 되돌린 메일은 덩이를 떼고 링크 파일로 돌려준다. 나중에 링크를 끊어도 본문의 날짜 · 주소는 그대로다(누르면 내려받기 화면이 "공유 멈춤").
  • 답장 · 전달: 링크 파일을 첨부 칸에 다시 싣지 않는다 — 인용 속 덩이는 표째 글(링크)로 그대로 가고 링크는 처음 기간까지 열린다. 인용으로 옮기면 표시가 지워진다. 전달 쓰기 화면은 칸 아래 한 줄로만 알린다(아래 "읽기 화면 › 전달 쓰기 화면"). 바깥 사람의 답장에 인용된 덩이도 우리 웹메일에서 본문 그대로다 — 첨부 칸으로 올리지 않고 접지도 않는다.

읽기 화면 (MailReadLargeFiles)

  • 첨부처럼 올리는 규칙: 주소가 우리 내려받기 주소이고 서버에 있는 링크이며, 링크 주인이 그 메일의 보낸 사람과 같을 때만 첨부 칸으로 올린다 — 바깥 사람 답장의 인용 속 링크 · 전달된 원래 링크는 본문 글이다.
  • 사내 받는 사람: 링크 파일을 첨부 칸에 일반 첨부와 함께 보인다 — 이름 아래 "1.2 GB · 11월 29일까지". 아이콘 · 다운로드는 일반 첨부와 같고, 내려받기 화면을 거치지 않고 바로 받는다. 「첨부 파일 N개」는 둘을 합한 수.
  • 본문의 덩이는 접는다: 그 자리에 접기 줄(높이 40 · 12.5 · 바탕 #F6F7F9) "본문의 대용량 첨부 안내" + "파일은 아래 첨부 칸에 있어 접어 두었습니다" + "펼치기". 본문 틀 안에서 스크립트 없이 열고 닫는다. 메일을 다시 열면 접혀 있고, 인쇄 화면은 펼친 채 인쇄한다.
  • 보낸 사람 자신: 파일마다 "1.2 GB · 3회 받음 · 11월 29일까지"(받지 않았으면 "아직 받지 않음"), 첨부 칸 아래 한 줄 "링크로 보낸 파일 2개는 받는 사람 모두 같은 링크로 받습니다. 끊으면 둘 다 받을 수 없습니다." + [링크 복사] [링크 끊기](36 — 차단한 주소의 [차단 풀기]와 같은 밀도 예외, [링크 끊기]는 빨간 글 테두리 단추). [링크 복사]는 이 메일 본문의 덩이에서 주소를 읽어 복사한다 — "링크를 복사했습니다."(못 하면 "링크를 복사하지 못했습니다."). 설정 목록에는 없다(서버는 링크를 해시로만 안다). 받은 수는 처음부터 받기 시작한 수뿐(화면 열기 · 이어 받기 · 내가 받은 것은 빼고)이고, 누가 받았는지는 남기지 않는다.
  • 링크 끊기 확인(alertdialog 400): "대용량 첨부 링크를 끊을까요?" / "받는 사람 모두 이 메일의 파일 2개(설계도_v3.dwg, 현장영상_1029.mp4)를 더는 내려받을 수 없습니다. 이미 받은 파일은 되돌릴 수 없고, 끊은 링크는 다시 열 수 없습니다." 링크 끊기 [취소](처음 초점 · Esc). 끊으면 "링크를 끊었습니다. 받는 사람은 더 이상 내려받을 수 없습니다." — 되돌리기는 없다. 쓰던 보관 공간은 바로 비워진다.
  • 기간 지남 · 공유 멈춤: 흐리게(이름 #667085 · 아이콘 #98A2B3 — 투명도가 아니라 글자색) + "받을 수 있는 기간이 11월 29일에 끝났습니다" · "보낸 사람이 링크를 끊어 받을 수 없습니다", 내려받기 단추 없음. 보낸 사람 자신에게는 받은 수가 남고 끊은 날이 보인다 — "1.2 GB · 3회 받음 · 10월 31일에 링크를 끊었습니다"(한국 날짜 · 응답의 revokedAt, 없으면 "링크를 끊었습니다"). 끝난 뒤 90일이 지나면 첨부 칸에서 빠지고 덩이는 그냥 본문이 된다.
  • 보안 검사: 검사 중이면 [다운로드]를 aria-disabled(title · 까닭 연결) + "보안 검사가 끝나면 받을 수 있습니다". 위험은 빨간 줄 "보안 검사에서 위험한 내용이 나와 받을 수 없습니다"(단추 없음). 검사하지 못한 파일은 받을 수 있고 글로만 알린다.
  • 전달 쓰기 화면: 원래 메일의 덩이는 인용 속 글(링크)로 그대로 간다 — 첨부 칸에 다시 싣지 않고 칸 아래 ⓘ 한 줄로만 알린다 "원래 메일의 대용량 첨부 2개는 인용 속 링크로 함께 갑니다. 11월 29일까지 받을 수 있습니다.". 공유가 멈췄거나 기간이 지난 링크면 갈색 한 줄 "인용 속 ‘도면_초안.dwg’ 링크는 공유가 멈춰 받을 수 없습니다."("… 기간이 지나 받을 수 없습니다.", 파일이 여럿이면 "‘설계도_v3.dwg’ 외 1개") — 보내기는 막지 않는다. 전달하는 사람이 새 큰 파일을 더하면 그것은 이 메일의 새 링크다(첨부 칸의 「링크로 보내는 파일」 — 전달하는 사람의 보관 한도에 잡힌다). 받는 쪽에서 인용 속 원래 링크는 본문 글이고, 내려받기 화면의 보낸 사람 · 끊을 수 있는 사람은 처음 보낸 사람(링크 주인)이다. 답장도 같다.
  • 목록의 클립 아이콘은 지금처럼 메일에 담긴 첨부만 본다 — 링크 파일만 있는 메일은 클립이 없다(아래 "처음 판에서 미룬 것").

내려받기 화면 (SharePage)

  • 독립 화면: 앱 레일 · 상단 바 · 로그인 메뉴가 없다. 머리 바(#0B3640, 60)에 흰 ARA SYSTEM 로고(88) + "파일 내려받기", 바탕 #F6F7F9 에 가운데 열 640. 탭 제목 "김아라 님이 보낸 파일 · ARA SYSTEM".
  • 정상: "김아라 님이 보낸 파일 2개 (1.8 GB)"(20) → "보낸 사람 김아라 ara.kim@arasystem.it.kr · ARA SYSTEM"(로그인한 계정의 값 — 보낸 사람이 바꿀 수 없다) → "한국 시간으로 2026년 11월 29일(일)까지 내려받을 수 있습니다. 30일 남았습니다."(날짜만 굵게) → 파일 목록(줄마다 아이콘 36 · 이름 · "1.2 GB · 보안 검사 결과 이상 없음" · [내려받기](채운 단추 44)) → "내려받다 끊기면 브라우저의 다운로드 목록에서 다시 시도해 주세요. 받은 곳부터 이어서 받습니다." → 청록 상자 "이 화면은 로그인이나 비밀번호를 묻지 않습니다. 묻는 화면이 나오면 닫고 보낸 사람에게 확인해 주세요." → "받을 이유가 없는 파일이면 postmaster@arasystem.it.kr(으)로 알려 주세요."(조사는 주소의 끝 글자로 고르고 영문이면 "(으)로" — 주소는 운영 설정이라 바뀔 수 있다, 2026-11-07)(운영 설정 — 처음 값 postmaster@메일 도메인) → 영어 한 줄.
  • 메일 제목 · 본문 · 받는 사람, 내려받은 수 · 남은 수는 보이지 않는다. [내려받기]는 형식과 상관없이 늘 저장이고, 누르기 직전에 상태를 한 번 더 묻는다. noindex · no-referrer.
  • 상태(모든 상태에서 머리 · 영어 한 줄은 그대로. 신고 주소 줄은 링크 정보를 받은 상태에서만 — 없는 링크 · 요청이 너무 많음 · 오류 응답에는 그 값이 없다):
    • 검사 중 — 모두 검사 중이면 제목 "보안 검사 중입니다" / "파일을 검사하고 있습니다. 몇 분 뒤 이 화면을 다시 열어 주세요." + [다시 확인]. 일부면 그 줄만 "보안 검사 중" + [내려받기] aria-disabled.
    • 위험 — "내려받을 수 없는 파일입니다" / "보안 검사에서 위험한 내용이 발견되어 막았습니다. 보낸 사람에게 알렸습니다." 그 줄은 빨강 "위험한 내용이 있어 막았습니다", 단추 없음.
    • 검사하지 못함 — 제목은 그대로, 줄에 갈색 "암호가 걸린 압축 파일처럼 안을 볼 수 없어 검사하지 못한 파일입니다. 보낸 사람을 아는 경우에만 열어 주세요." + [내려받기](받을 수 있다).
    • 횟수 초과(파일마다 운영 설정 — 처음 값 500번, 넘은 파일만) — 그 줄에 "내려받을 수 있는 횟수를 넘었습니다. 보낸 사람에게 다시 보내 달라고 요청해 주세요."(숫자는 쓰지 않는다 — 한도는 운영 설정이고 응답에 없다)
    • 만료 — "내려받을 수 있는 기간이 지났습니다" / "2026년 11월 29일까지 받을 수 있었던 파일입니다. 파일은 지워져 다시 받을 수 없습니다. 필요하면 보낸 사람(김아라, ara.kim@arasystem.it.kr)에게 다시 보내 달라고 요청해 주세요."
    • 회수 — "보낸 사람이 공유를 멈췄습니다" / "필요하면 보낸 사람(김아라, ara.kim@arasystem.it.kr)에게 문의해 주세요." 파일 이름도 보이지 않는다.
    • 없는 링크(모르는 주소 · 끝난 지 90일이 지나 기록을 지운 링크) — "찾을 수 없는 링크입니다" / "메일에 있던 주소를 끝까지 다 복사했는지 확인해 주세요." 보낸 사람도 보이지 않는다.
    • 잠시 막힘(같은 IP 의 요청이 너무 많음) — "요청이 너무 많습니다" / "잠시 뒤 다시 시도해 주세요." + [다시 시도].
  • 좁은 폭(휴대폰 360): 머리 52 · 로고 76, 보낸 사람 주소는 이름 아래 줄로, 줄마다 [내려받기]가 이름 아래 전체 폭(44).

설정 › 메일 › 대용량 첨부 (SettingsLargeFiles)

  • 메뉴: 메일 묶음의 차단한 주소 아래 「대용량 첨부」 — 서명 · 부재중 자동 응답 · 필터 · 보내기 · 읽기 · 차단한 주소 · 대용량 첨부 · 알림.
  • 머리 "대용량 첨부" + "메일에 링크로 보낸 큰 파일입니다. 받는 사람은 보낸 날부터 30일 동안 내려받을 수 있고, 링크를 끊으면 그때부터 받을 수 없습니다."
  • 사용량 상자: "보관 사용량 6.2 / 10 GB"(메일함 사용량과 같은 표기) + 4px 막대(90% 를 넘으면 빨강 — 메일함 사용량과 같다) + "보낸 링크 4.3 GB, 임시 저장했거나 올리다 멈춘 파일 1.9 GB입니다. 기간이 지나거나, 링크를 끊거나, 보내지 않은 파일을 지우면 바로 줄어듭니다."
  • 보내지 않은 파일(사용량 상자 바로 아래 — 공간을 바로 비울 수 있는 곳이라 보낸 링크보다 위): 「보내지 않은 파일 3개」 + "1.9 GB · 임시 저장한 메일에 실렸거나 올리다 멈춘 파일이라 지우면 바로 공간이 생깁니다". 없으면 묶음째 보이지 않는다.
    • 줄(52): 아이콘 · 이름 / (올리다 멈춘 파일은 앞에 갈색 "올리다 멈춤") · 크기(멈춘 파일은 "1.2 GB" / "420 MB 올림") · 실린 저장본의 제목 · 링크는 보이지 않는다(2026-10-30 — 저장본 id 는 저장할 때마다 바뀌고 엔진 밖에서도 지워져 서버가 따라가기 무겁다, 계약 4절 LargeFile 에 없음) · 남는 기한 "11월 29일까지 남음" / "올린 뒤 30일"(올리다 멈춘 파일은 이어 올리기 규칙대로 "내일 오후 2:41까지 남음" / "마지막으로 올린 뒤 24시간") · [지우기](36, 빨간 글).
    • [지우기] → 확인 창(alertdialog 400) "보내지 않은 파일을 지울까요?" / "‘시공도면_v4.dwg’(620 MB) 파일을 지웁니다. 이 파일을 넣어 둔 임시 저장본이 있으면 그 메일에서 파일을 빼고 다시 올려야 합니다. 지운 파일은 되돌릴 수 없습니다." 지우기 [취소](처음 초점 · Esc) → "‘시공도면_v4.dwg’ 파일을 지웠습니다." — 줄이 사라지고 사용량이 바로 준다. 그 저장본을 쓰기 화면에서 열면 첨부 칸에 빨간 줄 "[설정] → [대용량 첨부]에서 지운 파일입니다. 빼고 다시 올려 주세요."(쓰기 화면의 "기간이 지나 지워진 파일"과 같은 모양 · 다른 글).
  • 보낸 링크: 「보낸 링크 6개」 + "메일마다 하나씩, 최근에 보낸 순서". 메일(링크)마다 한 덩이 — 링크가 메일 한 통에 하나라 [링크 끊기]는 덩이에 한 번이다(파일 줄마다 두면 같은 메일의 다른 파일까지 끊긴다).
    • 머리: 메일 제목(청록 링크 → 보낸편지함의 그 메일, 메일을 지웠으면 글자만) / "10월 30일 (금) 보냄 · 파일 2개 · 1.8 GB" · 오른쪽 "11월 29일까지" / "30일 남음"(3일 이하면 갈색 굵게) · 링크 끊기.
    • 파일 줄(44 · 바탕 #FBFBFC): 아이콘 · 이름 · 크기 · "3회 받음" / "마지막 오늘 오후 2:41"(받지 않았으면 "아직 받지 않음") · 상태.
    • 상태 글: 사용 중 · 검사 중(회색) · 위험(빨강 굵게 + "내려받기를 막음", 줄 바탕 #FEF3F2) · 기간 지남 · 공유 멈춤. 검사하지 못한 파일은 상태 아래 갈색 "검사하지 못함". 뱃지 없이 글자만.
    • 끝난 링크는 덩이째 흐리게 — 머리 오른쪽 "공유 멈춤 / 10월 22일에 끊음" · "기간 지남 / 10월 25일까지였습니다", 단추 없음. 90일 뒤 목록에서 빠진다.
    • 최근에 보낸 차례 · 20개씩 [더 보기].
  • [링크 복사]는 두지 않는다 — 서버는 링크를 해시로만 알아 원래 주소를 모른다. 주소는 보낸 메일의 읽기 화면(보낸 사람 자신)에서 복사한다.
  • [링크 끊기] → 읽기 화면과 같은 확인 창 · 같은 알림(창 글에 메일 제목). 끊는 동안 그 덩이의 [링크 끊기]를 막고, 끝나면 초점을 덩이 머리로.
  • 빈 상태: 사용량 "0 / 10 GB" + "링크로 보낸 파일이 없습니다" / "메일 쓰기에서 20MB가 넘는 파일을 고르면 링크로 보냅니다. 받는 사람은 보낸 날부터 30일 동안 내려받을 수 있습니다." 보내지 않은 파일이 있으면 그 묶음은 위에 그대로 있다. 목록을 받지 못하면 지금의 목록 오류와 같은 모양.
  • 위험이 나오면 빨간 알림 "올린 대용량 파일 ‘견적_요청서.zip’에서 위험한 내용이 발견되어 막았습니다." + [대용량 첨부 보기] — 웹메일을 열어 둔 동안 실시간 알림(large-file)으로 바로 띄운다. 그때 열려 있지 않았으면 알림은 없고 목록 · 첨부 칸의 상태(위험)로만 안다 — 이벤트에 보냈는지가 없어 "보낸 파일 · 내려받기를 막았습니다"로 나누지 않는다(계약 4절 "악성 검사"의 문구). 같은 메일의 다른 파일은 그대로 받을 수 있다.

처음 판에서 미룬 것

  • 링크 → 메일에 담기 되돌리기 단추(제품 조사 3.1) — 빼고 다시 고르면 한도 안에서 메일에 담긴다.
  • 링크 파일만 있는 메일의 목록 클립 아이콘 — 목록의 클립은 지금처럼 메일에 담긴 첨부만 본다.
  • 전달 원본 · 저장본에서 이어 온 첨부의 「링크로 보내기」 — 이 쓰기 화면에서 고른 파일에만.
  • 확정본이 미룬 것: 기간 고르기 · 연장, 다시 보내기, 받는 사람 확인 코드 · 비밀번호, 사내만, 받는 사람별 링크 · 기록, 처음 받음 · 만료 전 알림, 미리 보기, zip 모두 받기, 폴더 올리기, 관리자 화면 · 퇴사자 처리, 보낸 메일을 지울 때 파일도, 모바일 앱, 파일 저장소 연동.

12단계 — 계정 보안 (2026-10-31 확정 — LoginTwoFactor · SettingsTwoFactor · SettingsAppPasswords · SettingsLoginHistory)

2단계 인증(인증 앱 TOTP) · 앱 비밀번호 · 로그인 기록. 직원이 스스로 켠다 — 관리자가 전원에게 강제하는 것은 13단계다. 데이터 · 규칙은 계약 5절(docs/mail/api-contract.md "계정 보안 — 12단계")이 정하고, 새 에러 코드(AUTH_006~AUTH_009 — 계약 5절 "에러 코드")의 사전 문구 초안도 거기 있다. 설정 메뉴의 계정 묶음: 비밀번호 · 2단계 인증 · 앱 비밀번호 · 로그인 기록(새 항목 아이콘: 방패(체크) · 열쇠 · 시계(되감기)). 새 토큰은 없다 — 키 [복사] · 표의 [지우기] · [로그아웃] · 안내 상자 안 단추는 차단한 주소의 [차단 풀기]와 같은 밀도 예외 36(--size-notice-action)이다. 각 보드 위쪽은 실제 크기 화면(설정 셸은 SettingsAccount 와 같다 — 메뉴 208 · 콘텐츠 열 800), 아래는 상태 카드다.

로그인 — 코드 단계 (LoginTwoFactor)

  • 누가 오나: 비밀번호가 맞고 2단계 인증을 켠 직원만. 켜지 않은 직원은 지금처럼 바로 메일함으로 간다. 비밀번호가 틀리면 지금처럼 「메일 주소나 비밀번호가 맞지 않습니다. 다시 확인해 주세요.」 — 2단계를 켰는지는 비밀번호가 맞은 뒤에야 드러난다.
  • 틀은 로그인 화면과 같다. 위에서부터:
    • 제목 "2단계 인증" / "비밀번호를 확인했습니다. 휴대폰 인증 앱에 표시된 코드를 넣어 주세요."
    • 주소 + [다른 계정으로 로그인] — 처음 화면으로(주소 칸은 비움), 확인한 비밀번호는 버린다.
    • 칸 "인증 앱에 표시된 6자리 코드" — 숫자 6자리만, 붙여 넣은 글의 띄어쓰기 · 하이픈은 지운다. inputmode="numeric" · autocomplete="one-time-code".
    • [확인] → "인증 앱을 쓸 수 없나요? 관리자에게 문의해 주세요." → "Outlook이나 휴대폰 메일 앱은 이 코드를 묻지 않습니다. [설정] → [앱 비밀번호]에서 만든 비밀번호로 연결해 주세요."
  • 상태
    • 코드 틀림 — 칸 아래 빨간 글(AUTH_006 사전 문구 "코드가 맞지 않습니다. 휴대폰 설정에서 날짜와 시간을 자동으로 맞추게 해 두었는지 확인해 주세요."). 칸을 비우고 초점은 그 칸, 알림은 없다.
    • 시도 과다 — 위쪽 빨간 상자(로그인 오류와 같은 자리 · 같은 모양)에 REQ_001 사전 문구. 코드 실패는 비밀번호 틀림과 따로 센다(같은 주소 15분 5회 · 하루 20회). 칸 · 단추는 그대로 둔다.
    • 확인 중 — 「확인하는 중…」(로그인 단추와 같은 모양 · aria-busy), 칸은 읽기만. 맞으면 받은편지함으로 — 로그인 전에 가려던 주소가 있으면 그곳으로.
  • 이 화면에 10분 넘게 머물면 비밀번호를 바로 지우고, [확인] 때 처음 화면으로 가 위쪽 상자에 "코드를 넣는 시간이 지났습니다. 다시 로그인해 주세요."를 보인다. 새로고침도 처음 화면이다(비밀번호는 화면 메모리에만 있다).
  • 코드 단계에서 비밀번호가 틀리면(그사이 바뀜) 처음 화면의 위쪽 오류 상자에 보인다 — 지금 로그인 화면과 같은 자리. 6자리가 아니면 보내지 않고 칸 아래에 AUTH_007 사전 문구.
  • 로그인 기록에는 코드까지 맞으면 「2단계 인증으로 성공」, 틀리면 「코드가 틀려 실패」로 남는다.
  • 성공에 쓴 코드는 2분 동안 다시 받지 않는다 — 30초 안에 두 곳(PC · 휴대폰)에서 연달아 로그인하면 두 번째는 「코드가 올바르지 않습니다…」이고, 앱에 다음 코드가 뜰 때 넣는다.

설정 › 계정 › 2단계 인증 (SettingsTwoFactor)

  • 머리 "2단계 인증" + "로그인할 때 비밀번호와 함께 휴대폰 인증 앱의 코드를 확인합니다. 비밀번호가 새어 나가도 휴대폰이 없으면 로그인할 수 없습니다."
  • 상태는 글자로만(뱃지 · 스위치 없음) — 테두리 상자 하나:
    • 「꺼짐」(회색 굵게) / "지금은 로그인할 때 비밀번호만 확인합니다." + [2단계 인증 켜기]
    • 「2026년 10월 31일부터 켜짐」(초록 #067647 굵게 — 켠 때를 모르면 「켜짐」) / "로그인할 때마다 비밀번호와 인증 앱의 코드를 확인합니다." + [2단계 인증 끄기]
  • 꺼져 있을 때 상자 아래:
    • 갈색 경고 "켜면 메일 프로그램은 앱 비밀번호로 다시 연결해야 합니다" / "Outlook, 썬더버드, 휴대폰 메일 앱은 웹메일 비밀번호로 새로 연결할 수 없고, 이미 연결해 둔 프로그램도 다음에 다시 연결할 때 막힙니다. 켜기 전에 앱 비밀번호를 만들어 두면 편합니다."(앱 비밀번호 링크)
    • 「켜는 방법」 1 · 2 · 3(청록 굵은 숫자 — 동그라미 없음): 인증 앱 설치(Google · Microsoft Authenticator 처럼 6자리 코드를 만드는 앱) → [2단계 인증 켜기]를 눌러 QR 찍기 → 코드와 지금 비밀번호를 넣으면 켜진다.
  • 켜져 있을 때 상자 아래: "휴대폰을 바꿀 때는 2단계 인증을 끈 뒤 새 휴대폰으로 다시 켜 주세요. 끌 때 지금 휴대폰의 코드가 필요합니다."(16단계부터 [휴대폰 바꾸기] — 첫 줄은 16단계 절의 글) · "휴대폰을 잃어버렸거나 인증 앱을 지웠으면 관리자에게 문의해 주세요." · "메일 프로그램은 앱 비밀번호로 연결합니다."
  • 켜기 창(모달 620 — wide, 필터 고치기 창과 같은 폭 · 처음 초점은 코드 칸)
    • ① "인증 앱으로 QR 코드를 찍어 주세요" / "인증 앱에서 [+] → [QR 코드 스캔]을 고른 뒤 이 그림을 찍어 주세요." + QR 그림(160 — 실제 otpauth 주소는 오류 정정 M 에서 49칸이라 보드의 128 보다 키웠다)
    • "찍을 수 없으면 이 키를 입력해 주세요" + 32자 키(4자씩 띄움, 고정폭) + [복사](36 — 「키를 복사했습니다.」) + "앱에는 ‘ARA SYSTEM (ara.kim@arasystem.it.kr)’로 저장됩니다."
    • ② "앱에 표시된 6자리 코드" ③ "지금 비밀번호" → [켜기] [취소]
    • 칸 아래 오류(알림 없이, 첫 잘못된 칸에 초점): 코드 AUTH_006 · 지금 비밀번호 AUTH_005("지금 비밀번호가 올바르지 않습니다."). 둘 다 틀렸으면 코드부터 — 서버가 코드를 먼저 본다. 틀린 코드는 지우고 그 칸에 초점을 둔다. QR · 키는 그대로라 다시 찍지 않아도 된다. 비밀번호 틀림은 로그인 실패와 함께, 코드 틀림은 코드 실패로 따로 센다.
    • 켜는 동안 「켜는 중…」 · [취소]. 맞으면 창을 닫고 화면이 「켜짐」이 되며, 위에 안내 상자 "2단계 인증을 켰습니다" / "Outlook, 썬더버드, 휴대폰 메일 앱 같은 메일 프로그램은 다시 연결할 때 앱 비밀번호가 필요합니다." 앱 비밀번호 만들기 — 이 화면을 떠나면 사라진다.
    • 키는 창을 열 때마다 새로 만든다(10분 안에 켠다 — 지나면 AUTH_008 로 처음부터). 켜지 않고 닫으면 그 키는 버린다 — 앱에 먼저 저장한 항목은 지워 달라고 안내한다. QR · 키는 이 창에만 보이고 로그에 남기지 않는다.
    • 켜도 다른 곳의 로그인(웹메일 · ARA SYSTEM 앱)은 끊지 않는다 — 끊으려면 로그인 기록에서.
  • 끄기 창(alertdialog 400 · 처음 초점 「취소」, Esc 도 같다): "2단계 인증을 끌까요?" / "끄면 비밀번호만으로 로그인합니다. 만들어 둔 앱 비밀번호는 그대로 남습니다." + 지금 비밀번호 · 인증 앱 코드 → 끄기 [취소]. 끄면 「2단계 인증을 껐습니다.」 — 화면은 「꺼짐」. 틀리면 켜기 창과 같은 칸 아래 글이다.
  • 비밀번호 화면(설정 › 비밀번호)
    • 2단계 인증을 켠 직원: 지금 양식 맨 아래에 「인증 앱 코드」 칸 + "코드는 30초마다 바뀌니 다른 칸을 다 채운 뒤에 넣어 주세요." 머리 설명은 "웹메일에 로그인하는 비밀번호입니다. 2단계 인증이 켜져 있어 메일 프로그램은 앱 비밀번호로 연결합니다." 다른 탭에서 켜서 AUTH_007 이 오면 2단계 상태를 다시 받아 코드 칸을 보인다.
    • 모든 직원: 단추 아래 "바꾸면 이 브라우저를 빼고 다른 기기와 브라우저에서 모두 로그아웃됩니다."(2026-11-06 — 앱 세션도 끊긴다) "앱 비밀번호도 모두 지워지니, 메일 프로그램은 새 앱 비밀번호로 다시 연결해 주세요."

설정 › 계정 › 앱 비밀번호 (SettingsAppPasswords)

  • 머리 "앱 비밀번호" + "Outlook, 썬더버드, 휴대폰 메일 앱 같은 메일 프로그램에 넣는 비밀번호입니다. 프로그램마다 따로 만들어 두면 기기를 잃어버렸을 때 그 비밀번호만 지울 수 있습니다." 오른쪽 "3 / 5개" + [앱 비밀번호 만들기].
  • 표(구현은 실제 table): 이름 · 만든 날 · 마지막 사용 · [지우기](36, 빨간 글). 최근에 만든 것이 위, 긴 이름은 말줄임. 마지막 사용은 "오늘 오전 9:12 · IMAP · 211.186.95.252" — 쓰지 않았으면 "아직 쓰지 않음"(회색, 오래 쓰지 않은 줄은 지워도 된다는 신호).
  • 메일 프로그램 설정값 상자: 받는 메일(IMAP) mx1.arasystem.it.kr · 포트 993 · SSL/TLS / 보내는 메일(SMTP) mx1.arasystem.it.kr · 포트 465 · SSL/TLS / 사용자 이름 = 메일 주소 전체 / 비밀번호 = "웹메일 비밀번호가 아닌, 여기서 만든 앱 비밀번호". 호스트 · 포트는 화면 상수 하나에 둔다(전 직원 전환 때 다시 본다).
  • 아래 안내: "앱 비밀번호는 메일 프로그램에서 메일을 읽고 보낼 때만 씁니다. 웹메일 로그인에는 쓸 수 없습니다." · "2단계 인증을 켜면 메일 프로그램은 앱 비밀번호로만 연결합니다. 켜지 않아도 만들어 쓸 수 있습니다." · "웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워지니, 메일 프로그램마다 새로 만들어 주세요." · "휴대폰이나 PC를 잃어버렸다면 그 기기의 앱 비밀번호를 바로 지워 주세요."
  • 5개를 모두 만들었을 때: [앱 비밀번호 만들기]는 aria-disabled + 머리 아래 한 줄 "5개까지 만들 수 있습니다. 쓰지 않는 앱 비밀번호를 지운 뒤 만들어 주세요." — 누르면 창을 열지 않고 까닭을 읽어 준다(role=status).
  • 빈 상태: "0 / 5개" + "만든 앱 비밀번호가 없습니다" / "Outlook이나 휴대폰 메일 앱에서 메일을 보려면 앱 비밀번호를 만들어 주세요. 2단계 인증을 켰다면 메일 프로그램은 앱 비밀번호로만 연결할 수 있습니다." 설정값 상자 · 아래 안내는 그대로. 목록을 받지 못하면 지금의 목록 오류와 같은 모양(사전 문구 + [다시 시도]) — 그동안 [만들기]는 막는다(개수를 모르므로).
  • 만들기 창(400 · 처음 초점 이름 칸): 「이름」 + "어느 프로그램이나 기기에서 쓰는지 알아볼 수 있게 지어 주세요. 40자까지 쓸 수 있습니다." → [만들기] [취소]. 칸 아래: "이름을 넣어 주세요." · "같은 이름의 앱 비밀번호가 있습니다."(앞뒤 공백을 지운 값으로 화면이 먼저 본다) · 그사이 다른 창에서 5개를 채웠으면 AUTH_009 사전 문구.
  • 만든 결과 창(620 · 처음 초점 [복사]): "앱 비밀번호를 만들었습니다" / "‘회사 PC 아웃룩’ 앱 비밀번호입니다. 메일 프로그램의 비밀번호 칸에 아래 값을 넣어 주세요."
    • 값은 고정폭으로 4자씩 떨어뜨려 보이지만 띄어쓰기는 없다 — [복사](44 — 창의 주 동작이라 보드대로)는 40자 그대로. "40자입니다. 보기 편하게 4자씩 띄어 놓았지만, 실제로는 띄어쓰기 없이 넣으면 됩니다."
    • 경고 "이 창을 닫으면 다시 볼 수 없습니다. 잊어버리면 지우고 새로 만들어 주세요." + 설정값 요약 + [닫기].
    • 복사 「앱 비밀번호를 복사했습니다.」 / 못 하면 「앱 비밀번호를 복사하지 못했습니다. 값을 직접 골라 복사해 주세요.」
    • [닫기] · Esc 로 닫으면 목록 맨 위에 새 줄이 생기고 값은 화면 메모리에서도 지운다. 복사하지 않고 닫아도 다시 묻지 않는다.
  • 지우기 확인(alertdialog 400 · 처음 초점 「취소」): "‘휴대폰 메일 앱’ 앱 비밀번호를 지울까요?" / "이 비밀번호로 연결한 메일 프로그램은 다음 연결부터 로그인하지 못합니다. 지운 비밀번호는 되돌릴 수 없습니다." 지우기 [취소] → 「‘휴대폰 메일 앱’ 앱 비밀번호를 지웠습니다.」 — 줄이 빠지고 개수가 준다(되돌리기 없음).

설정 › 계정 › 로그인 기록 (SettingsLoginHistory)

  • 머리 "로그인 기록" + "최근 90일 동안의 로그인입니다. 모르는 기록이 있으면 바로 비밀번호를 바꾸고, 2단계 인증이 꺼져 있으면 켜 주세요."(두 링크).
  • 로그인된 기기(2026-11-06 — 앱 세션이 함께 들어가 「로그인된 브라우저」에서 바꿨다): 「로그인된 기기」 + "웹메일 3곳"(앱 세션이 있으면 "웹메일 2곳 · ARA SYSTEM 앱 1곳" — 세션의 client 로 세고 0곳인 쪽은 쓰지 않는다. "앱 N곳"으로 줄이지 않는다 — 앱 비밀번호를 쓰는 "휴대폰 메일 앱"과 헷갈리지 않게) · 오른쪽 다른 곳 모두 로그아웃.
    • 줄마다 PC · 휴대폰 아이콘(16) · 기기(칸 머리 "기기" — 아이콘 칸의 화면 낭독 이름은 "종류") / IP / 마지막 활동 / 로그아웃. 앱 세션은 "ARA SYSTEM 앱 · Android"(운영체제를 모르면 "ARA SYSTEM 앱")이고 아이콘은 늘 휴대폰이다 — 앱인지는 client 로만 가린다(앱 꼴 User-Agent 의 웹 세션은 웹메일로 센다). 맨 위는 지금 이 브라우저 — 단추 대신 「지금 이 브라우저」 글자만. 모르면 "알 수 없는 브라우저".
    • 아래 "메일 프로그램은 이 목록에 없습니다. 연결을 끊으려면 [앱 비밀번호]를 지워 주세요."(링크 — 2026-11-06 앱 세션이 함께 보여 고침, 11-07 문구 다듬기)
    • 줄의 [로그아웃]은 묻지 않고 바로 끊는다 → 「‘Edge · Windows’에서 로그아웃했습니다.」(되돌리기 없음). 끊은 줄은 빠지고, 다른 곳이 없으면 [다른 곳 모두 로그아웃]을 그리지 않는다.
    • [다른 곳 모두 로그아웃] → 확인(alertdialog 400 · 처음 초점 「취소」) "다른 곳에서 모두 로그아웃할까요?" / "이 브라우저를 뺀 2곳(Edge · Windows, ARA SYSTEM 앱 · Android)에서 로그아웃합니다. 그곳에서 다음에 무엇이든 누르면 바로 로그인 화면이 나옵니다." "메일 프로그램은 로그아웃되지 않습니다. 연결을 끊으려면 앱 비밀번호를 지워 주세요." [모두 로그아웃](청록 주 단추 — 데이터를 지우는 일이 아니다) [취소] → 「다른 2곳에서 로그아웃했습니다.」
    • 끊긴 브라우저 · 앱은 다음 요청에서 바로 로그인 화면으로 간다(열린 실시간 알림도 30초 안에) — 계약 5절 "로그인된 기기".
  • 최근 로그인: 「최근 로그인」 + "최근 90일 · 웹메일, ARA SYSTEM 앱, 메일 프로그램" · 오른쪽 전환 [전체 · 실패만](.segmented--outline — role=group + aria-pressed). 표: 시각 · 방식 · IP · 기기 · 결과.
    • 방식: "웹메일" / "ARA SYSTEM 앱" / "메일 프로그램 · IMAP" · "메일 프로그램 · SMTP" + 앱 비밀번호로 들어왔으면 아래 줄 "앱 비밀번호 ‘회사 PC 아웃룩’"(지운 앱 비밀번호도 그 이름 그대로).
    • 기기: 웹메일은 "Chrome · Windows", 앱은 운영체제만 "Android"(방식 칸이 이미 앱 이름이고 기기 칸 120 에서 잘리지 않게 — 모르면 "—"), 메일 프로그램은 "—". 나라 · 지역은 보이지 않는다.
    • 결과: 「성공」 · 「2단계 인증으로 성공」 · 「비밀번호가 틀려 실패」 · 「코드가 틀려 실패」 · 「앱 비밀번호가 필요해 실패」(2단계를 켠 계정에 메일 프로그램이 웹메일 비밀번호로 연결하려 함). 실패는 빨간 굵은 글자만 — 뱃지 없음.
    • 최근 순서 · 20개씩 더 보기. 「실패만」이면 "최근 90일 · 실패 4번", [더 보기]도 실패만 이어서.
    • 빈 상태(실패만): "최근 90일 동안 실패한 로그인이 없습니다" / "[전체]를 누르면 성공한 로그인도 볼 수 있습니다." 전체가 비었으면 "이 기능이 생긴 날부터 기록이 쌓입니다."
  • 목록을 받지 못하면 지금의 목록 오류와 같은 모양(사전 문구 + [다시 시도]).

처음 판에서 미룬 것

복구 코드, 이 브라우저 기억, 문자 · 메일 코드, 보안 키 · 패스키, 관리자가 2단계 인증을 강제하기(13단계), 켜진 채 휴대폰 바꾸기, 앱 비밀번호 이름 바꾸기 · 만료, 새 기기 로그인 알림 메일, 계정 변경 기록(비밀번호 바꾸기 · 2단계 켜기 등), 로그인 기록의 나라 · 지역.

13단계 — 전환 준비 (2026-10-31 확정 — LoginPasswordChange · LoginTwoFactorSetup · SettingsMailClients · MailNoticesCutover)

전 직원 전환 전에 필요한 화면이다 — 관리자가 준 임시 비밀번호를 바꾸는 로그인 단계, 회사가 2단계 인증을 필수로 정했을 때의 켜기 단계와 유예 안내, 메일 프로그램 연결 안내, 받은편지함 알림 메일 · 휴지통 30일 안내 · 로그인 문의 줄. 데이터 · 규칙은 계약 6절(docs/mail/api-contract.md "전환 준비 — 13단계")이 정한다 — 제한된 세션(restriction) · twoFactorDeadline · isRequired · GET /api/support-contact · AUTH_010. 알림 메일과 메일 프로그램 연결의 글은 이 절이 원본이다(화면 상수 · 메일 본문으로 그대로 옮긴다). 설정 메뉴의 계정 묶음: 비밀번호 · 2단계 인증 · 앱 비밀번호 · 메일 프로그램 연결(새 — 아이콘 PC · 휴대폰, lucide monitor-smartphone) · 로그인 기록. 새 토큰은 없다 — 유예 노란 줄 40 · 줄 안 단추 36 은 안내 줄 밀도 예외(--size-notice-bar · --size-notice-action)를 다시 쓴다. 메일 프로그램 연결의 펼침 줄은 52(이름 · 설명 두 줄)라 예외가 아니다. 로그인 틀(브랜드 패널 + 양식 칸 360)과 칸 · 단추 · 위쪽 오류 상자는 Login · LoginTwoFactor 와 같다. 각 보드 위쪽은 실제 크기 화면, 아래는 상태 카드다. 화면이 모르는 제한 이름이 오면(서버가 새 제한을 더했을 때) 로그인 틀에 AUTH_010 사전 문구와 [다른 계정으로 로그인]만 보인다 — 새로 받기 · 다시 보내기를 되풀이하지 않는다.

로그인 — 새 비밀번호 정하기 (LoginPasswordChange)

  • 누가 오나: 운영 도구로 입사 · 비밀번호 재설정을 한 직원이 받은 임시 비밀번호로 로그인했을 때(restriction: passwordChange). 마칠 때까지 메일 · 주소록 · 설정을 열 수 없다(AUTH_010) — 새로고침 · 다른 탭 · 주소를 바꿔 들어와도 이 단계다(화면은 me 의 restriction 을 본다). 2단계 인증을 켠 직원은 비밀번호 → 코드 → 이 단계다(비밀번호만 재설정하면 2단계는 그대로). 2단계 인증이 필수인데 아직 켜지 않았으면 이 단계 다음이 「2단계 인증 켜기」다.
  • 틀(위에서부터):
    • 제목 "새 비밀번호를 정해 주세요" / "관리자가 정한 임시 비밀번호로 로그인했습니다. 메일을 쓰려면 먼저 새 비밀번호를 정해 주세요."
    • 주소 + [다른 계정으로 로그인] — 로그아웃하고 처음 화면으로(주소 칸은 비움).
    • (임시 비밀번호가 화면 메모리에 없을 때만) 「임시 비밀번호」 + "관리자에게 받은 임시 비밀번호를 다시 넣어 주세요."
    • 「새 비밀번호」 + "8자 이상이고 임시 비밀번호와 달라야 합니다. 영문, 숫자, 기호를 섞으면 더 안전합니다."
    • 「새 비밀번호 확인」 — 보기 단추 없이 새 비밀번호의 보기를 따른다(설정 › 비밀번호와 같다).
    • (2단계 인증을 켠 직원만) 맨 아래 「인증 앱 코드」 + "로그인할 때 넣은 코드는 다시 쓸 수 없으니, 앱에 다음 코드가 뜨면 넣어 주세요." — 로그인 코드 칸 모양(18px · 자간), 숫자 6자리 · one-time-code.
    • [비밀번호 바꾸고 계속] — 보내는 동안 「바꾸는 중…」(aria-busy, 칸은 읽기만). 요청 한도 30초(PASSWORD_CHANGE_TIMEOUT_MS — 설정 › 비밀번호와 같다).
    • 맨 아래(위쪽 선 · 12px 회색): "아웃룩 같은 메일 프로그램을 연결해 두었다면 비밀번호를 바꾼 뒤 다시 연결해 주세요."
    • 칸은 로그인 칸 모양(테두리 #8A94A3 · 글자 14px · 이름 12.5px 굵게)이고, 임시 비밀번호 · 새 비밀번호 칸 끝에 설정 화면의 보기를 둔다 — 로그인 첫 화면의 비밀번호 칸에는 없다.
  • 임시 비밀번호: 로그인 때 넣은 것을 화면 메모리에서 지금 비밀번호로 쓴다 — 묻지 않는다. 10분 뒤 메모리에서 지운다(12단계 코드 단계와 같은 10분). 메모리에 없으면(새로고침 · 다른 탭 · 10분 지남) 맨 위에 「임시 비밀번호」 칸을 보인다.
  • 코드를 다시 받는 까닭: 엔진이 비밀번호를 바꿀 때 코드를 꼭 요구하고, 로그인 때 쓴 코드를 서버가 기억해 다시 써도 새 비밀번호를 치는 사이 코드의 유효 시간(앞뒤 30초)을 지날 수 있다. 그래서 앱에 다음 코드가 뜨면 넣는다(성공에 쓴 코드는 2분 동안 다시 받지 않는다 — 12단계).
  • 상태(칸 아래 오류는 알림 없이 · 처음 걸린 칸에 초점):
    • 규칙에 맞지 않음(8자 미만 · 임시 비밀번호와 같음) — 보내지 않고 새 비밀번호의 안내 글이 그대로 빨간 오류가 된다. 엔진이 규칙으로 거절하면 같은 자리에 VAL_001 사전 문구.
    • 확인 칸이 다름 — 확인 칸 아래 "새 비밀번호가 서로 다릅니다."
    • 임시 비밀번호 칸이 빔 — 보내지 않고 그 칸의 안내 글이 그대로 빨간 오류가 된다.
    • 임시 비밀번호 틀림 — 그 칸 아래 AUTH_005 사전 문구("지금 비밀번호가 올바르지 않습니다."). 화면 메모리에서 쓴 비밀번호가 거절되면 기억을 지우고 「임시 비밀번호」 칸을 이 오류와 함께 보인다.
    • 코드 틀림 — 코드 칸 아래 AUTH_006 사전 문구, 칸을 비운다.
    • 시도 과다 — 위쪽 빨간 상자(로그인 오류와 같은 자리 · 같은 모양)에 REQ_001 사전 문구. 임시 비밀번호 틀림은 로그인 실패 한도, 코드 틀림은 코드 실패 한도로 센다. 칸 · 단추는 그대로.
    • 바꾼 뒤 넘기지 못함(새 토큰 받기 /auth/refresh 가 장애) — 먼저 me(세션 표의 지금 값)를 묻는다. 제한이 남아 있으면 그 단계로, 없으면 가려던 주소로. me 도 받지 못하면 이 단계에 머물며 위쪽 빨간 상자에 사전 문구 + 다시 시도 — 칸 · 단추는 막고 [다른 계정으로 로그인]만 누를 수 있다. 2단계 켜기 단계도 같다.
  • 끝나면: 서버가 제한을 지우고 화면은 /auth/refresh 로 새 토큰을 받아 받은편지함으로 간다(로그인 전에 가려던 주소가 있으면 그곳) — 알림 "새 비밀번호를 정했습니다." 2단계 인증이 필수인데 켜지 않았으면 받은편지함 대신 「2단계 인증 켜기」 단계로. 다른 곳의 로그인(웹메일 · ARA SYSTEM 앱) 끊기 · 앱 비밀번호 지우기는 설정 › 비밀번호와 같다(계약 5절) — 맨 아래 안내 줄이 그 까닭이다.

로그인 — 2단계 인증 켜기(필수) (LoginTwoFactorSetup)

  • 누가 오나: 회사가 2단계 인증을 필수로 정한 날(시작일)부터, 아직 켜지 않은 직원(예외 주소 빼고)이 비밀번호를 맞혔을 때(restriction: twoFactorSetup). 켜기 전에는 메일 · 주소록 · 설정을 열 수 없다(AUTH_010). 새 비밀번호 정하기가 함께 걸린 직원(입사)은 그것 먼저.
  • 틀(위에서부터):
    • 제목 "2단계 인증을 켜 주세요" / "회사 정책에 따라 로그인할 때 휴대폰 인증 앱의 코드도 확인합니다. 지금 켜면 바로 메일을 쓸 수 있습니다."
    • 주소 + [다른 계정으로 로그인]
    • 차례(청록 굵은 숫자 — 동그라미 없음, 숫자 칸 22 · 사이 18 — 12단계 켜기 창과 같다):
      • ① "인증 앱으로 QR 코드를 찍어 주세요" / "인증 앱에서 [+] → [QR 코드 스캔]을 고릅니다. 앱이 없으면 Google Authenticator나 Microsoft Authenticator를 먼저 설치해 주세요." QR(148 — 양식 칸 360 안에 키를 옆에 두려고 12단계 켜기 창의 160 보다 작게) 오른쪽에 "찍을 수 없으면 이 키를 입력해 주세요"(12px 굵게) + 32자 키(4자씩 · 두 줄 · 고정폭 12px) + [복사](36 — "키를 복사했습니다." / 못 하면 "키를 복사하지 못했습니다. 키를 직접 골라 복사해 주세요."), 그 아래 "앱에는 ‘ARA SYSTEM (주소)’로 저장됩니다."
      • ② 「앱에 표시된 6자리 코드」 — 로그인 코드 칸 모양.
      • (지금 비밀번호가 화면 메모리에 없을 때만) ③ 「지금 비밀번호」 + "새로고침한 뒤에는 지금 비밀번호를 한 번 더 넣어 주세요." — [보기] 있음.
    • [켜고 계속] — 보내는 동안 「켜는 중…」(aria-busy, 칸은 읽기만). 요청 한도 30초(TWO_FACTOR_CHANGE_TIMEOUT_MS).
    • 문의 줄 "인증 앱을 쓸 수 없나요?" + 연락처(아래 "문의 줄").
    • 맨 아래(위쪽 선 · 12px): "Outlook이나 휴대폰 메일 앱은 이 코드를 묻지 않습니다. 켠 뒤에는 [설정] → [앱 비밀번호]에서 만든 비밀번호로 연결해 주세요."
  • 지금 비밀번호: 로그인 때 넣은 것(방금 새 비밀번호를 정했으면 그것)을 화면 메모리에서 쓴다 — 12단계 켜기 창의 ③ 을 묻지 않는다. 10분 뒤 지우고, 메모리에 없으면 ③ 칸을 보인다.
  • 키: 이 화면을 열 때마다 새로 만든다(POST /api/two-factor/enrollments — 10분 안에 켠다). 켜지 않고 떠나면(다른 계정 · 새로고침) 그 키는 버린다 — 새로고침 뒤에는 QR · 키가 바뀌므로 앱에 먼저 저장한 항목은 지우고 다시 찍는다. QR · 키는 화면 메모리에만 두고 로그에 남기지 않는다(12단계 그대로).
  • 상태:
    • 코드 틀림 — 칸 아래 AUTH_006 사전 문구, 칸을 비우고 초점. QR · 키는 그대로라 다시 찍지 않는다.
    • 시간이 지남 · 그사이 바뀜(AUTH_008) — 위쪽 빨간 상자에 사전 문구("2단계 인증 설정이 그사이 바뀌었거나 시간이 지났습니다. 처음부터 다시 해 주세요.") + 새 QR · 키로 다시 그린다(코드 칸은 비움). 다른 곳에서 이미 켰으면 이 세션은 서버가 끊는다(계약 6절 — 2단계를 거치지 않은 세션) — 다음 요청에서 세션 만료로 로그인 화면에 돌아가고, 다시 로그인하면 코드 단계다(2026-11-06 — 앞에 적었던 "건너뛰고 받은편지함으로"는 서버가 먼저 끊어 일어나지 않는다).
    • ③ 지금 비밀번호가 빔 — 보내지 않고 그 칸의 안내 글이 그대로 빨간 오류가 된다(새 비밀번호 단계의 임시 비밀번호와 같다).
    • 지금 비밀번호 틀림(③ 이 있을 때) — 칸 아래 AUTH_005. 둘 다 틀렸으면 코드부터(서버가 코드를 먼저 본다 — 12단계). 화면 메모리에서 쓴 비밀번호가 거절되면 기억을 지우고 ③ 칸을 이 오류와 함께 보인다.
    • 시도 과다 — 위쪽 상자에 REQ_001(코드 실패는 같은 주소 15분 5회 · 하루 20회 — 12단계 그대로).
    • 켠 뒤 넘기지 못함 — 새 비밀번호 단계의 같은 상태와 같다(me 를 먼저 묻고, 그것도 실패하면 위쪽 상자 + [다시 시도]).
  • 켠 뒤: 서버가 제한을 지우고 받은편지함으로 간다(가려던 주소가 있으면 그곳) — 알림 "2단계 인증을 켰습니다. 메일 프로그램은 앱 비밀번호로 연결합니다." + [앱 비밀번호 만들기] (단추가 있는 알림 — 10초, 마우스 · 초점이 있으면 멈춤) → 설정 › 앱 비밀번호. 웹메일 비밀번호로 메일 프로그램을 연결해 둔 직원이 다음 연결 때 막히기 전에 알린다.
  • 유예 기간(시작일 전 — 로그인은 그대로, me 의 twoFactorDeadline):
    • 메일 모듈 화면(목록 · 읽기 · 쓰기 · 검색)의 상단 바 바로 아래, 모듈 폭 전체에 노란 줄 하나(높이 40 · 바탕 #FFF7E6 · 아래 선 #F0D9A8 · 글 #7A5200 · 방패 아이콘 15): "11월 30일까지 2단계 인증을 켜 주세요"(굵게) ". 그 뒤에는 켜야 로그인할 수 있습니다." + [지금 켜기](36 — 읽기 화면 안내 줄의 단추와 같은 .notice-btn) → 설정 › 2단계 인증.
    • 날짜는 시작일의 전날(KST)이다 — 시작일이 12월 1일이면 「11월 30일까지」. 올해가 아니면 해를 붙인다("2027년 1월 3일까지").
    • 닫기 단추는 없다 — 켜야 사라진다. 주소록 · 설정에는 없다(설정 › 2단계 인증은 아래 안내).
  • 설정 › 2단계 인증 — 필수인 직원(GET /api/two-factor 의 isRequired · deadline):
    • 유예 중 · 꺼짐 — 머리 아래 갈색 상자(방패): "11월 30일까지 2단계 인증을 켜 주세요" / "회사 정책에 따라 그 뒤에는 2단계 인증을 켜야 로그인할 수 있습니다." 그 아래 상태 상자 · 경고 · 켜는 방법 · 켜기 창은 12단계 그대로.
    • 켜짐 — 상태 상자의 [2단계 인증 끄기] 대신 회색 글 "회사 정책에 따라 끌 수 없습니다"(서버도 VAL_001 로 거절). 아래 안내의 첫 줄은 "휴대폰을 바꿀 때는 관리자에게 2단계 인증을 풀어 달라고 요청해 주세요. 다음에 로그인할 때 새 휴대폰으로 다시 켜면 됩니다."(16단계부터 이 직원도 [휴대폰 바꾸기] — 첫 줄은 16단계 절의 글)(나머지 두 줄은 12단계 그대로).
  • 휴대폰을 바꿀 때(필수인 직원): 관리자에게 「2단계 해제」를 요청한다(운영 도구 — 받은편지함 알림 메일이 간다) → 다음 로그인 때 이 켜기 단계로 새 휴대폰을 등록한다. 직원이 직접 하는 휴대폰 바꾸기는 미뤘다(아래).

설정 › 계정 › 메일 프로그램 연결 (SettingsMailClients)

  • 메뉴: 계정 묶음의 앱 비밀번호 아래 「메일 프로그램 연결」. API 는 없다 — 화면 상수로만 그린다(설정값은 MAIL_CLIENT_SETTINGS — 앱 비밀번호 화면과 같은 값, 차례 글도 상수). 2단계 인증 상태만 이미 있는 GET /api/two-factor(스토어)로 보고, 받지 못하면 켠 직원의 안내로 둔다(더 안전한 쪽).

  • 머리 "메일 프로그램 연결" + "아웃룩, 썬더버드, 휴대폰 메일 앱에서 회사 메일을 보는 방법입니다."

  • 설정값 상자: 「메일 프로그램 설정값」 — 앱 비밀번호 화면과 같은 이름 · 값 표(이름 칸 136).

    이름 값
    받는 메일 (IMAP) mx1.arasystem.it.kr · 포트 993 · SSL/TLS
    보내는 메일 (SMTP) mx1.arasystem.it.kr · 포트 465 · SSL/TLS
    사용자 이름 (직원 주소) · 메일 주소 전체
    비밀번호 켠 직원 "웹메일 비밀번호가 아닌 앱 비밀번호" / 켜지 않은 직원 "앱 비밀번호를 권합니다. 2단계 인증을 켜지 않았다면 웹메일 비밀번호로도 연결됩니다."
  • 상자 바로 아래 — 2단계 인증 상태로 가른다. 둘 다 앱 비밀번호 만들기 → 설정 › 앱 비밀번호.

    • 켠 직원 — 갈색 상자(열쇠): "비밀번호 칸에는 꼭 앱 비밀번호를 넣어 주세요" / "2단계 인증을 켜 두어 웹메일 비밀번호로는 연결되지 않습니다. 프로그램마다 앱 비밀번호를 하나씩 만들어 넣어 주세요." — 단추 .notice-btn.
    • 켜지 않은 직원 — 청록 상자(열쇠): "앱 비밀번호로 연결하기를 권합니다" / "지금은 웹메일 비밀번호로도 연결되지만, 2단계 인증을 켜면 다시 연결할 때 막힙니다. 앱 비밀번호는 기기마다 따로 지울 수 있어 잃어버렸을 때도 안전합니다." — 단추 .line-btn.
  • 프로그램별 연결 방법: 「프로그램별 연결 방법」 + "쓰는 프로그램을 누르면 연결 방법을 볼 수 있습니다."

    • 그 아래 갈색 주의 줄(경고 삼각형): "새 Outlook과 휴대폰 Outlook 앱은 메일을 Microsoft 클라우드를 거쳐 받습니다. 회사 정책에 따라 되도록 다른 메일 프로그램을 써 주세요." 권하지 않음으로 정했다(2026-10-31) — 회사가 허용하면 줄을 지운다.
    • 펼침 목록(테두리 상자 하나 · 줄 사이 선): 줄 52 — 아이콘 18(PC · 휴대폰) · 이름(굵게) / 설명(12px 회색) · 오른쪽 ⌄(펼치면 ⌃). 줄 전체가 버튼(aria-expanded · 펼친 것만 aria-controls)이고, 펼친 차례는 region(이름 = 그 버튼)으로 이름 글과 같은 선(왼쪽 44)에서 번호 줄(청록 굵은 숫자 — 동그라미 없음)로 보인다. 처음에는 모두 접힘, 하나를 펼치면 다른 것은 접힌다.
    • 표기: 누르거나 고르는 것은 [ ], 호스트 이름은 굵게(2026-11-05 — 고르는 것도 「 」 대신 [ ], 위 "화면 문구 — 사용자에게 말하듯"). 12단계 「켜는 방법」과 같다. 화면 그림은 넣지 않는다. 맥 메일 · 휴대폰 Outlook 앱은 넣지 않는다.
    • 차례 글(아래 그대로 — 구현 때 실제 앱(한국어판)의 메뉴 이름과 한 번 더 맞춘다):
      • 아웃룩 (Windows) — 설명 "Microsoft 365나 Outlook 2016 이후의 데스크톱 Outlook"
        1. Outlook에서 [파일] → [계정 추가]를 누릅니다.
        2. 메일 주소를 넣고 [고급 옵션] → [내 계정을 수동으로 설정]을 고른 뒤 [연결]을 누릅니다.
        3. 계정 유형에서 [IMAP]을 고릅니다.
        4. 받는 메일과 보내는 메일 모두 서버는 mx1.arasystem.it.kr로 넣습니다. 포트는 받는 메일 993, 보내는 메일 465이고, 암호화 방법은 [SSL/TLS]를 고른 뒤 [다음]을 누릅니다.
        5. 비밀번호 칸에 앱 비밀번호를 넣고 [연결]을 누릅니다.
      • 새 Outlook — 설명 "메일이 Microsoft 클라우드를 거치는 Windows의 새 Outlook"
        1. 설정 → [계정] → [메일 계정] → [계정 추가]를 누릅니다.
        2. 메일 주소를 넣고 [계속]을 누른 뒤 공급자에서 [IMAP]을 고릅니다.
        3. [고급 설정 표시]를 누르고 받는 서버와 보내는 서버에 위 설정값을, 비밀번호 칸 두 곳에 모두 앱 비밀번호를 넣습니다.
        4. [계속]을 누르면 메일을 Microsoft 클라우드에 동기화한다는 안내가 나옵니다. 위의 주의 글을 읽고 정해 주세요.
      • 썬더버드 — 설명 "Windows와 Mac에서 주소와 앱 비밀번호만 넣으면 됩니다"
        1. 썬더버드에서 [≡] → [새로 만들기] → [기존 메일 계정]을 누릅니다.
        2. 이름과 메일 주소를 넣고, 비밀번호 칸에 앱 비밀번호를 넣은 뒤 [계속]을 누릅니다.
        3. 메일 주소와 앱 비밀번호만 넣으면 설정을 스스로 찾습니다. [IMAP]이 보이면 [완료]를 누릅니다.
        4. 찾지 못하면 [수동 설정]에서 위 설정값을 넣습니다.
      • iPhone · iPad — 설명 "기본 메일 앱"
        1. [설정] → [메일] → [계정] → [계정 추가] → [기타] → [메일 계정 추가]를 누릅니다. iOS 18에서는 [설정] → [앱] → [메일]에서 시작합니다.
        2. 이름, 메일 주소, 설명을 넣고, 암호 칸에 앱 비밀번호를 넣은 뒤 [다음]을 누릅니다.
        3. [IMAP]을 고르고, 받는 메일 서버와 보내는 메일 서버의 호스트 이름에 mx1.arasystem.it.kr, 사용자 이름에 메일 주소 전체, 암호에 앱 비밀번호를 넣습니다. 보내는 메일 서버 칸이 선택 사항으로 보여도 꼭 채워 주세요.
        4. [다음] → [저장]을 누릅니다. 포트는 받는 메일 993, 보내는 메일 465로 저절로 맞춰집니다.
      • 안드로이드 — 설명 "Gmail 앱으로 설명하며, 삼성 이메일 앱 같은 다른 메일 앱도 같은 값을 넣으면 됩니다"
        1. Gmail 앱에서 프로필 사진 → [다른 계정 추가] → [기타]를 누릅니다.
        2. 메일 주소를 넣고 [수동 설정] → [개인(IMAP)]을 고릅니다.
        3. 비밀번호에 앱 비밀번호를 넣습니다.
        4. 받는 서버와 보내는 서버는 mx1.arasystem.it.kr로 넣습니다. 포트는 받는 서버 993, 보내는 서버 465이고, 보안 유형은 [SSL/TLS]를 고른 뒤 [다음]을 누릅니다.
  • 아래 안내 상자(#F3F8F8 · 가운뎃점 줄): "연결이 안 되면 사용자 이름에 메일 주소 전체를 넣었는지, 비밀번호 칸에 앱 비밀번호 40자를 띄어쓰기 없이 넣었는지 확인해 주세요." · "POP3로는 연결할 수 없으니 계정 유형은 IMAP을 골라 주세요." · "웹메일 비밀번호를 바꾸면 앱 비밀번호도 모두 지워지니, 프로그램마다 새로 만들어 넣어 주세요." · "그래도 안 되면 관리자에게 문의해 주세요."

  • 좁은 폭: 설정 메뉴가 콘텐츠 위로 올라간다(지금 설정 셸의 줄바꿈). 설정값 상자는 이름(12px 회색) 위 · 값 아래 한 칸으로. 펼침 줄의 이름 · 설명이 줄을 바꿔도 줄 전체가 누르는 곳이다.

  • 앱 비밀번호 화면(12단계)에 더하는 것: 설정값 상자 바로 아래 한 줄(12.5px · #4A5363) "프로그램별 연결 방법은 메일 프로그램 연결에서 볼 수 있습니다." — 「메일 프로그램 연결」이 이 화면 링크다. 12단계 보드는 고치지 않았다.

  • 전 직원 전환 때: 썬더버드의 "설정을 스스로 찾습니다"와 아웃룩 자동 설정은 autoconfig · autodiscover 주소와 SRV 레코드를 낸 뒤부터 된다(인프라 — mail/README.md) — 그 전에는 썬더버드도 [수동 설정](4번 차례)이다. 호스트 mx1.arasystem.it.kr 는 전환 때 다시 본다(같은 상수).

받은편지함 알림 메일 · 휴지통 30일 · 문의 줄 (MailNoticesCutover)

  • 알림 메일 — 공통

    • 서버가 그 직원의 받은편지함에 바로 넣는다(계약 6절 — 안 읽음 · 받은 시각은 지금 · 실시간 알림과 바탕화면 알림이 간다, 필터 · 스팸 판정은 거치지 않는다). 넣지 못해도 원래 일은 그대로 끝난다.

    • 보낸 사람 ARA SYSTEM <postmaster@<MAIL_DOMAIN>>, 받는 사람 그 직원(이름 <주소>). 목록 · 읽기 화면은 다른 메일과 같다 — 보낸 사람 「ARA SYSTEM」, 아바타 "A", 뱃지 · 아이콘으로 따로 가르지 않는다.

    • 링크를 넣지 않는다 — 직원에게 "회사 보안 알림에는 링크가 없다"는 습관을 주어 링크 달린 피싱 메일이 눈에 띄게 한다. 할 일은 메뉴 경로(웹메일의 [설정] → …)를 굵은 글로 적고, 맨 아래 한 줄로도 알린다. 웹메일 안의 알림 단추(2단계 인증을 켠 뒤의 [앱 비밀번호 만들기] · 11단계 위험 파일 알림의 [대용량 첨부 보기])는 메일이 아니라 그대로 둔다.

    • 본문 차례: 인사 한 줄 → 표 → 할 일(메뉴 경로 글) → 문의 줄 → 맨 아래 한 줄. HTML 과 같은 글의 텍스트 본문을 함께 넣는다.

    • 자리 표시: {이름} 직원의 표시 이름(없으면 주소의 @ 앞) · {시각} · {올린 때} KST "2026년 10월 30일 (금) 오후 3:20" · {파일 이름} 올린 이름 그대로 · {크기} 11단계 크기 표기("48.2 MB") · {연락처} 문의 연락처 설정값. 이름 · 파일 이름 · 연락처는 HTML 본문에 넣을 때 이스케이프한다.

    • 문의 줄: {확인 문장} 다음 줄에 {연락처}. 연락처를 정하지 않았으면 그 줄을(HTML 은 앞의 <br> 와 함께) 뺀다.

    • 맨 아래 한 줄(세 통 같음): "이 메일은 ARA SYSTEM이 자동으로 보낸 알림입니다. 회사 보안 알림에는 링크가 없으니, 비슷한 메일에 링크가 있다면 누르지 않도록 주의해 주세요."

    • HTML 꼴(세 통 같음 — 보내는 메일 정화 sanitize-outgoing-html.ts 의 허용 목록 안이고 감싸는 div 는 보내는 메일과 같은 기본 글꼴 · 14px · 1.75, 표의 줄은 칸마다 같은 꼴을 되풀이한다):

      <div style="font-family: 'Malgun Gothic', '맑은 고딕', 'Apple SD Gothic Neo', sans-serif; font-size: 14px; line-height: 1.75; color: #1F2733">
        <p style="margin: 0 0 14px">{인사 한 줄}</p>
        <table style="width: 100%; border-collapse: collapse; margin: 0 0 18px; font-size: 13.5px; line-height: 1.6">
          <tbody>
            <tr>
              <th style="width: 104px; padding: 8px 12px; border: 1px solid #E6E8EC; vertical-align: top; background-color: #F8F9FB; color: #4A5363; font-weight: 600; text-align: left">{칸 이름}</th>
              <td style="padding: 8px 12px; border: 1px solid #E6E8EC; vertical-align: top">{값}</td>
            </tr>
          </tbody>
        </table>
        <p style="margin: 0 0 2px; font-weight: 700">할 일</p>
        <p style="margin: 0 0 20px">{할 일 — 메뉴 경로는 웹메일의 <strong>[설정] → …</strong>}</p>
        <p style="margin: 0; padding-top: 14px; border-top: 1px solid #EEF0F3; font-size: 13px; color: #4A5363">{확인 문장}<br>{연락처}</p>
        <p style="margin: 6px 0 0; font-size: 12px; color: #667085">이 메일은 ARA SYSTEM이 자동으로 보낸 알림입니다. 회사 보안 알림에는 링크가 없으니, 비슷한 메일에 링크가 있다면 누르지 않도록 주의해 주세요.</p>
      </div>
      
    • 텍스트 본문 꼴(표는 줄마다 "칸 이름: 값", 메뉴 경로는 굵게 없이 그대로):

      {인사 한 줄}
      
      {칸 이름}: {값}
      
      할 일
      {할 일}
      
      {확인 문장}
      {연락처}
      
      이 메일은 ARA SYSTEM이 자동으로 보낸 알림입니다. 회사 보안 알림에는 링크가 없으니, 비슷한 메일에 링크가 있다면 누르지 않도록 주의해 주세요.
      
  • ① 관리자가 비밀번호를 재설정했습니다 — 운영 도구의 「비밀번호 재설정」 뒤.

    • 제목 "관리자가 비밀번호를 재설정했습니다"
    • 인사 "{이름} 님, 관리자가 비밀번호를 임시 비밀번호로 바꿨습니다."
    • 표: 시각 — {시각} / 바뀐 것 — "비밀번호가 임시 비밀번호로 바뀌었고, 처음 로그인할 때 새 비밀번호를 정하게 됩니다" / 로그아웃 — "모든 기기의 웹메일과 앱에서 로그아웃되었습니다"(2026-11-06 — 앱 세션도 끊는다) / 앱 비밀번호 — "모두 지워져서 메일 프로그램은 새 앱 비밀번호로 다시 연결해야 합니다"
    • 할 일 "아웃룩이나 휴대폰 메일 앱을 쓰면 메일 프로그램마다 앱 비밀번호가 필요합니다. 웹메일의 **[설정] → [앱 비밀번호]**에서 새로 만들어 주세요."
    • 확인 문장 "직접 요청한 일이 아니면 바로 IT 담당에게 알려 주세요."
  • ② 관리자가 2단계 인증을 껐습니다 — 운영 도구의 「2단계 해제」(휴대폰 분실) 뒤.

    • 제목 "관리자가 2단계 인증을 껐습니다"
    • 인사 "{이름} 님, 휴대폰을 잃어버렸거나 인증 앱을 쓸 수 없다고 알려 주셔서 관리자가 2단계 인증을 껐습니다."
    • 표: 시각 — {시각} / 바뀐 것 — "2단계 인증이 꺼져서 지금은 비밀번호만으로 로그인합니다" / 그대로인 것 — "비밀번호, 앱 비밀번호, 로그인된 브라우저"
    • 할 일 "새 휴대폰에 인증 앱을 설치한 뒤 웹메일의 **[설정] → [2단계 인증]**에서 다시 켜 주세요. 회사가 2단계 인증을 필수로 정했다면 다음에 로그인할 때 켜는 화면이 먼저 나옵니다."
    • 확인 문장 "직접 요청한 일이 아니면 바로 IT 담당에게 알려 주세요."
  • ③ 올린 대용량 파일에서 위험한 내용이 발견되었습니다 — 악성 검사가 위험으로 판정했을 때, 그 파일을 올린 직원에게(파일마다 한 통).

    • 제목 "올린 대용량 파일에서 위험한 내용이 발견되었습니다"
    • 인사 "{이름} 님, 올린 대용량 파일을 보안 검사했더니 위험한 내용이 발견되어 보내거나 내려받을 수 없게 막았습니다."
    • 표: 파일 — {파일 이름} / 크기 — {크기} / 올린 때 — {올린 때} / 조치 — "이 파일만 막았고, 함께 올린 다른 파일은 그대로 보내고 받을 수 있습니다"
    • 할 일 "직접 만든 파일이 아니면 PC를 백신으로 검사해 주세요. 다른 곳에서 받은 파일이면 보낸 사람에게도 알려 주세요. 막은 파일은 웹메일의 **[설정] → [대용량 첨부]**에서 볼 수 있습니다."
    • 확인 문장 "검사 결과가 잘못되었다고 생각되면 IT 담당에게 알려 주세요."
    • 웹메일을 열어 둔 동안의 빨간 알림(11단계)은 그대로 둔다 — 이 메일은 그때 열려 있지 않았던 직원도 알게 한다.
  • 휴지통 · 스팸메일함 — 30일 줄:

    • 목록 머리(52) 바로 아래 회색 줄(바탕 #F8F9FB · 아래 선 #EEF0F3 · 위아래 8 · 12.5px #4A5363 · 시계 아이콘 14 #667085): 휴지통 "휴지통에 들어간 메일은 30일 뒤 자동으로 지워집니다." / 스팸메일함 "스팸메일함에 들어간 메일은 30일 뒤 자동으로 지워집니다."
    • 늘 보인다(닫기 없음). 목록 보기(표)에서는 표 머리 위 — 같은 줄. 검색 결과 · 다른 메일함에는 없다. 머리의 [휴지통 비우기] · [스팸메일함 비우기]는 지금 그대로다.
    • 메일이 없으면 줄을 빼고 빈 상태 안내 글에 붙인다: "휴지통이 비어 있습니다" / "지운 메일은 여기에 30일 동안 남아 있다가 저절로 지워집니다." · "스팸메일함이 비어 있습니다" / "스팸 메일은 여기에 30일 동안 남아 있다가 저절로 지워집니다."
    • 지우는 것은 메일 서버다 — 그 메일함에 들어간 날부터 30일, 매일 새벽 3시(KST). API 는 없다(계약 6절).
  • 문의 줄(로그인 · 2단계 코드 단계 · 2단계 켜기):

    • 연락처는 GET /api/support-contact 의 text(운영 도구로 넣는 설정값 · 200자 · 한 줄 — 로그인 없이 받는다). 보드의 "IT 담당 홍길동 · 02-000-0000 · 사내 메신저 @it" 는 자리 표시다. 글자로만 보인다(링크 · HTML 없음).
    • 꼴: 물음(12.5px · #667085) 다음 줄에 연락처(#344054 · 500) — 연락처가 길어도 중간에서 끊기지 않게 줄을 나눈다.
    • 로그인 — 있을 때 "비밀번호를 잊으셨나요?" + 연락처 / 없을 때 지금 글 "비밀번호를 잊으셨다면 관리자에게 문의해 주세요."
    • 코드 단계(LoginTwoFactor) · 2단계 켜기 — 있을 때 "인증 앱을 쓸 수 없나요?" + 연락처 / 없을 때 지금 글 "인증 앱을 쓸 수 없나요? 관리자에게 문의해 주세요."
    • 받지 못하면(오류 · null) 없을 때와 같다.
    • 잠긴 계정(퇴사 · 털림)도 비밀번호 틀림과 같은 글 "메일 주소나 비밀번호가 맞지 않습니다. 다시 확인해 주세요."다(원인을 가리지 않는다) — 그 아래 문의 줄로 안내한다.

처음 판에서 미룬 것

직원이 직접 하는 휴대폰 바꾸기(지금 휴대폰의 코드로 확인 → 새 휴대폰 등록 — 그때까지는 관리자의 「2단계 해제」), 웹메일 관리자 화면(공통 기반 때 — 그때까지 운영 도구 · 엔진 관리 화면), 비밀번호 스스로 재설정(복구 연락처가 필요하다), 새 기기 로그인 알림 메일, 잠긴 계정만의 안내 글, 공용 주소로 보내기 · 공유 메일함. 시안에서 뺀 것: 알림 메일의 링크 단추(넣지 않는다 — 위 규칙), 유예 노란 줄의 닫기, 맥 메일 · 휴대폰 Outlook 앱의 연결 차례, 프로그램별 화면 그림.

14단계 — 검색 · 대화 묶기 (2026-11-05 확정 — Search14 · Thread14 · ListThreads14)

자체 검색 색인으로 붙여 쓴 말 · 앞부분 · 제목 맨 앞의 말머리까지 찾고(부분 일치), 조건 검색과 검색어 강조를 더한다. 같은 대화(답장 · 전달로 이어진 메일)를 읽기 화면에 늘 보이고, 목록은 사용자가 켜면 대화로 묶는다. 까닭은 엔진 실측(2026-11-03)이다 — 엔진 스레드는 회신: · 답장: · 전달: 을 떼지 않아 한국어 Outlook 의 답장이 따로 떨어지고, 엔진 검색은 어절이 통째로 같을 때만 맞는다. 데이터 · API 규칙(색인 · 대화 · 조건)은 구현 때 계약 2절에 더한다. 구현 · 배포는 전환 뒤 안정화(1~2주) 다음이다. 새 크기 토큰은 없다 — 조건 칸 40 은 필터 조건 칸의 밀도 예외(--size-filter-control), [대화로 묶기] 36 은 안내 줄 단추의 밀도 예외(--size-notice-action)를 다시 쓴다(구현 때 [조건] 32 도 — 아래 "14단계 구현하며 정한 보완"). 강조 노랑만 색 토큰(--color-search-mark)을 하나 둔다. 문구는 위 「화면 문구 — 사용자에게 말하듯」을 따른다.

검색 · 조건 검색 · 강조 (Search14)

  • 찾는 방식: 서버의 자체 색인(지금 DB — 글자 두 개씩 묶은 색인)에서 부분 일치로 찾는다. "계약"으로 계약서 · 계약직 · 계약은 · 계약을, 제목 맨 앞의 [공지] · Re: 까지 찾는다. 여러 낱말은 모두 맞아야 하고 큰따옴표는 구절이다(지금과 같다). 영문은 대소문자를 가리지 않는다. 끝이 바뀐 말('보내' → '보냈습니다')과 첨부 파일 안의 글은 찾지 않는다(첨부 이름은 찾는다).
  • [조건]: 검색 칸 안 오른쪽(✕ 앞)의 단추. 누르면 칸 바로 아래에 「조건으로 찾기」 패널(검색 칸 폭)이 열린다(aria-expanded).
    • 칸: 보낸 사람 · 받는 사람(참조도) · 제목 · 내용 · 기간(전체 기간 · 최근 1주 · 최근 1달 · 최근 3달 · 최근 1년 · 날짜 고르기 → 시작 ~ 끝) · 첨부(체크 "첨부 파일이 있는 메일만"). 칸 높이 40.
    • 아래 안내 "검색 칸에 쓴 말이 들어 있고 이 조건에도 맞는 메일을 찾습니다. 받는 사람으로 찾을 때는 참조로 받은 사람까지 찾습니다." 단추 [조건 지우기] · [찾기].
    • 패널이 열려 있거나 조건이 걸려 있으면 [조건] 단추가 청록 바탕이다. 검색 칸이 비어도 조건만으로 찾을 수 있다.
  • 조건이 걸린 결과: 찾을 곳 줄 아래 한 줄 "조건: 이도윤 님이 보낸 메일, 최근 1달, 첨부 있음" + [조건 지우기]. 조건은 쉼표로 잇는다(꼬리표 · 알약을 쓰지 않는다).
  • 찾을 곳은 지금과 같다(그 메일함 | 전체 메일함 — 전체는 휴지통과 스팸메일함을 뺀다). 조건도 주소에 싣는다 — 이름은 구현 때 계약에서 정하고, 검색어처럼 로그에 남기지 않는다.
  • 결과 줄(높이 64, 세 줄): 보낸 사람 · 시각 / 제목(+ 첨부 표시) / 미리 보기 한 줄(맞은 말 둘레). 제목 · 미리 보기의 맞은 말은 <mark>(노랑 #FCE7A4 · 글자색은 그대로).
  • 읽기 화면 강조: 제목 · 본문 · 첨부 이름의 맞은 말. 본문은 서버가 정화한 HTML 의 글 조각에만 <mark> 를 넣는다(찾기와 같은 정규화 · 부분 일치). 맞은 곳 사이 이동(다음 · 이전)은 이번에 넣지 않는다.
  • 검색 준비 중: 지난 메일의 색인을 만드는 동안 그 직원의 검색은 지금 방식(엔진) 그대로이고, 결과 머리 아래 안내 줄(옅은 청록 바탕 · role=status) "지난 메일까지 낱말의 일부로도 찾을 수 있게 밤사이에 준비하고 있습니다. 메일이 많으면 며칠 걸릴 수 있고, 그동안은 낱말을 끝까지 다 써야 찾을 수 있습니다."(2026-11-07 사용자 관점 점검 — 색인은 밤에만 채워, 낮 내내 같은 글이면 멈춘 줄 안다)
  • 결과 없음: "‘계약’이 들어간 메일이 없습니다" / "다른 말로 찾거나 조건을 줄여 보세요." + [전체 메일함에서 찾기](그 메일함에서 찾을 때만). 지금의 한계 안내는 색인이 준비된 뒤에는 뺀다 — 준비 중에는 위 안내 줄과 겹치지 않게 "준비가 끝나기 전에는 ‘서버이전’의 ‘서버’처럼 붙여 쓴 말의 일부나, 제목 맨 앞의 [공지]나 Re: 같은 말머리로는 찾지 못합니다."(2026-11-07).
  • 검색 결과는 대화로 묶지 않는다(한 통씩).

읽기 화면 — 같은 대화 (Thread14)

  • 본문 · 첨부 아래 "같은 대화 N통". 오래된 차례로 테두리 상자 안의 줄(높이 48): ▸ · 머리글자 · 이름(내가 보냈으면 "나" + 회색 "보낸편지함") · 미리 보기 한 줄 · 시각.
  • 지금 보는 메일의 줄은 옅은 청록 바탕에 "지금 보고 있는 메일"이고 누를 수 없다. 다른 줄을 누르면 그 자리에서 펼쳐 본문과 [답장] · [이 메일만 열기]가 보이고(aria-expanded), 다시 누르면 접는다.
  • 대화에 드는 메일: 보낸편지함을 포함한 모든 메일함 — 휴지통과 스팸메일함은 뺀다. 한 통뿐이면 이 칸이 없다.
  • 같은 대화의 기준(우리 규칙 — 엔진보다 넓다): 답장 관계(References · In-Reply-To)가 있고 말머리를 뗀 제목이 같을 때. 떼는 말머리는 Re · RE · Fw · FW · Fwd · AW · WG · 회신 · 답장 · 전달 · 대괄호 꼬리표이고 여러 겹도 뗀다. 제목이 다르면 묶지 않는다(엔진과 같다 — 남의 메일이 잘못 붙지 않게). 원본보다 늦게 색인된 메일은 들어온 뒤 합친다. 색인을 다 만들기 전 그 직원은 엔진의 스레드 그대로다.

목록 — 대화로 묶어 보기 (ListThreads14)

  • 목록 머리 오른쪽의 [대화로 묶기](눌림 단추 — aria-pressed, 높이 36)와 설정 › 메일 › 보내기 · 읽기의 같은 켬/끔 한 줄. 사용자마다 서버에 저장한다(다른 기기에서도 같다). 기본은 끔이다 — 전환 직후라 보던 목록을 바꾸지 않는다.
  • 켜면 한 줄이 그 메일함의 대화 하나다: 참여자(이름을 쉼표로 — 내가 보냈으면 "나") · 통 수("4통" — 회색 글, 뱃지 · 알약 없음, 한 통이면 없음) · 마지막 시각 / 마지막 메일 제목(+ 첨부 표시). 하나라도 안 읽었으면 굵게.
  • 통 수는 대화 전체(보낸편지함 포함 · 휴지통과 스팸 빼고)이고, 고르기 · 읽음 · 중요 · 이동 · 삭제는 그 메일함 안에 있는 그 대화의 메일 모두에 한다.
  • 줄을 누르면 그 메일함의 마지막 메일을 열고, 아래에 같은 대화 칸이 보인다. 목록 머리 글은 켰을 때 "대화 6개, 안 읽은 메일 2통", 껐을 때 "메일 9통, 안 읽은 메일 2통" 꼴이다.

처음 판에서 미룬 것

본문 강조의 다음 · 이전 이동, 검색어 줄임말(from: 같은 꼴), 첨부 파일 안의 글 찾기, 끝이 바뀐 말 찾기(형태소 분석). 대화째 끌어다 놓기와 같은 대화 칸 머리의 [모두 펼치기] · 펼친 줄 머리의 [답장](2026-11-07 사용자 관점 점검에서 나와 미뤘던 것)은 15단계에서 했다(아래 "15단계").

휴대폰 앱 받기 (2026-11-08 확정 — SettingsMobileApp)

안드로이드 앱(ARA SYSTEM — flutter/)은 스토어(Play)에 올리지 않고, 직접 빌드해 서명한 APK 를 웹메일에 로그인한 직원만 받게 한다. PC 에서는 설정 › 계정 › 휴대폰 앱(A)이 QR 코드로 휴대폰에 넘기고, 휴대폰 브라우저는 앱 틀 밖의 받기 화면 /app(B)을 연다. 앱의 업데이트 화면 · 설정 › 앱 정보의 [새 버전 받기]도 /app 을 연다(앱은 API_URL 에서 /api 를 뗀 출처 + /app — 같은 주소 배포). 데이터는 서버 두 경로다(로그인 필요 · 제한된 세션은 막힘 — 계약 1절 "휴대폰 앱 받기 — 사내 배포"): 판 정보 GET /api/app-releases/android/latest ({ version, sizeBytes, releasedAt } — 가장 늦게 올린 판, 없으면 404 RES_001)와 파일 GET /api/app-releases/android/latest/file (APK — attachment; filename="ARA-SYSTEM-<버전>.apk" · no-store · Range). 판을 올리는 것은 서버의 운영 도구다(웹에 올리기 화면은 없다). 글의 원본은 화면 상수 frontend/src/shared/constants/mobile-app.ts 이고 아래는 옮겨 적은 것이다. 문구는 위 「화면 문구 — 사용자에게 말하듯」을 따른다. 새 크기 토큰 · 밀도 예외는 없다(글자 크기 17 하나만 토큰으로 더했다 — 문서 끝 "휴대폰 앱 받기 구현하며 정한 보완"). 뱃지 · 최신 · NEW 같은 꼬리표는 달지 않는다.

설정 › 계정 › 휴대폰 앱 (SettingsMobileApp A)

  • 메뉴: 계정 묶음의 메일 프로그램 연결 다음 · 로그인 기록 앞 「휴대폰 앱」(아이콘은 이미 있는 휴대폰 그림 smartphone). 주소 /settings/account/mobile-app, 탭 제목 "휴대폰 앱 · ARA SYSTEM".
  • 머리 "휴대폰 앱" + "안드로이드 휴대폰에 ARA SYSTEM 앱을 설치하면 메일을 읽고 보내고, 새 메일 알림도 받을 수 있습니다." 콘텐츠 열은 다른 설정 화면과 같은 800.
  • 받을 앱 있음(A1) — 「안드로이드 앱」(14 굵게) 아래 테두리 칸(모서리 10):
    • 앱 줄(안쪽 16 · 사이 14): 앱 그림 48 · "ARA SYSTEM"(14 · 600) / "버전 1.0.0 · 올린 날 2026년 11월 18일 · 크기 31 MB"(12.5 회색 · 숫자 고정폭) · 오른쪽 [앱 내려받기](채운 단추 44 · 아이콘 15 — 화면 낭독은 크기까지 "앱 내려받기 (31 MB)").
    • 가는 선 아래 QR 줄(사이 20): QR 160(흰 상자 · 안쪽 6 · 테두리) + 「휴대폰으로 받기」(13.5 · 600) / "휴대폰 카메라로 QR 코드를 찍어 주세요. 휴대폰 브라우저에서 웹메일에 로그인하면 바로 앱을 받을 수 있습니다." / 가는 선 아래 "QR 코드를 찍을 수 없으면 휴대폰 브라우저의 주소 칸에 아래 주소를 직접 넣어 주세요." + 주소 "portal.arasystem.it.kr/app"(14 · 600) / "PC에서 받은 파일은 휴대폰으로 옮겨야 설치할 수 있습니다."(12.5 회색). QR 은 2단계 인증 켜기와 같은 QrCodeImage(160)이고, 지금 주소의 출처에 /app 을 붙인 주소만 담는다 — 비밀이 아니다(그림 이름 "휴대폰에서 앱을 받는 주소의 QR 코드").
    • 「설치 방법」 — 번호 줄(청록 굵은 숫자 · 동그라미 없음, 2단계 인증 「켜는 차례」와 같은 꼴). 2번의 파일 이름은 받은 판의 버전이다:
      1. 휴대폰에서 [앱 내려받기]를 눌러 파일을 받아 주세요. 브라우저가 이 파일을 받을지 한 번 더 물으면 그대로 받아 주세요.
      2. 받은 ‘ARA-SYSTEM-1.0.0.apk’ 파일을 열어 주세요. 처음 설치할 때는 휴대폰이 알 수 없는 앱의 설치를 허용할지 묻습니다. 허용한 뒤 돌아와 설치를 이어 가 주세요.
      3. Play 스토어를 거치지 않은 앱이라 Play 프로텍트가 경고할 수 있습니다. 웹메일에서 받은 ARA SYSTEM 앱이면 설치를 이어 가 주세요.
      4. 설치한 앱을 열고 웹메일과 같은 메일 주소와 비밀번호로 로그인해 주세요. 앱 비밀번호는 만들지 않으셔도 됩니다.
    • 「새 버전이 나오면」 — "새 버전도 같은 곳에서 받아 설치하면 됩니다. 쓰던 앱을 지우지 않고 설치하면 로그인과 설정이 그대로 남습니다." / "꼭 필요한 업데이트가 있으면 앱이 먼저 알려 드립니다."
    • 아이폰 안내 상자(#F3F8F8 · 안내 아이콘): "아이폰용 앱은 아직 없습니다" / "아이폰에서는 브라우저로 웹메일을 쓰거나, 기본 메일 앱에 회사 메일을 연결해 주세요. 연결 방법은 메일 프로그램 연결에서 볼 수 있습니다." — 「메일 프로그램 연결」이 그 설정 화면 링크다.
  • 받을 앱 없음(A2 — 404 RES_001, 알림 없음): 「안드로이드 앱」 아래 테두리 상자에 휴대폰 아이콘 24 · "아직 받을 수 있는 앱이 없습니다"(14 · 600) / "앱이 준비되면 이곳에서 받아 설치할 수 있습니다." + 가는 선 아래 문의 줄. QR · 설치 방법 · 새 버전 · 아이폰 안내는 모두 뺀다.
  • 문의 줄: 로그인 화면의 문의 줄과 같은 꼴이다(연락처는 13단계 GET /api/support-contact). 연락처가 있으면 "언제 받을 수 있는지 궁금하신가요?" 다음 줄에 연락처, 없거나 받지 못하면 대신 "언제 받을 수 있는지는 관리자에게 문의해 주세요."
  • 좁은 폭(휴대폰 — 480 이하): 설정 메뉴가 위로 올라가고(지금 설정 셸), QR 줄은 빼며, [앱 내려받기]는 이름 아래 전체 폭으로 둔다.

휴대폰 받기 화면 /app (SettingsMobileApp B)

  • 독립 화면: 앱 레일 · 상단 바가 없다(인쇄 화면처럼 앱 틀 밖의 보호 화면 — meta.requiresAuth). 탭 제목 "휴대폰 앱 받기 · ARA SYSTEM".
  • 로그인하지 않았을 때: 지금의 로그인 화면으로 갔다가(?redirect=/app) 로그인을 마치면 이 화면으로 돌아온다 — 로그인 화면은 새로 그리지 않았다. 새 비밀번호 정하기나 2단계 인증 켜기 단계가 걸린 직원도 그 단계를 마친 뒤 돌아온다. 이미 로그인해 둔 휴대폰 브라우저는 바로 열린다.
  • 브랜드 띠(#0B3640 — 로그인 브랜드 패널을 줄인 꼴): 흰 로고 120 · "휴대폰 앱 받기"(20 · 흰 굵게) · "안드로이드 휴대폰에 설치하면 메일을 읽고 보내고, 새 메일 알림도 받을 수 있습니다."(14 · #A3C2C6). 내용 바탕은 #F8F9FB, 본문 14.
  • 받을 앱 있음(B1):
    • 카드(테두리 · 모서리 12 · 안쪽 16): 앱 그림 52 · "ARA SYSTEM"(17 굵게) / "안드로이드 앱"(13 회색) → 버전 · 올린 날 · 크기(이름 칸 56 · 13) → [앱 내려받기](전체 폭 · 높이 48 · 글 15 · 아이콘 18). 휴대폰에서도 [앱 내려받기]까지 첫 화면에 들어온다.
    • 「설치 방법」(15 굵게 — 줄 사이 10 · 줄 높이 1.55):
      1. [앱 내려받기]를 눌러 파일을 받아 주세요. 브라우저가 한 번 더 물으면 그대로 받아 주세요.
      2. 받은 파일을 열어 주세요. 처음에는 알 수 없는 앱의 설치를 허용할지 묻습니다. 허용한 뒤 설치를 이어 가 주세요.
      3. Play 프로텍트가 경고해도 이 화면에서 받은 ARA SYSTEM 앱이면 설치를 이어 가 주세요.
      4. 설치한 앱을 열고 웹메일과 같은 메일 주소와 비밀번호로 로그인해 주세요.
    • 설치 방법 아래 한 줄(13 · #4A5363) "새 버전도 이 화면에서 받아 설치하면 됩니다. 쓰던 앱을 지우지 않고 설치하면 로그인과 설정이 그대로 남습니다." — 앱의 [새 버전 받기]로 온 직원이 쓰던 앱을 먼저 지우지 않게.
    • 아이폰 안내 상자(설정과 같다) → 가는 선 아래 문의 줄(13): 연락처가 있으면 "설치가 잘 되지 않나요?" 다음 줄에 연락처, 없으면 "설치가 잘 되지 않으면 관리자에게 문의해 주세요."
  • 받을 앱 없음(B2): 테두리 상자에 휴대폰 아이콘 28 · "아직 받을 수 있는 앱이 없습니다"(17) / "앱이 준비되면 이 화면에서 받을 수 있습니다." + 문의 줄(설정의 받을 앱 없음과 같은 글). 설치 방법과 아이폰 안내는 뺀다.
  • 넓은 화면(480 초과 — B3): 띠는 화면 폭 전체로 두고, 로고(150) · 제목(22) · 내용은 가운데 칸 480 에 맞춘다. 내용은 휴대폰과 같다.

두 화면 공통

  • [앱 내려받기]: 첨부처럼 <a href download> 이동이고, 누르기 직전에 세션을 확인한다(confirmSession) — 세션이 끝났으면 세션 만료 창이 뜨고 받지 않는다. 받는 파일 이름은 서버가 정한다("ARA-SYSTEM-1.0.0.apk").
  • 앱 정보를 받지 못하면: 칸(카드) 자리에 사전 문구와 [다시 시도]를 둔다. 받는 동안은 자리만 둔다.
  • 앱 그림: 남색 바탕에 흰 ARA 마크 — 홈 화면 아이콘 apple-touch-icon.png 를 둥글게 깎은 것이라 새 그림이 들지 않는다.
  • 크기: 설정의 [앱 내려받기] 44, /app 의 [앱 내려받기] 48(전체 폭) — 44 보다 크니 밀도 예외가 아니다. 새 크기 토큰은 없다(48 은 --space-48).

구현 때 맞출 것 · 처음 판에서 미룬 것

  • 받기와 설치 때 뜨는 창의 글은 브라우저와 제조사마다 다르다. 갤럭시 S23 에서 삼성 인터넷과 Chrome 으로 받기 경고, 알 수 없는 앱 설치 허용, Play 프로텍트 경고를 한 번씩 거쳐 보고 차례 글을 맞춘다(첫 판 구현 때는 아직 하지 않았다 — 맞춘 글은 화면 상수와 이 절을 함께 고친다).
  • 서명 키가 다른 판(시험판)이 깔린 휴대폰은 새 판을 덮어 설치할 수 없다 — 그때는 앱을 지운 뒤 설치하고 로그인을 다시 한다.
  • 미룬 것: 앱 안에서 받아 바로 설치, 최소 판이 아닌 새 판 알림, 아이폰(Ad Hoc · TestFlight), Google 개발자 인증(2027 전 세계 — 그 전에 등록할지 정한다).

15단계 — 메일 · 메일함 다루기 (2026-11-09 확정 — Reader15 · ListDrag15 · Mailbox15)

메일과 메일함을 옮기고 정리하는 일을 더한다 — 읽기 화면 [이동 ▾] · 끌어다 놓기 · 메일함 옮기기 · 하위 메일함까지 지우기 · 차단할 때 받은 메일 옮기기 · 예약 메일 [지금 보내기]. 서버 약속은 계약 2절("메일함 관리" · "보낸 사람 차단" · "보내기 취소 · 예약 발송")이다. 글의 원본은 화면 상수(shared/constants/mailbox-manage.ts · mail-block.ts · mail-send-later.ts · mail-filter.ts)이고 아래는 옮겨 적은 것이다. 새 크기 토큰 · 밀도 예외는 없다 — 옮기기 창의 줄 40 은 메일함 패널의 줄(--size-row-min), [지금 보내기] 36 은 안내 줄 단추(--size-notice-action)를 다시 쓴다. 문구는 위 「화면 문구 — 사용자에게 말하듯」을 따른다.

읽기 화면 · 목록 · 상단 바 (Reader15 1~3 · 6 · ListDrag15)

  • 읽기 화면 [이동 ▾]: 도구 줄 답장 · 전체 답장 · 전달 | 이동 ▾ · 스팸 신고 · 삭제 · ⋯ — 메일이 어디로 가는지를 한 묶음에 둔다(목록 보기는 지금처럼 구분선 없이 [전달] 뒤). 메뉴는 목록 도구 줄의 [이동 ▾]와 같은 부품(항목 40) · 같은 옮길 곳이다. 그 메일이 든 메일함(검색 결과에서 연 메일도 그 메일이 든 메일함) · 임시보관함 · 휴지통은 없다 — 휴지통으로는 [삭제]로 옮긴다. 보내기를 기다리는 메일(보내는 중 · 예약)에는 [스팸 신고]처럼 없다. 키보드는 목록의 [이동 ▾]와 같다(Enter · Space · 아래 화살표로 열면 첫 항목에 초점, Esc 는 메뉴만 닫고 [이동 ▾]으로).
  • 옮긴 뒤: [삭제]와 같은 길 — 분할 보기는 목록의 다음 메일을 열고 목록 보기는 목록으로 돌아간다. 알림 "메일 1통을 거래처 › 2025로 옮겼습니다." + [되돌리기](10초 — 되돌리면 "메일 1통을 받은편지함으로 되돌렸습니다.").
  • 옮길 메일함은 메일함 패널처럼: 같은 차례로 깊이만큼 들여 쓰고(10 · 28 · 44 · 56, 3단계까지) 앞에 패널과 같은 아이콘, 기본 메일함과 내 메일함 사이에 가는 선. 들여 쓴 메일함은 화면 낭독이 위 메일함부터 읽는다("거래처 안의 2025"). 목록의 [이동 ▾]도 같은 모양이다.
  • 그 메일이 든 메일함에 하위 메일함이 있으면 빼지 않고 고를 수 없는 흐린 줄(aria-disabled)로 남기고 이름 아래에 "이 메일이 있는 메일함" — 하위 메일함이 누구 밑에 있는지 보이게. 하위 메일함이 없으면 지금처럼 뺀다. 목록의 [이동 ▾]는 같은 규칙에 글만 "고른 메일이 있는 메일함"이다.
  • 알림의 메일함 이름: 같은 이름의 메일함이 다른 곳에도 있을 때만 위 메일함 이름을 붙인다("거래처 › 2025로" — 정하는 규칙은 아래 "15단계 구현하며 정한 보완"의 메일함 나무 도우미). 목록의 [이동 ▾] · 끌어다 놓기 · 되돌리기 알림도 같다.
  • 메뉴 자리: 화면(보이는 영역) 밖으로 넘치면 반대 끝에 맞춘다 — 단추 왼쪽 끝에 맞춘 메뉴가 오른쪽 밖으로 넘치면 메뉴 오른쪽 끝을 단추 오른쪽 끝에. 위아래만 바꾸던 메뉴 자리 정하기에 좌우를 더해 같은 부품의 모든 메뉴에 걸린다.
  • 좁은 읽기 칸: 도구 줄의 이름은 화면 폭이 아니라 읽기 칸 폭(컨테이너 쿼리)으로 숨긴다 — 이름을 한 줄에 다 보일 폭에 여유를 둔 580(도구 줄 좌우 여백 포함 · 휴지통은 [영구 삭제] 때문에 605)보다 좁으면 아이콘만 40×44([이동 ▾]는 ▾ 까지). 이름은 마우스를 올리면 뜨는 글(title)과 화면 낭독(aria-label)으로 읽힌다. 분할 보기 1144~1233px 에서 두 줄로 꺾이던 일이 없고, 360 에서도 두 줄로 끝난다.
  • 끌어다 놓기(목록 → 메일함 패널): 고른 줄을 끌면 고른 것 모두, 고르지 않은 줄을 끌면 그 줄만(고른 것은 그대로). 대화로 묶은 목록의 줄은 그 대화에서 지금 메일함에 든 메일(보내기를 기다리는 메일은 빼고 — [이동 ▾]와 같다), 보내기를 기다리는 메일의 줄 · 그런 메일뿐인 대화 줄은 끌 수 없다.
    • 끄는 동안 마우스 오른쪽 아래에 "메일 N통" · "대화 N개" 표(알림과 같은 어두운 바탕 · 12.5 굵은 흰 글자 · 봉투 · 말풍선 아이콘). 끈 줄은 흐리게 하지 않는다.
    • 놓을 수 있는 메일함 위에서는 그 줄에 2px 청록 점선 · 옅은 청록 바탕(쓰기 화면 파일 놓기와 같은 모양 — 초점 선과 헷갈리지 않게). 놓을 수 없는 곳(지금 메일함 · 임시보관함 · 「예약됨」)은 줄이 그대로이고 마우스 커서만 금지 모양이다.
    • 놓을 곳은 [이동 ▾]의 옮길 곳 + 휴지통. 휴지통에 놓으면 [삭제], 스팸메일함에 놓으면 [스팸 신고]와 같고 그 밖은 [이동 ▾]와 같다(알림 · [되돌리기]도 같다).
    • 마우스로만 한다 — 키보드 · 터치는 [이동 ▾]. 메일함 패널은 쓰기 화면에도 보이므로 파일 끌기는 받지 않는다(첨부 놓기와 겹치지 않게). 줄의 링크가 끌어 가던 메일 주소(URL)는 싣지 않는다 — 다른 창 · 바깥 프로그램에 놓아도 아무 일 없다.
  • 스팸메일함으로 옮기는 세 길 같게: [스팸 신고] · [이동 ▾]에서 스팸메일함 · 스팸메일함에 끌어다 놓기는 모두 스팸 표시를 붙여 옮긴다. 스팸메일함에서 휴지통이 아닌 메일함으로 옮기면(메뉴 · 끌기) [스팸 아님]과 같이 스팸 아님 표시를 붙인다 — "메일 1통을 스팸이 아닌 메일로 표시해 거래처로 옮겼습니다." 휴지통으로는 표시를 바꾸지 않는다. [되돌리기]는 표시도 되돌린다.
  • 화면에 파일을 잘못 놓으면(메일 · 주소록 · 설정 — 로그인 뒤 모든 모듈 화면): 지금은 브라우저가 그 파일을 열어 웹메일을 떠났다 — 무시한다(놓는 동안 금지 커서). 쓰기 화면은 지금처럼 어디에 놓아도 첨부로, 서명 설정은 그림으로 받는다.
  • 상단 바 좁은 폭(360 — 바 안쪽 256): [로그아웃]은 줄지도 꺾이지도 않는다. 이름 · 주소는 넘치면 끝을 "…"로 줄이고 마우스를 올리면 다 보인다(title "김아라 (ara.kim@arasystem.it.kr)"). 화면 폭 480 이하에서는 주소를 감추고 이름만 둔다. 줄 수와 차례(메일 · 검색 칸 · 계정)는 지금과 같다 — 계정 줄을 첫 줄로 올리지 않는다(Tab 차례). 검색 칸은 바 안쪽 폭에 맞게 준다.
  • 같은 대화 [모두 펼치기]: 칸 머리 오른쪽의 글자 단추(36 — 펼친 줄의 [답장]과 같은 크기, 안내 줄 단추 --size-notice-action 재사용). 불러온 줄(지금 보는 메일 빼고)을 모두 펼치고 [모두 접기]로 바뀐다 — 줄을 하나씩 다 펼쳐도 [모두 접기]이고, 하나라도 접혀 있으면 [모두 펼치기]. [더 보기]로 더 받은 줄은 접힌 채로 온다. 본문은 한꺼번에 받지 않는다 — 화면에 들어오는 줄부터 하나씩(요청 수 한도와 화면 무게), 아직 화면 밖인 줄은 받는 중과 같은 자리만 둔다. 읽음은 지금 규칙 그대로 — 본문이 화면에 보인 메일만 읽음이 되고 펼쳤어도 화면 밖이면 그대로다.
  • 펼친 줄 머리 [답장]: 펼친 줄 머리 오른쪽(36) — 긴 메일을 끝까지 내리지 않고 답장한다. 줄 머리 전체가 펼침 단추라 머리를 감싸는 틀을 두고 펼침 단추와 [답장]을 나란히 놓는다(마우스를 올린 바탕은 틀 전체). 아래쪽 [답장] · [이 메일만 열기]는 그대로이고, 임시 저장본 · 예약한 메일의 줄에는 없다(지금과 같다).

메일함 ⋯ 「옮기기」 · 옮기기 창 (Mailbox15 1~4)

  • ⋯ 메뉴: 내 메일함(역할 없는 메일함 — 받은편지함 아래에 만든 메일함 포함)에만 「옮기기」. 차례는 모두 읽음으로 표시 · 하위 메일함 만들기 · 이름 바꾸기 · 옮기기 · 지우기. 창을 여는 다른 항목처럼 이름 뒤에 …를 붙이지 않는다. 메일함을 지우는 동안에는 지금처럼 모든 항목을 누를 수 없다.
  • 옮기기 창(모달 400): 제목 "‘2025’ 메일함을 어디로 옮길까요?" · 글 "하위 메일함과 안의 메일도 함께 옮깁니다."(하위가 없으면 "안의 메일도 함께 옮깁니다.") → 옮길 곳 목록 → [옮기기] [취소].
    • 목록(테두리 칸 · 줄 40 · 메일함 패널과 같은 차례 · 들여쓰기 10 · 28 · 44 · 56 · 패널 아이콘): 「맨 위」(위 화살표 — "다른 메일함 안에 두지 않습니다") · 받은편지함(과 그 아래) · "내 메일함" 묶음. 보낸편지함 · 임시보관함 · 스팸메일함 · 휴지통은 없다(하위 메일함을 만들 수 있는 곳과 같다). 메일함이 많으면 목록 칸 안에서 스크롤한다.
    • 처음에는 아무 곳도 고르지 않은 채 열리고 [옮기기]는 꺼져 있다. 고른 줄은 패널의 지금 메일함처럼 청록 바탕 · 굵은 글자 · 오른쪽 체크.
    • 고를 수 없는 줄은 이름 · 아이콘만 흐리게(까닭 글은 그대로): 옮기는 메일함과 그 하위(까닭 없음) · 지금 있는 곳("지금 있는 곳" — 맨 위의 메일함이면 맨 위 줄) · 바로 아래에 같은 이름의 메일함이 있는 곳("같은 이름의 메일함이 있습니다" — 대소문자를 가리지 않는다, 서버에 묻기 전에 화면이 하는 검사).
    • 줄은 radiogroup 의 radio 다. 화살표로 줄을 옮기면 고를 수 있는 줄은 바로 고르고, Enter 는 [옮기기], Esc 는 [취소]와 같다. 들여 쓴 줄은 위 메일함부터 읽는다("거래처 안의 2025").
  • 그사이 같은 이름이 생겼을 때(드묾 — 409 MAIL_009): 목록 아래 빨간 사전 문구 "같은 이름의 메일함이 이미 있습니다." — 알림 없이 창을 둔다. 다른 곳을 고르면 사라진다. 그사이 메일함이 사라졌으면(RES_001) 알림을 띄우고 창을 닫은 뒤 메일함 목록을 다시 받는다(이름 바꾸기 창과 같다). 옮기는 동안에는 [옮기기]를 다시 누를 수 없다.
  • 옮긴 뒤: 메일함 패널에 바로 나타나고(하위 메일함과 안의 메일이 함께 간다 · 그 메일함으로 옮기는 필터도 그대로 돈다), 보던 메일함이면 그 메일함을 계속 본다. 알림 "‘2025’ 메일함을 ‘프로젝트’ 안으로 옮겼습니다."(받은편지함은 따옴표 없이 "…받은편지함 안으로 …") · 맨 위로는 "‘2025’ 메일함을 다른 메일함 밖으로 옮겼습니다." + [되돌리기](원래 있던 곳으로 — "‘2025’ 메일함을 ‘거래처’ 안으로 되돌렸습니다.").

하위 메일함까지 지우기 (Mailbox15 5)

  • 하위 메일함이 있는 메일함의 「지우기」는 지금까지의 "지울 수 없습니다" 창 대신 확인 창(처음 초점 [취소]): "‘거래처’ 메일함과 하위 메일함 2개를 지울까요?" · "하위 메일함에 든 메일까지 모두 40통을 휴지통으로 옮깁니다. 휴지통에 들어간 메일은 30일 뒤 자동으로 지워집니다. 지운 메일함은 되돌릴 수 없습니다." [지우기] [취소]. 메일이 하나도 없으면 둘째 글은 "하위 메일함까지 모두 비어 있습니다. 지운 메일함은 되돌릴 수 없습니다." 하위 메일함 수 · 메일 수는 모든 깊이를 센다.
  • 확인하면 같은 지우기를 하위까지(?withChildren=true) 보낸다 — 진행 · 결과는 지금 메일함 지우기 그대로("‘거래처’ 메일함을 지우는 중 · 12 / 40통" → "‘거래처’ 메일함을 지웠습니다. 안의 메일 40통은 휴지통에 있습니다."). 휴지통을 비우는 동안에는 [지우기]를 누를 수 없다(지금과 같다).

보낸 사람 차단 창 — 받은편지함의 메일도 옮기기 (Mailbox15 6~7)

  • 「이 메일도 스팸메일함으로 옮기기」 아래에 「받은편지함에 있는 이 보낸 사람의 메일도 스팸메일함으로 옮기기」 + "옮긴 메일은 한꺼번에 되돌릴 수 없습니다." — 창을 열 때마다 꺼진 채 시작한다. 「이 도메인 전체」를 고르면 "…이 도메인의 메일도 …"(그 도메인만 — 하위 도메인은 보지 않는다). 두 칸은 붙여 두고, 칸 글이 두 줄이면 고르기 상자를 첫 줄에 맞춘다. 칸 아래 글은 글 선(28)에 맞춘다.
  • 새 칸을 켰는데 이 메일이 받은편지함에 있으면 첫 칸은 켜진 채 잠기고(aria-disabled) 아래에 "받은편지함의 메일을 옮길 때 함께 옮깁니다." — 새 칸을 끄면 첫 칸은 원래 값으로 돌아온다. 이 메일이 다른 메일함에 있으면 잠그지 않는다. 스팸메일함의 메일이면 첫 칸은 지금처럼 없고 새 칸은 남는다.
  • 이 탭에서 받은편지함을 정리하는 동안(필터 적용 · 다른 차단 정리)은 새 칸을 끄고(aria-disabled) 칸 아래 글을 "받은편지함을 정리하는 중이라 지금은 고를 수 없습니다."로 바꾼다. 이 탭에서 스팸메일함을 비우는 동안도 끄고 "스팸메일함을 비우는 중이라 지금은 고를 수 없습니다."로 바꾼다(보드에 없다 — 아래 "15단계 구현하며 정한 보완").
  • 차단한 뒤: 새 칸을 켜지 않았으면 지금 알림 그대로(+ [되돌리기]). 켰으면 차단 알림을 따로 띄우지 않고 알림 하나가 진행을 보이다 같은 자리에서 결과로 바뀐다 — [되돌리기]는 없다(차단은 설정에서 푼다): "‘news@example.com’을(를) 차단했습니다. 받은편지함의 메일을 스팸메일함으로 옮기는 중 · 1,200 / 4,800통 확인"(확인한 메일 / 받은편지함의 메일 수 — 첫 응답 전에는 받은편지함의 메일 수) → "‘news@example.com’을(를) 차단하고 받은편지함의 메일 37통을 스팸메일함으로 옮겼습니다." + [스팸메일함 보기] · 옮길 메일이 없으면 "…차단했습니다. 받은편지함에는 옮길 메일이 없습니다."(단추 없음).
    • 멈춤: "…받은편지함은 4,800통 가운데 1,200통까지 확인해 12통을 옮긴 뒤 멈췄습니다. 다시 시도해 주세요." + [다시 시도](멈춘 자리부터 · 수는 이어 센다)
    • 처음부터 실패: "…차단했지만 받은편지함의 메일은 옮기지 못했습니다. 다시 시도해 주세요." + 다시 시도
    • 다른 창에서 하는 중: "…차단했습니다. 다른 창에서 받은편지함이나 스팸메일함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요." + [다시 시도] — 차단한 뒤에는 ⋯ 메뉴가 「차단 풀기」라 다시 고를 길이 이것뿐이다. 서버의 차단 정리는 받은편지함 정리(필터 적용)뿐 아니라 스팸메일함 비우기와도 잠금을 나누어 두 곳을 함께 말한다(2026-11-09 재리뷰). 필터 적용의 429 글은 "다른 창에서 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요." 그대로다(7단계의 "다른 창에서 필터로 …"를 이 글로 바꿨다).
    • 그동안 다른 화면으로 가도 이어 하고, 탭을 닫거나 로그아웃하면 멈추고 알림을 닫는다. 필터 적용과 이 일은 한 직원에 한 번에 하나다. 잘못 옮긴 메일은 스팸메일함에서 [스팸 아님]으로 돌려놓는다.

예약한 메일 [지금 보내기] (Reader15 4~5)

  • 예약한 메일의 청록 안내 줄 단추가 [지금 보내기] [고치기] [예약 취소] 차례다(같은 흰 단추 36 — 누르는 동안 세 단추를 함께 막는다). 예약됨 목록 줄 · 휴대폰 앱에는 두지 않는다.
  • 누르면 [보내기]를 누른 것과 같다 — 보내기 취소 설정의 초만큼 기다렸다 보내고, 그동안 "메일을 보냈습니다." + [보내기 취소] + 남은 시간 막대(9단계 마감 알림 그대로). 확인 창은 두지 않는다. 끔으로 두었거나 예약 시각이 막 지나 이미 보낸 메일이면 오류가 아니라 "메일을 보냈습니다." + [보낸편지함 보기]. 받는 사람 일부가 받지 못하면 쓰기 화면과 같은 문장이다.
  • 알림의 [보내기 취소]는 지금의 [보내기 취소]와 같다(그 메일을 쓰기 화면으로 — 다시 예약하려면 [보내기 ▾] → [예약 발송…]).
  • 보낸 메일은 예약됨에서 빠지므로 분할 보기는 다음 예약 메일을 열고 목록 보기는 예약됨 목록으로 간다([예약 취소]와 같다). 예약됨 수를 다시 맞춘다. 보낸편지함에서 연 메일이면 그 자리에 두고 안내 줄만 사라진다(날짜 칸은 보내는 동안 "보내는 중").
  • 예약 시각보다 먼저 보내므로 대용량 첨부 링크의 기간은 실제로 보낸 날부터 센다(서버).
  • 보내지 못했거나 결과를 모를 때(서버가 503 MAIL_001 로 답함 — 결과를 모르는 실패와 엔진이 거절해 임시보관함으로 되돌린 실패가 같은 꼴이다. 앞단의 502 · 504 · 시간 초과 · 연결 끊김(SYS_001)도 결과를 모르는 실패라 같은 갈래다): 사전 문구 알림은 끄고, 화면이 그 메일을 다시 받아 붙잡은 발송과 메일함으로 가른다(2026-11-09 15단계 리뷰 · 재리뷰).
    • 임시보관함: "지금 보내지 못해 임시보관함으로 옮겼습니다." + [임시보관함 보기] — 예약됨을 떠났으니 안내 줄을 내리고 이동은 [예약 취소]와 같다.
    • 예약이 그대로(예약을 거두기 전에 멈췄다 — 예약한 시각에 나간다): "지금 보내지 못했습니다. 다시 시도해 주세요." + [다시 시도](같은 요청 — 같은 메일은 요청 하나로 묶는다). 안내 줄은 그대로 둔다.
    • 예약만 풀렸다(보낸편지함에 있고 기다리는 발송이 없다 — 예약한 시각에 따로 나가지는 않는다): "메일을 보냈는지 확인하지 못했습니다. [다시 시도]를 누르면 아직 보내지 않았을 때만 보냅니다." + [다시 시도]. 이미 나갔을 수 있어(보내기 취소 0초로 바로 나감 · 응답만 잃음 · 그사이 예약 시각이 됨) 못 보냈다고 단정하지 않는다 — 그 글을 믿고 손으로 다시 보내면 두 통이 간다(2026-11-09 마지막 확인). [다시 시도]는 같은 요청이라 안전하다(이미 보낸 메일이면 서버가 200 으로 답한다). 예약이 아닌 것은 확인했으니 "…에 보낼 메일입니다." 안내 줄은 내린다 — 예약됨 · 보낸편지함 · 검색 어디서 열었든. 예약됨에서는 빠지므로 이동은 [예약 취소]와 같고, 보낸편지함 · 검색에서 연 메일은 그 자리에 둔다.
    • 다시 낸 발송이 보내기 취소를 기다린다: 보낸 것과 같다("메일을 보냈습니다." + [보내기 취소]).
    • 그 메일을 다시 받지 못했다: 위와 같은 "메일을 보냈는지 확인하지 못했습니다. …" + [다시 시도] — 보냈다 · 못 보냈다 · 되돌렸다고 말하지 않는다(닫힌 쪽). 안내 줄은 마지막으로 안 상태 그대로 두고, 다음 다시 맞추기에서 바로잡힌다.
    • 예약됨 수와 목록은 어느 쪽이든 다시 맞춘다. 받는 사람이 모두 받지 않으면(400 MAIL_010 · 503 MAIL_011 — 그 메일은 임시보관함에) 사전 문구 그대로다.
    • 알려진 한계: 드물게 [다시 시도]를 누르지 않고 알림을 닫으면 그 메일은 보내지 않은 채 보낸편지함에 남는다 — 9단계 예약 취소와 같은 한계다.

처음 판에서 미룬 것

메일함을 끌어 옮기기 · 순서 바꾸기(옮기기 창으로), 차단할 때 옮긴 메일의 한꺼번에 되돌리기, 예약됨 목록 줄의 단추, 옮기기 단축키, 휴대폰 앱의 [지금 보내기].

16단계 — 개인 설정 (2026-11-10 확정 — Contacts16 · Signature16 · Settings16)

직원이 혼자 할 수 있는 설정을 넓힌다 — 주소록 가져오기 · 내보내기, 여러 서명과 쓰기 화면 [서명 ▾], 부재중 응답의 서식 본문과 회사 밖 응답, 2단계 인증 휴대폰 바꾸기. 서버 약속은 계약 2절("서명" · "부재중 자동 응답") · 3절(주소록) · 5절("2단계 인증")이다. 글의 원본은 화면 상수(shared/constants/contacts.ts · mail-signature.ts · mail-vacation.ts · account-security.ts)이고 아래는 옮겨 적은 것이다. 새 크기 토큰 · 밀도 예외는 없다 — [사내 | 회사 밖] 미리 보기 전환 36 은 서명 미리 보기의 .segmented(--size-segment-item-lg)를 다시 쓴다. 새 아이콘은 upload 하나다. 문구는 위 「화면 문구 — 사용자에게 말하듯」을 따른다.

주소록 가져오기 · 내보내기 (Contacts16)

다른 메일이나 휴대폰에서 내보낸 연락처 파일을 주소록에 넣고, 내 주소록을 파일로 내려받는다. 서버 약속은 계약 3절("가져오기 · 내보내기"). 새 크기 토큰 · 밀도 예외는 없다(머리 52 · 단추 44 그대로). 새 아이콘은 올리기(upload) 하나다.

  • 자리: 「전체 연락처」 목록 머리 오른쪽에 [가져오기] · [내보내기](도구 단추 — 그룹 머리의 [구성원 넣기]와 같은 꼴). 그룹 · 사내 목록의 머리는 그대로다(그룹 하나만 내보낼 때는 내보내기 창에서 고른다).
    • 연락처가 하나도 없으면 [내보내기]를 끄고(aria-disabled) 까닭 "내보낼 연락처가 없습니다."(마우스를 올리면 뜨는 글 · 화면 낭독).
    • 연락처가 3,000개면 [가져오기]도 끄고 패널의 3,000개 안내를 까닭으로 쓴다. 가져오는 동안(되돌리기 포함)은 [가져오기]가 바쁜 표시(aria-busy)이고 눌러도 아무 일이 없다.
    • 빈 주소록 안내: [연락처 추가] 옆에 테두리 단추 [가져오기], 그 아래 "다른 메일이나 휴대폰에서 내보낸 연락처 파일이 있으면 [가져오기]로 한 번에 넣을 수 있습니다." + 지금 줄("메일을 읽다가 …").
  • 가져오기 창 ① 파일 고르기(넓은 창 620): 제목 "연락처 가져오기" · "네이버, Google, Outlook 같은 메일이나 휴대폰에서 내보낸 연락처 파일을 그대로 넣을 수 있습니다."
    • 놓는 자리(168 · 회색 점선): 올리기 아이콘 · [파일 고르기](처음 초점 — .vcf · .csv · .xlsx 만 보이게 걸러 둔 파일 고르는 창) · "파일을 여기로 끌어다 놓아도 됩니다."
    • 창 어디에 끌어다 놓아도 받는다 — 끄는 동안 자리가 2px 청록 점선 · 옅은 청록 바탕, "놓으면 이 파일에서 연락처를 찾습니다". 창 밖 주소록 화면에 놓은 파일은 지금처럼 무시한다.
    • 읽는 동안: 도는 아이콘 · "파일을 읽는 중…" · 파일 이름. 그사이 [취소]는 읽기를 멈추고 창을 닫는다. 화면 낭독기에는 창을 열 때부터 있는 상태 영역(role=status — 글만 바꾼다)이 같은 글을 읽는다(글과 함께 생기는 영역은 읽지 않기도 한다). 읽다가 뜻밖에 멈추면(깨진 파일 등) "읽는 중"에 머물지 않고 "열지 못했습니다" 글로 돌아온다.
    • 아래 "vCard(.vcf), CSV(.csv), 엑셀(.xlsx) 파일을 넣을 수 있습니다. 파일 하나는 20MB까지입니다." · [취소].
    • 읽지 못하면 자리 아래 빨간 글로만(알림 없음) — 다른 파일을 고르거나 놓으면 지운다:
      • 넣을 수 없는 형식 "넣을 수 없는 파일입니다. vCard(.vcf), CSV(.csv), 엑셀(.xlsx) 파일을 골라 주세요."
      • 옛 엑셀 "옛 엑셀 파일(.xls)은 넣을 수 없습니다. 엑셀에서 [다른 이름으로 저장]을 눌러 .xlsx나 CSV 파일로 저장한 뒤 넣어 주세요."
      • 20MB 넘음 "파일이 너무 큽니다. 파일 하나는 20MB까지 넣을 수 있습니다."(읽지 않고 바로)
      • 연락처를 찾지 못함 "이 파일에서 연락처를 찾지 못했습니다. 이름이나 이메일이 든 열이 있는지 확인해 주세요."(vCard 는 앞 문장만)
      • 열지 못함 "이 파일을 열지 못했습니다. 암호를 건 엑셀 파일이면 암호를 푼 뒤 넣어 주세요."
      • 여러 파일 "파일은 한 번에 하나만 넣을 수 있습니다."
    • 형식은 이름 끝과 파일 앞부분을 함께 본다 — 이름이 .csv 여도 안이 엑셀이면 엑셀로, 안이 옛 엑셀(.xls)이면 .xls 글.
  • 가져오기 창 ② 미리 보기: 파일 이름 + [다른 파일 고르기](파일 고르는 창을 바로 연다 — 고른 그룹과 체크 칸은 그대로) · "연락처 340명을 찾았습니다."(넣지 못하는 줄까지 센 수).
    • 세 묶음(테두리 상자 — 글과 숫자로만, 뱃지 없음):
      • "넣을 연락처 · 330명" — 앞 6명의 표(이름 64 · 이메일 · 전화 96 · 회사 · 그룹 76) + "그 밖에 324명".
      • "이미 있는 연락처는 건너뜁니다 · 8명" — 눌러 펼친다(aria-expanded). 줄마다 "4번째 줄 · 박지훈 jihoon.park@example.com · 주소록에 있음"(또는 "2번째 줄과 같음").
      • "넣지 못하는 줄 · 2개" — 눌러 펼친다. "57번째 줄 · 010-9034-6621 · 이름과 이메일이 모두 없습니다."(까닭은 갈색).
      • 펼친 목록은 6줄 높이(186)까지 늘고 넘으면 목록 안에서 넘긴다. 줄 번호는 엑셀에서 보이는 행 번호(머리 줄이 1번째 줄), vCard 는 "12번째 연락처"이고 묶음 이름이 "넣지 못하는 연락처"다.
    • 그룹: "파일에 적힌 그룹 4개 가운데 거래처, 세무는 이미 있는 그룹에 넣고 동호회, 학교는 새로 만듭니다."(이름이 많으면 앞 셋과 "외 N개", 없으면 "파일에 적힌 그룹이 없습니다.").
    • 「가져온 연락처를 모두 넣을 그룹」: 넣지 않음(처음) · 이미 있는 그룹(고르기 칸 280×44 — "거래처 · 4명") · 새 그룹(이름 칸 280×44 · 50자) + "파일에 적힌 그룹에 더해 넣습니다." 새 그룹 이름이 이미 있는 그룹과 같으면 그 그룹에 넣는다. 이름을 비운 채 넣으려 하면 칸 아래 "그룹 이름을 넣어 주세요." 그룹이 100개면 「새 그룹」을 끄고 "그룹은 100개까지 만들 수 있습니다."
    • 체크 칸 「주소, 생일, 홈페이지, 별명처럼 주소록에 칸이 없는 정보는 메모 끝에 붙이기」(처음 켜짐) + "메모가 1,000자를 넘게 되면 붙이지 않습니다." — 켜 두면 견본 "예: 강민지의 메모"(원래 메모는 흐리게 · 빈 줄 · "가져온 정보" · "회사 주소: 서울 중구 세종대로 110" · "생일: 1988년 3월 14일 (음력)" · "홈페이지: …" · "별명: …").
    • 남은 자리보다 많으면 노란 상자 "연락처는 3,000개까지 둘 수 있어 앞에서부터 188명만 넣습니다. 나머지 142명은 넣지 않습니다." — 단추의 수도 그만큼.
    • [연락처 330명 넣기](처음 초점) [취소]. 넣을 연락처가 없으면 [연락처 넣기]를 끄고(aria-disabled) 단추 왼쪽에 "넣을 연락처가 없습니다."
    • Esc · [취소]는 묻지 않고 닫는다(아직 아무것도 넣지 않았다). 창이 화면보다 길면 창 안이 스크롤하고 단추 줄은 고정하지 않는다(필터 창과 같다).
  • 넣는 동안 · 끝난 뒤(알림 — 필터 적용 · 차단 정리와 같은 결): [연락처 N명 넣기]를 누르면 창을 닫고 진행 알림 "연락처를 넣는 중 · 120 / 330명"(넣은 수 / 넣을 수 — 아래 「요청 묶음」 하나가 끝날 때마다 고친다).
    • 끝: 같은 자리에서 "연락처 330명을 넣었습니다. 이미 있던 8명은 건너뛰었습니다." + [되돌리기] + ✕ — ✕로 닫을 때까지 남는다(저절로 닫히지 않는다). 건너뛴 수 · 한도에 걸린 수는 한 문장씩 덧붙인다 — "연락처는 3,000개까지 둘 수 있어 나머지 142명은 넣지 못했습니다." · "그룹은 100개, 그룹 하나에는 500명까지 둘 수 있어 24명은 그룹에 넣지 못했습니다."
    • [되돌리기]: "가져온 연락처를 지우는 중 · 100 / 330명" → "가져온 연락처 330명과 새로 만든 그룹 2개를 지웠습니다."(새 그룹이 없으면 "가져온 연락처 330명을 지웠습니다."). 이번에 넣은 연락처와 이번에 새로 만든 그룹만 지운다 — 원래 있던 그룹에 넣은 것은 연락처를 지우며 함께 빠진다. 되돌리다 멈추면 "가져온 연락처 330명 가운데 100명을 지우고 멈췄습니다. 다시 시도해 주세요." + [다시 시도].
    • 멈춤: 흰 오류 알림 "연락처 330명 가운데 120명을 넣고 멈췄습니다. 다시 시도해 주세요." + [다시 시도](남은 연락처부터 — 줄마다 화면이 붙인 표시(uid)가 있어 두 번 들어가지 않는다), 처음부터 넣지 못했으면 "연락처를 넣지 못했습니다. 다시 시도해 주세요." 멈춘 알림에는 [되돌리기]가 없다 — 다시 시도해 끝낸 뒤의 [되돌리기]가 이번에 넣은 연락처를 모두 지운다. 알려진 한계: 멈춘 알림을 다시 시도하지 않고 닫으면 그때까지 넣은 연락처는 남는다(같은 파일을 다시 넣으면 이미 있는 연락처로 건너뛴다).
    • 넣는 동안 다른 화면으로 가도 이어 넣는다. 탭을 닫거나 로그아웃하면 멈추고 알림을 닫는다. 끝나거나 멈추면 연락처 수 · 그룹 목록 · 보고 있는 목록을 다시 받는다. 실패 까닭(연결 · 서버)은 지금처럼 오류 사전의 글로 알리고 진행 알림은 결과로만 바뀐다.
  • 내보내기 창(400 — 새 그룹 창과 같은 폭): 제목 "연락처 내보내기".
    • 「내보낼 연락처」 내 주소록 전체("연락처 342명" — 처음) · 그룹 하나("고른 그룹의 구성원만" — 고르면 그룹 고르기 칸 44, 첫 그룹이 골라진다, "거래처 · 24명") + "내 주소록의 연락처만 내보내고 사내 목록은 넣지 않습니다."
    • 「파일 형식」 vCard (.vcf) "다른 메일이나 휴대폰으로 옮길 때"(처음) · CSV (.csv) "엑셀에서 열어 볼 때". 고른 상자는 청록 테두리 · 옅은 청록 바탕.
    • "파일 이름 주소록-2026-11-10.vcf"(그룹이면 "주소록-거래처-2026-11-10.csv" — 날짜는 이 PC 의 날짜, 파일 이름에 쓸 수 없는 글자는 _).
    • 구성원이 없는 그룹을 고르면 [내보내기]를 끄고(aria-disabled) 그룹 칸 아래 "‘협력사 담당’ 그룹에는 아직 아무도 없어 내보낼 연락처가 없습니다." — 다른 그룹을 고르면 바로 돌아온다.
    • [내보내기] [취소] — 누르면 창을 닫고 바로 내려받는다(첨부 받기처럼 이동 직전에 로그인을 확인, 파일은 서버가 만든다). 따로 알림은 띄우지 않는다.
  • 받는 파일 · 칸 맞추기: vCard 2.1 · 3.0 · 4.0(한 파일에 여러 명), CSV(네이버 · Google 새 · 옛 머리 · Outlook · 우리가 내보낸 꼴을 머리 줄로 알아보고, 모르는 꼴은 열 이름으로 짐작), 엑셀 .xlsx(첫 시트 · 첫 줄이 머리). CSV 글자는 BOM, UTF-8, 아니면 EUC-KR(한국어 엑셀의 CSV) 차례. 성 + 이름은 이름 하나로(한글끼리는 붙여 쓴다 — 강 + 민지 = 강민지), 이메일과 전화는 종류대로(팩스와 직접 입력한 번호는 기타), 전화는 하이픈을 넣는다(010-1234-5678). 이미 있는 연락처는 같은 이메일(앞뒤 공백 · 대소문자 무시), 이메일이 없으면 이름과 전화번호가 모두 같을 때다.

설정 › 메일 › 서명 — 여러 서명 (Signature16 1~5)

서버 약속은 계약 2절 "서명"(16단계 — /api/mail-signatures · /api/mail-signature-defaults)이다. 글의 원본은 화면 상수(shared/constants/mail-signature.ts)이고 아래는 옮겨 적은 것이다. 새 크기 토큰 · 밀도 예외는 없다 — 목록 줄 52 는 --size-header-min, 칸 · 단추 44 는 보통 크기다.

  • 배치: 머리(제목 "서명" · 설명) → 「메일에 넣을 서명」(서명이 있을 때만) → 왼쪽 「내 서명」 목록(208) · 오른쪽 고른 서명 고치기(이름 · 서명 칸 · 안내 · [저장] … [이 서명 지우기] · 가는 선 · 미리 보기). 콘텐츠 열은 다른 설정 화면과 같은 800. 목록과 고치는 칸을 담은 칸이 640보다 좁으면 목록을 위로 올려 칸 폭을 쓰게 하고 고치는 칸을 그 아래에 둔다 — 창 폭이 아니라 그 칸의 폭을 본다(컨테이너 쿼리). 설명은 "보내는 메일 끝에 넣는 글입니다. 여러 개 만들어 두면 메일을 쓸 때 [서명]에서 골라 바꿀 수 있고, 그 메일에서만 고치거나 지울 수도 있습니다."
  • 목록: 만든 차례 · 줄 52 · 고른 줄은 메일함 패널의 지금 메일함처럼 청록 바탕 · 굵은 글자(aria-current). 이름 아래 회색 12 는 그 서명을 기본으로 넣는 메일("새 메일" · "답장과 전달" · "새 메일, 답장과 전달" — 뱃지 없음). 목록 아래 [새 서명](글자 단추 44) · "서명 3개를 만들었고, 10개까지 만들 수 있습니다." — 10개면 [새 서명]은 누를 수 없고(aria-disabled — 초점은 받는다) 글이 "서명은 10개까지 만들 수 있습니다. 쓰지 않는 서명을 지워 주세요."로 바뀐다(단추가 aria-describedby 로 가리킨다). 화면 낭독기는 목록을 「내 서명」으로, 고른 줄을 지금 항목으로 읽는다.
  • 처음 고른 서명: 새 메일에 넣는 서명, 새 메일이 넣지 않음이면 첫 서명.
  • 메일에 넣을 서명(지금의 두 체크를 대신한다): 칸 이름 84 · 고르기 240(서명 이름들 · 넣지 않음) · 아래 "메일을 쓰기 시작할 때 본문에 넣어 둡니다. 고르는 대로 바로 저장됩니다." 고르는 대로 저장하고, 끝나면 고른 칸 오른쪽에 초록 ✓ "저장했습니다"(role=status) — 다시 고르면 내린다. 저장은 한 번에 하나(보내기 · 읽기 설정과 같은 결 — 보내는 동안 고른 것은 끝난 뒤 다르면 보낸다), 끝내 저장하지 못하면 고르기 전 값으로 되돌리고 오류 알림. 고른 것은 나가기 확인의 까닭이 아니다.
  • 이름: 칸 320(좁으면 칸 폭) · 30자에서 더 들어가지 않는다 · 아래 "메일을 쓸 때 [서명]에서 고르는 이름입니다. 30자까지 쓸 수 있습니다." [저장]할 때 비었으면(앞뒤 공백을 뺀다) "이름을 넣어 주세요.", 다른 서명과 같으면(영문 대소문자 무시) "같은 이름의 서명이 있습니다." — 안내 대신 빨간 글 · 칸 빨간 테두리(aria-invalid) · 이름 칸에 초점, 알림은 없다. 이름을 고치면 안내로 돌아간다. 저장하면 이름은 앞뒤 공백을 뺀 글로 바뀐다.
  • [저장] 뒤 글: 기본으로 넣는 서명이면 지금 글 "저장했습니다. 다음에 메일을 쓸 때부터 들어갑니다.", 아니면 "저장했습니다." — 무엇이든 다시 바꾸면 내린다.
  • 편집기 · 그림 · 미리 보기는 지금 서명 화면 그대로(그림 서명마다 3장 · 크기 줄 · 화면 어디에 놓은 그림은 지금 고른 서명에). 미리 보기는 고른 서명이 [새 메일 | 답장]에 들어갈 자리를 늘 보인다 — 아래 글 "새 메일은 본문 끝에 서명을 넣습니다." · "답장과 전달에는 인용한 원문 위에 서명을 넣습니다."(그 메일에 넣을지는 「메일에 넣을 서명」이 정한다).
  • 서명이 없을 때(2): 점선 상자 안 펜 아이콘 36 · "아직 서명이 없습니다" · "보내는 메일 끝에 넣을 이름, 소속, 연락처를 만들어 두면 메일을 쓸 때마다 본문에 넣어 둡니다. 사내용과 대외용처럼 여러 개를 만들어 골라 쓸 수도 있습니다."(최대 400) · [새 서명](채운 단추). 「메일에 넣을 서명」 칸과 목록은 없다(고를 서명이 없다). 지금 서명을 쓰던 직원은 서버가 그 서명을 「기본 서명」으로 옮겨 두어 1의 모습으로 시작한다.
  • 새 서명(3): 목록 끝에 "새 서명 / 저장하지 않음" 줄 · 빈 이름 칸(안 글 "예: 사내, 대외, 영문")에 초점 · 빈 서명 칸 · [저장] [취소]. 처음 [저장]에 만들고 그 뒤로는 같은 칸에서 고친다(쓰던 자리 · 실행 취소 기록이 남는다). 첫 서명이면 두 곳의 기본이 그 서명이 된다(서버가 정한다). 그 밖의 새 서명은 기본을 바꾸지 않는다. [취소]는 새 줄을 빼고 전에 고른 서명을 연다(서명이 없었으면 2의 화면). 새 서명을 연 채 [새 서명]을 누르면 이름 칸으로 간다.
  • 지우기(4): [이 서명 지우기](빨간 글자 단추 · [저장]과 떨어뜨려 오른쪽 끝) → 확인 창(폭 360 · 처음 초점 [취소] · Esc 도 [취소]) "‘대외’ 서명을 지울까요?" + 기본이던 곳마다 다른 글(새 메일 · 답장과 전달 · 둘 다 · 기본 아님 "지운 서명은 되돌릴 수 없습니다.") · [지우기](빨강 채움) [취소]. 지우면 알림 "‘영문’ 서명을 지웠습니다." · 목록의 첫 서명을 연다(초점은 그 줄) — 마지막 서명이면 2의 화면(초점은 [새 서명]). 그 서명을 기본으로 넣던 곳은 넣지 않음이 되고 다른 서명이 저절로 기본이 되지 않는다.
  • 저장하지 않은 것이 있을 때(5): 다른 서명을 고르면 "다른 서명을 고르면 ‘대외’ 서명에서 바꾼 내용이 사라집니다.", [새 서명]이면 "새 서명을 만들면 ‘대외’ 서명에서 바꾼 내용이 사라집니다." — 제목 "저장하지 않은 서명이 있습니다" · [계속 고치기](처음 초점 · Esc) [바꾼 내용 버리기]. 바꾼 것이 없으면 묻지 않는다. 다른 화면으로 갈 때는 지금 나가기 확인 그대로("이 화면을 나가면 바꾼 서명이 사라집니다." · [계속 고치기] [나가기] — 저장하는 중 · 세션이 끝났을 때는 묻지 않고, 탭을 닫으면 브라우저가 묻는다).

쓰기 화면 [서명 ▾] (Signature16 6)

  • 자리: 아래 단추 줄 [보내기 ▾] [임시 저장] [서명 ▾] — 서명은 서식이 아니라 메일 전체에 붙는 것이라 편집기 툴바(좁은 폭에서 접는 차례)에 넣지 않았다. [임시 저장]과 같은 글자 단추(44)에 ▾ 12. 화면 낭독은 툴바의 글꼴 단추처럼 지금 서명까지 — "서명: 대외" · "서명: 넣지 않음"(본문의 서명 자리가 어느 서명과도 같지 않으면 "서명"만). 편집기를 불러오는 중 · 보내는 중에는 누를 수 없다.
  • 메뉴(편집기 메뉴 — 항목 40 · 폭 220, 긴 이름은 끝을 …): 서명(만든 차례 · 지금 본문에 든 서명에 ✓ · "기본" 표시 없음) · 넣지 않음 · 가는 선 · 서명 관리. 서명이 없으면 넣지 않음과 서명 관리만. 키보드는 [보내기 ▾]와 같다 — Enter · Space · 아래 화살표로 열면 첫 항목에 초점(마우스로 열면 메뉴 자체), Esc 는 메뉴만 닫고 [서명 ▾]로. 아래 자리가 모자라면 위로 연다(아래 줄에서는 보통 위).
  • 고르면 본문의 서명 자리만 그 서명으로 바뀐다 — 쓴 글 · 인용 · 첨부는 그대로이고, 서명 안을 고쳤다면 고친 것도 새 서명으로 바뀐다. 실행 취소(Ctrl+Z)로 되돌린다. 바꾸면 화면 낭독기에 "서명을 ‘사내’로 바꿨습니다."(알림 없음). 넣지 않음은 서명 자리와 그 앞의 빈 줄을 뺀다. 서명 자리를 지운 뒤 서명을 고르면 처음 넣는 자리(새 메일은 쓴 글 끝, 답장과 전달은 인용 위)에 빈 줄 하나 뒤로 넣는다.
  • 처음 넣는 서명: 새 메일은 「새 메일」 기본, 답장 · 전달은 「답장과 전달」 기본(넣지 않음이면 넣지 않는다). 이어 쓰는 임시 저장본에는 다시 넣지 않는다 — 서명 자리의 글이 저장한 서명과 같으면 그 서명에 ✓, 다르면 ✓ 없이.
  • 서명 관리: 설정 › 메일 › 서명으로 — 지금 나가기 규칙대로 먼저 임시 저장하고 저장하지 못했을 때만 묻는다.
  • 받는 사람이 회사 밖이어도 저절로 바꾸지 않는다(처음 판 — 직원이 고르는 대로).

설정 › 메일 › 부재중 자동 응답 — 서식 본문 · 회사 밖 응답 (Settings16 1~4)

  • 사용 · 기간 · 제목 칸 · [저장] · 저장한 뒤의 글은 4단계 그대로다.
  • 내용 칸은 서명 칸과 같은 편집기(지연 로딩 — 못 받으면 서명 설정과 같은 사전 문구 + [다시 시도]): 툴바 52 · 굵게 · 기울임 · 밑줄 | 글자 색 · 링크(단추 40×44 — 그림 단추 없음, 붙여 넣은 그림 · 파일은 받지 않는다) · 본문 맑은 고딕 14 · 줄 간격 1.75 · 높이 150. 툴바의 화면 낭독 이름 "자동 응답 서식". 칸 아래 안내 "굵게, 기울임, 밑줄, 글자 색, 링크를 쓸 수 있습니다. 그림은 넣을 수 없고, 서명은 붙지 않습니다."
    • 글자 수는 칸 이름 오른쪽 "132 / 600"(12 · 회색 — 주소록 메모와 같은 자리). 서식을 뺀 보이는 글로 센다(받는 쪽의 글자 본문과 같은 글 — 링크는 "글 (주소)"). 켜 둔 채 600자를 넘으면 굵게 빨갛게.
    • 서식(굵게 · 기울임 · 밑줄 · 글자 색 · 링크)을 쓴 칸만 받는 사람에게 서식 본문과 글자 본문을 함께 보내고, 글만 쓴 칸은 글자 본문만 보낸다(서식 본문 null — 사내 · 회사 밖 칸 따로, 끄면서 하는 저장도 같다). 서버는 서식 본문 · 회사 밖 응답을 쓴 직원만 새 원본 판으로 저장한다 — 글만 쓰는 직원은 옛 판 그대로라 되돌리기 계획의 전제가 지켜진다(16단계 리뷰). 16단계 전에 저장한 글만 있는 응답은 줄마다 문단으로 열린다(빈 줄은 빈 문단).
  • 회사 밖에서 온 메일(내용 칸과 [저장] 사이 · fieldset): "보낸 사람 주소가 회사 메일 주소(@arasystem.it.kr)가 아닌 메일입니다."(도메인은 내 주소의 것) + 고르기 셋 — 보내기 · 읽기 설정의 외부 이미지 칸과 같은 모양(라디오 18 · 이름 굵게 · 아래 설명 12.5 · 줄 44): 사내와 같은 응답("회사 안팎 모두 위의 제목과 내용으로 답합니다." — 처음 값) · 보내지 않기("회사 밖에서 온 메일에는 자동으로 답하지 않습니다.") · 따로 쓰기("회사 밖에서 온 메일에 보낼 제목과 내용을 따로 씁니다."). 칸이 열리는 따로 쓰기가 맨 끝이다.
    • 따로 쓰기를 고르면 바로 아래(고르기 글자 선 28 에 맞춰 들여 씀 · 사이 18)에 "회사 밖에 보낼 제목"(최대 폭 520 · 150자) · "회사 밖에 보낼 내용"(같은 편집기 · 툴바 이름 "회사 밖 자동 응답 서식" · 같은 글자 수 · 아래 안내 없음). 위의 제목 · 내용은 이때부터 사내에서 온 메일에만 간다.
    • 다른 고르기로 바꾸면 두 칸을 닫지만 써 둔 글은 남겨 두고 함께 저장한다 — 다시 따로 쓰기를 고르면 그대로 나온다.
    • 자동 응답을 끄면 이 묶음도 흐려진다(고르기 글 회색 · 칸은 다른 칸처럼).
  • 미리 보기: 머리 오른쪽 전환 [사내 | 회사 밖](36 · 화면 낭독 이름 "미리 볼 답장" — 처음은 사내 쪽, 사내와 같은 응답일 때도 둔다). 칸을 고친 값을 받는 사람이 보는 서식 그대로 바로 그린다. 아래 글: 사내와 같은 응답 — 사내 쪽 "받은 메일의 보낸 사람에게 위 글로 답합니다. 같은 사람에게는 매번 보내지 않습니다."(지금 글) · 회사 밖 쪽 "회사 밖에서 온 메일에도 같은 글로 답합니다. …" / 보내지 않기 · 따로 쓰기 — 사내 쪽 "사내에서 온 메일에 위 글로 답합니다. …" · 따로 쓰기의 회사 밖 쪽 "회사 밖에서 온 메일에 위 글로 답합니다. …"(제목 · 내용은 회사 밖에 보낼 것) / 보내지 않기의 회사 밖 쪽은 답장 대신 회색 상자(최소 높이 120) "회사 밖에서 온 메일에는 자동으로 답하지 않습니다." · 아래 글 없음 / 꺼 두었으면 두 쪽 모두 "꺼져 있어 자동으로 답하지 않습니다."
  • 켤 때 검사(켠 채 [저장]일 때만 — 꺼 둔 채로는 빈 칸 · 긴 글도 저장한다): 보는 차례 기간 · 제목 · 내용 · 회사 밖에 보낼 제목 · 회사 밖에 보낼 내용, 첫 칸으로 초점. 알림은 띄우지 않는다.
    • 빈 칸은 사전 문구(VAL_001). 내용이 600자를 넘으면 칸 아래 "내용은 600자까지 쓸 수 있습니다." + 빨간 글자 수(회사 밖에 보낼 내용 칸도 같은 글).
    • 글은 600자 안인데 서식이 많아 서식 본문이 10,000자를 넘으면 사전 문구(VAL_001 — 서명과 같은 한도).
    • 편집기 칸은 빨간 테두리와 aria-invalid(설명은 오류 → 안내 차례). 그 칸을 고치기 시작하면 아래 글이 사라진다.
    • 회사 밖 두 칸은 따로 쓰기일 때만 본다 — 사내와 같은 응답 · 보내지 않기면 비어 있어도 저장한다. 서버가 거절하면 지금처럼 [저장] 옆에 사전 문구.
    • 서버가 MAIL_013(필터 · 차단 · 부재중을 담는 메일 규칙이 한도를 넘음)으로 거절하면 같은 자리에 "자동 응답이 너무 깁니다. 서식이나 내용을 줄여 주세요."(알림 없음) — 부재중은 서식 본문이 두 번 들어가 필터가 없어도 넘을 수 있어 사전 문구("필터나 차단 목록이 …") 대신 이 글이다(16단계 리뷰). 저장하는 사이 화면을 떠났으면 같은 글을 알림으로.

설정 › 계정 › 2단계 인증 — 휴대폰 바꾸기 (Settings16 5~8)

  • 켜져 있을 때 상태 상자의 [2단계 인증 끄기] 바로 앞에 [휴대폰 바꾸기](테두리 44 · 아이콘 없음). 회사 정책으로 끌 수 없는 직원도 "회사 정책에 따라 끌 수 없습니다" 앞 같은 자리에 있다. 꺼져 있을 때는 없다(켜기 창으로). 아래 안내의 첫 줄은 두 직원 모두 "휴대폰을 바꿀 때는 [휴대폰 바꾸기]를 눌러 주세요. 새 휴대폰과 지금 휴대폰의 코드가 모두 필요합니다."(둘째 · 셋째 줄은 그대로).
  • 바꾸기 창(모달 620 — 켜기 창과 같은 폭) "2단계 인증 휴대폰 바꾸기" · 제목 아래 차례 "새 휴대폰 등록 › 지금 휴대폰으로 확인"(12.5 · 글로만 — 지금 걸음 청록 굵게 · 마친 걸음 앞 초록 체크 · 남은 걸음 회색, 뱃지 · 동그라미 숫자 없음).
    • ① 새 휴대폰 등록: "새 휴대폰을 등록하고 지금 휴대폰으로 확인하면 바뀝니다. 바꾸기를 마칠 때까지는 지금 휴대폰의 코드로 로그인합니다." → 1 "새 휴대폰의 인증 앱으로 QR 코드를 찍어 주세요"(QR 160 · 키 4자씩 두 줄 · [복사] 36 — 켜기 창과 같은 부품) + "바꾸지 않고 닫으면 이 키는 더 쓸 수 없습니다. 새 휴대폰의 인증 앱에 먼저 추가했다면 앱에서 그 계정을 지워 주세요." / 2 "새 휴대폰의 앱에 표시된 6자리 코드" → [다음] [취소]. 처음 초점은 코드 칸. 키는 창을 열 때마다 새로 만들고 QR · 키는 이 창 안에만 두었다가 닫으면 지운다. [다음]은 새 휴대폰의 코드부터 확인한다(확인하는 동안 "확인하는 중…") — 맞으면 ②. 그동안 2단계 인증은 지금 휴대폰으로 그대로 켜져 있다.
    • ② 지금 휴대폰으로 확인: "새 휴대폰을 확인했습니다. 지금 휴대폰의 코드와 비밀번호를 넣으면 바뀌고, 그 뒤로는 새 휴대폰의 코드로만 로그인합니다." → "지금 휴대폰의 앱에 표시된 6자리 코드"(처음 초점) · "지금 비밀번호"(최대 320 · 보기 단추) → 위에 선을 둔 문의 줄 "지금 휴대폰을 쓸 수 없나요? 관리자에게 2단계 인증을 풀어 달라고 요청한 뒤, 새 휴대폰으로 다시 켜 주세요." (로그인 화면의 문의 줄과 같은 꼴 — 연락처가 있으면 다음 줄에) → [이전](왼쪽 · ‹) · [바꾸기] [취소]. 바꾸는 동안 "바꾸는 중…" · [바꾸기] · [이전] aria-disabled. [이전]은 ①로(QR · 키는 그대로 · 새 휴대폰의 코드 칸은 비운다 — 같은 코드는 2분 동안 다시 받지 않으니 앱의 다음 코드를 넣는다).
    • 틀렸을 때(알림 없이 그 칸 아래 사전 문구 · 그 칸에 초점 · 틀린 코드는 지운다): ① 새 휴대폰의 코드 AUTH_006 / ② 지금 비밀번호 AUTH_005 · 지금 휴대폰의 코드 AUTH_006(서버가 비밀번호를 먼저 보고 한 칸만 알린다). 보내기 전에 6자리가 아니면 AUTH_007 문구, 비밀번호가 비었으면 VAL_001 문구(칸 차례 — 코드 → 비밀번호). 시도가 너무 많으면 REQ_001 알림.
    • 10분이 지남 · 더 새 등록(AUTH_008 — ① · ② 어느 쪽이든): ①로 돌아와 창 위 빨간 상자에 사전 문구, 새 QR · 키로 다시 그리고 코드 칸을 비운다. 그사이 다른 곳에서 2단계 인증이 꺼졌으면 창을 닫고 사전 문구 알림(화면은 「꺼짐」).
    • 세션이 바뀌면(로그아웃 · 다른 기기의 [다른 곳 모두 로그아웃]) 키 · 칸을 지우고 창을 닫는다(켜기 창과 같다).
  • 바꾼 뒤: 창을 닫고 단추 없는 알림 "2단계 인증 휴대폰을 바꿨습니다. 이제 새 휴대폰의 코드로 로그인합니다." · 초점은 [휴대폰 바꾸기]. 화면 · 켠 때(「2026년 10월 31일부터 켜짐」)는 그대로다. [취소] · Esc 로 닫아도 서버가 이미 바꿨으면 화면을 맞추고 같은 알림을 띄운다. 로그인된 기기는 끊지 않고 알림 메일도 보내지 않는다(켜기 · 끄기와 같다).

처음 판에서 미룬 것

주소록: 칸 늘리기(주소 · 생일 · 홈페이지 · 별명 · 사진), 겹친 연락처 합치기, 모르는 CSV 의 열을 손으로 맞추기, 옛 엑셀(.xls), 여러 연락처 고르기 · 한꺼번에 지우기, 사내 목록 내보내기. 서명: 받는 사람에 따라 저절로 바꾸기, 휴대폰 앱에서 서명 고르기. 부재중: 「내 연락처에게만」, 그림 넣기. 2단계 인증: 복구 코드, 바꿀 때 알림 메일 · 계정 변경 기록.

구현하며 정한 보완 (2026-10-26 — 캔버스에는 아직 없다)

  • 찾지 못한 첨부(보관 시간이 지난 업로드·지워진 원본)는 실패로 표시하고, 전달 원본에는 "빼기"만 둔다(다시 올릴 수 없다).
  • 없는 메일함·메일(주소창에 남은 옛 id 등)에는 "다시 시도" 대신 "받은편지함으로"·"목록으로"를 둔다. "다시 시도"는 메일 서버 장애처럼 다시 해 보면 나아질 수 있는 오류에만 쓴다.
  • 읽기 화면의 본문 안내(2026-10-27 사용자 확정) — "외부 이미지를 차단했습니다"와 같은 모양(아이콘 alert-circle)의 안내 줄을 본문 위에 둔다. 순서는 생략 → 잘림 → 외부 이미지 차단이다. "표시할 수 없음"을 보일 때는 잘림 안내를 숨기고, 공백뿐인 글자 본문은 없는 것으로 본다:
    • 서버가 HTML 을 정화 한도 때문에 비웠을 때(isHtmlBodyOmitted): 글자 본문이 있으면 "본문이 복잡해 서식 없이 글자만 보여 드립니다.", 없으면 "본문이 너무 크거나 복잡해 여기서는 열 수 없습니다. [⋯] → [원문 내려받기 (.eml)]로 받아 볼 수 있습니다."
    • 보여 주는 본문이 읽기 한도(2MB)에서 잘렸을 때(isBodyTruncated): "본문이 길어 앞부분만 보여 드립니다. 전체는 [⋯] → [원문 내려받기 (.eml)]로 받아 볼 수 있습니다."

2단계 구현하며 정한 보완 (2026-10-27 — 캔버스에는 아직 없다)

  • 임시 저장
    • 쉬지 않고 쓰는 동안에도 처음 바꾼 때(또는 마지막 저장을 시작한 때)부터 30초가 지나면 저장한다 — 멈춤을 기다리다 오래 쓴 글을 잃지 않게.
    • 저장할 때마다 주소를 /mail/compose?draft=<새 id> 로 맞춘다 — 새로고침하거나 다시 열면 마지막 저장본을 이어 쓴다. 떠나기 전 저장은 주소를 바꾸지 않는다(가던 이동이 취소되므로). 대신 같은 탭에서 옛 ?draft= 로 돌아오면(뒤로 가기) 마지막 저장본을 연다 — 저장본이 바뀐 차례는 탭 메모리에만 두고 로그아웃 · 세션 만료 때 지운다.
    • 자동 저장 실패 가운데 메일 서버 장애·용량·요청 과다는 15초 → 30초 → 60초 뒤 저절로 다시 한다. 그 밖의 실패는 내용을 고치면 다시 저장한다.
    • 「임시 저장」은 아직 한 번도 저장하지 않았으면 바꾼 것이 없어도 저장한다 (빈 메일 · 미리 채운 답장 포함).
    • 형식이 틀린 주소 · 사전 검사에 걸린 첨부는 저장하지 않는다 (칸에는 남는다). 올리는 중인 첨부는 "저장하지 못한 내용"으로 친다.
    • 찾지 못한 첨부(MAIL_004)가 남아 있는 동안은 저장하지 않는다(「임시 저장」 · [다시 시도] 포함) — 첨부가 빠진 판으로 멀쩡한 저장본을 덮지 않게. 그 첨부를 빼면 다시 저장한다.
    • 본문이 한도를 넘으면 보내기처럼 인용을 줄여 저장한다(편집기 화면은 그대로). 인용을 다 줄여도 넘으면 저장하지 않고 보내기와 같은 안내를 본문 아래에 보이며, 내용을 고칠 때까지 다시 시도하지 않는다.
    • 다른 메일 프로그램이 만든 저장본에 우리가 실을 수 없는 첨부(허용 목록 밖 · 개수 · 합계 초과)가 있으면, 첫 저장은 원본을 지우지 않고 새 저장본으로 남긴다 — 원본은 첨부째 임시보관함에 그대로 있다.
    • 떠나기 전 저장은 5초까지만 기다린다. 끝나지 않으면 "저장하지 못한 내용이 있습니다"로 묻고, 묻는 사이에 저장이 끝나면 그대로 떠난다.
    • 보내는 중에 화면을 떠났는데 발송이 실패하면, 보내려던 내용을 임시 저장본으로 한 번 남긴다 (실패 알림은 그대로).
    • 알려진 한계: 떠나기 전 저장이 첫 저장이었던 새 메일 · 답장 · 전달은, 뒤로 가기로 돌아오면 새 양식이 열린다 (저장본은 임시보관함에 있다).
    • 휴지통 단추는 저장본을 휴지통 메일함으로 옮긴다(삭제 요청이 아니다 — 다른 창에서 먼저 휴지통으로 옮긴 것을 영구 삭제하지 않게). [되돌리기]로 되돌리면 "임시보관함으로 되돌렸습니다." 알림을 띄운다. 휴지통 메일함을 알 수 없으면 지우지 않고 실패로 알린다. 이미 없는 저장본이면 알림 없이 닫는다. 단추가 있는 알림은 10초 보인다. 알림은 마우스 · 초점이 그 위에 있는 동안 사라지지 않고, 놓으면 그 알림의 시간을 처음부터 다시 센다.
    • 이어 쓰는 저장본의 제목 줄: 답장 대상이 있으면 답장, 제목이 Fwd:·FW:·전달: 로 시작하면 전달, 그 밖은 새 메일.
  • 서명
    • 서명을 바꾸고 저장하지 않은 채 떠나면 묻는다 — 제목 "저장하지 않은 서명이 있습니다", 글 "이 화면을 나가면 바꾼 서명이 사라집니다.", [계속 고치기] [나가기].
    • 서명 그림 3장은 서명 칸에 있는 그림 수로 센다. 미리 보기의 답장 원문 자리는 예시 이름 대신 "(보낸 사람)" 같은 괄호 글이다.
    • 설정 화면의 미리 보기 전환 단추와 그림 크기 줄(작게 · 보통 · 원본 · 그림 빼기, 36px)은 목록의 보기 전환처럼 밀도 예외다 (캔버스 확정값).
  • 받는 사람 자동 완성: Tab 은 고른 추천을 넣고 그 칸에 머문다(고른 것이 없으면 원래 Tab). 화살표는 끝에서 처음으로 돌고, 마우스를 올린 줄이 골라진다. 초성 · 자모만 친 글자는 서버에 묻지 않는다(받아 둔 목록에서만 거른다).

3단계 구현하며 정한 보완 (2026-10-27 — 캔버스에는 아직 없다)

  • 읽기 화면의 삭제: 휴지통 밖의 메일은 휴지통으로 옮기고(알림 "메일 1통을 휴지통으로 옮겼습니다." + [되돌리기]), 휴지통의 메일은 단추가 「영구 삭제」가 되어 여러 통과 같은 확인 대화상자를 거친다 — 그사이 다른 창이 휴지통으로 옮긴 메일을 확인 없이 영구 삭제하지 않게 (웹 화면은 삭제 요청 DELETE 를 쓰지 않는다 — 계약 2절 "여러 통 한꺼번에").
  • 검색
    • 검색 화면의 탭 제목은 "메일 검색"이다 — "(3) 메일 검색 · ARA SYSTEM".
    • 낱말이 8개를 넘으면 찾지 않고 알린다(서버 한도와 같다). 같은 검색어로 다시 Enter 를 누르면 보이는 채로 다시 찾고 고른 것을 푼다.
    • 주소로 바로 들어와 검색을 시작한 곳을 모르면, 닫을 때 그 범위의 메일함으로 간다(범위가 전체면 받은편지함).
    • 검색 화면 안에서 다시 찾거나 범위를 바꾸면 방문 기록을 쌓지 않는다(메일함에서 시작한 첫 검색만 쌓인다).
    • 전체 메일함 결과에서 다른 메일함으로 옮긴 메일은 결과에 남기고 메일함 표시만 바꾼다(휴지통 · 스팸메일함으로 옮기면 뺀다).
  • 여러 통 처리
    • 목록 보기의 「선택 해제」는 폭 1100 미만에서도 이름을 남긴다(분할 보기의 목록 칸에서는 아이콘만).
    • 요청이 통째로 실패하면 고른 것을 그대로 둔다(다시 시도할 수 있게). 열어 둔 메일이 옮겨져 목록에서 빠지면 목록 주소로 바꾼다.
    • 영구 삭제 확인은 공용 대화상자 폭(360)을 쓴다.
    • 「되돌리기」는 이 화면이 알던 메일함 한 곳으로 되돌리고, 로그아웃 · 다른 계정 로그인 뒤에는 움직이지 않는다 (계약 2절 "알려진 한계").

4단계 구현하며 정한 보완 (2026-10-28 — 캔버스에는 아직 없다)

  • 설정: /settings 는 계정 › 비밀번호로 연다(앱 레일의 "설정").
  • 비밀번호
    • 8자 미만이거나 지금 비밀번호와 같으면 새 비밀번호 아래 안내 글이 그대로 빨간 오류로 바뀐다.
    • 서버의 거절은 칸 아래에만 보이고 알림은 띄우지 않는다 — 「지금 비밀번호가 올바르지 않습니다.」는 지금 비밀번호 칸, 엔진 규칙(VAL_001)은 새 비밀번호 칸.
    • 보기 단추는 이름을 바꾸지 않고 눌림 상태(aria-pressed)로 알린다(ARIA 권고).
    • 요청은 30초까지 기다린다(서버가 엔진을 두 번 부른다 — 계약 1절).
  • 부재중 자동 응답
    • 날짜는 하나만 골라도 된다 — 시작만이면 그날부터 끌 때까지, 끝만이면 지금부터 그날까지.
    • 꺼 둔 채 저장할 때 기간이 맞지 않으면(흐린 칸이라 고칠 수 없다) 날짜를 비워 저장한다 — 끄기는 늘 저장되게. 다시 열면 "끌 때까지 계속"이다.
    • 저장 글은 올해가 아니면 연도를 붙인다("2027년 1월 3일(일)"). 날짜가 하나면 "…부터 자동으로 답합니다." · "…까지 자동으로 답합니다."
    • 메일함 패널 안내: 끝나는 시각이 지나면 숨긴다. 아직 시작 전이면 "10월 30일 ~ 11월 3일 · 설정"(끝이 없으면 "10월 30일부터 · 설정"), 끝이 없으면 "끌 때까지 · 설정". 화면을 띄워 둔 채 시각이 지나도 1분 안에 다시 맞춘다.
    • 바꾸고 저장하지 않은 채 떠나면 묻는다 — 제목 "저장하지 않은 자동 응답이 있습니다", 글 "이 화면을 나가면 바꾼 내용이 사라집니다.", [계속 고치기] [나가기]. 저장하는 중에 떠났는데 서버가 거절하면 알림으로 알린다(저장 단추 옆 글이 보이지 않으므로).
  • 메일함 관리
    • 할 일이 없는 메일함(보낸편지함, 안 읽은 메일이 없는 임시보관함, 빈 휴지통 등)에는 ⋯ 를 그리지 않는다. 빈 휴지통 · 스팸메일함에는 비우기 항목도 목록 머리 단추도 없다.
    • 하위 메일함은 받은편지함 · 내 메일함 아래에만 만든다(계약은 보관함 아래도 받는다 — 다른 메일 프로그램이 만든 것을 그대로 보인다).
    • 대화상자는 공용 폭 360(캔버스는 400). "지울 수 없습니다"의 [확인]은 단추가 하나라 주 단추 모양이다(캔버스는 보조 단추 — 세션 만료 대화상자와 같게).
    • 이름을 바꾸지 않고 [바꾸기]를 누르면 요청 없이 닫는다. 이름에는 / 와 제어 문자(줄바꿈 등)를 쓸 수 없다.
    • 분할 보기의 목록 머리(384)에서는 「휴지통 비우기」 · 「스팸메일함 비우기」가 아이콘만이다(40×44 — 이름은 툴팁). 보기 전환 · 새로고침과 함께 이름까지 넣으면 넘친다(3단계 도구 줄과 같은 규칙).
    • ⋯ 메뉴는 보이는 영역 안에 다 들게 연다 — 패널 아래쪽 행에서는 위로 연다.
    • 보던 메일함을 지우면 받은편지함으로 간다. 비우기 · 지우기가 끝나면 초점은 메일함 패널의 지금 메일함(지웠으면 받은편지함)으로 간다 — 누른 단추가 사라지므로.
  • 오래 걸리는 처리
    • 하는 중의 글: "‘거래처’ 메일함을 지우는 중 · 12 / 40통" · "휴지통을 비우는 중 · 600 / 1,234통" (빈 메일함 지우기처럼 셀 메일이 없으면 수를 뺀다).
    • 도중에 실패하면 "1,234통 가운데 600통까지 마치고 멈췄습니다. 다시 시도해 주세요."로 바뀐다(까닭은 오류 알림으로 함께). 하나도 못 했으면 오류 알림만 남는다.
    • 다른 탭에서 먼저 끝냈으면 실패로 보이지 않는다 — "받은편지함에 안 읽은 메일이 없습니다." · "휴지통이 이미 비어 있습니다.", 이미 없는 메일함 지우기는 지운 것으로 본다.
    • 같은 메일함의 처리는 한 번에 하나다(다른 탭에서 같은 메일함을 처리하는 중이면 "요청이 너무 많습니다"). 진행 알림은 로그아웃 · 세션 만료 때 닫힌다.
    • 휴지통을 비우는 동안은 메일함을 지울 수 없고, 메일함을 지우는 동안은 휴지통을 비울 수 없다 — 비우기는 그동안 휴지통에 들어온 메일도 지우므로, 지우는 메일함의 메일이 휴지통에 들어가자마자 영구 삭제되지 않게(계약 2절 "메일함 관리").
    • 목록 머리 「비우기」의 확인은 연 그 메일함만 비운다 — 확인 창이 열린 채 다른 메일함으로 가면(뒤로 가기 등) 창을 닫는다.
    • 서버가 처리 수 0 을 세 번 연달아 주면 멈추고 실패로 알린다(같은 요청을 끝없이 보내지 않게).
  • 스팸: 「되돌리기」는 원래 메일함으로 옮기며 표시를 반대로 바꾼다 — 원래 아무 표시도 없던 메일은 "스팸 아님" 표시가 남는다(계약 2절 "스팸 · 원문").

5단계 구현하며 정한 보완 (2026-10-28 — 캔버스에는 아직 없다)

  • 알림을 막았을 때의 알림은 보드의 어두운 바탕 대신 기존 오류 알림 모양(흰 카드)을 쓴다. [설정 열기]는 브랜드 색.
  • "웹메일을 보고 있음" 은 같은 사용자로 로그인한 이 앱의 탭 가운데 보이고 초점이 있는 탭이 있다는 뜻이다 — 설정 화면을 보고 있어도 보내지 않는다. 두 번째 모니터에 웹메일을 띄워 두고 다른 창에서 일하면 보낸다(권하기 문구 "다른 창을 보는 동안에도"와 맞게).
  • 설정 › 알림
    • 허락은 했는데 알림을 꺼 둔 상태: 칸은 비우고 안내는 초록 "이 브라우저에서 알림이 허용되어 있습니다."(보드에 없는 상태).
    • 실시간을 쓸 수 없는 브라우저(Web Locks · BroadcastChannel · 스트림 읽기 가운데 하나라도 없음)는 바탕화면 알림도 "지원하지 않습니다"로 본다 — 알림은 실시간으로만 온다.
    • 연결 상태 글: 처음 잇는 중 "연결하는 중입니다…", 끊김 "연결할 수 없어 1분마다 확인합니다." 아래에 "저절로 다시 연결합니다. 회사 방화벽이나 프록시가 오래 열린 연결을 끊으면 이렇게 될 수 있습니다.", 지원하지 않음 "이 브라우저에서는 실시간 연결을 쓸 수 없습니다."
    • 주소는 /settings/mail/notify, 탭 제목은 "메일 알림"(머리글은 "알림").
    • 켜기 칸에서 허락을 받은 결과는 칸 아래 안내로만 보인다(화면 알림은 목록 위 권하기 줄에서만 — 설정 화면에서 [설정 열기]는 어색하다).
    • 연결이 끊긴 동안은 "연결할 수 없어 1분마다 확인합니다." 아래에 "연결될 때까지 바탕화면 알림은 오지 않습니다."를 더한다.
    • 알림을 만들 수 없는 브라우저(휴대폰 Chrome 은 new Notification() 을 막는다)는 처음 실패할 때 "지원하지 않습니다"로 바꾼다.
    • 한국어 안내 글은 낱말 가운데서 줄을 바꾸지 않는다(word-break: keep-all, 긴 주소는 overflow-wrap: anywhere).
  • 목록 위 권하기 줄은 메일함 목록에만 둔다(검색 결과에는 두지 않는다). 줄이 사라지면 초점을 목록 칸으로 옮긴다. 분할 보기의 좁은 목록 칸(384)에서는 짧은 글 "바탕화면 알림으로 새 메일 받기"를 쓴다(긴 글은 툴팁) — 긴 글은 잘려 끝까지 읽을 수 없었다.
  • 바탕화면 알림
    • "외 N명"은 서버가 보낸 새 메일(최대 5통)에서 겹치지 않는 보낸 사람(주소 기준)으로 센다.
    • 시험 알림도 새 메일 알림과 같은 자리(tag)를 쓴다. 누르면 창만 앞으로 온다.
    • 알림을 눌렀을 때 받은편지함을 아직 모르면 메일함을 받아 본 뒤 열고, 그래도 모르면 메일 첫 화면으로 간다. 누르기 전에 로그아웃 · 다른 계정 로그인이 있었으면 이동하지 않는다.
    • 아이콘은 192px PNG(frontend/src/assets/brand/notification-icon.png — ARA 마크를 #0B3640 둥근 사각 가운데에).
    • 사용자가 웹메일로 돌아오면(어느 탭이든 보이고 초점이 생기면) 띄운 알림을 닫는다 — 알림 센터에 지난 알림이 남지 않게.
    • 허락을 묻는 동안 단추 · 칸은 눌러도 다시 묻지 않을 뿐 초점을 잃지 않는다(aria-disabled). 막힘 · 지원 안 함으로 칸이 꺼지면 초점을 안내 글로 옮긴다.
  • 탭 제목 "(N)" 은 지금처럼 메일 화면에서만 붙인다(메일 밖 화면에는 받은편지함 수의 기준이 없다). 메일 밖에 있는 동안 생긴 바뀜은 남겨 두었다가 메일 화면으로 돌아오면 맞춘다.
  • 실시간 반영 시간: 실시간이 부른 목록 맞추기는 1초 모아서 한 번 한다(한 통을 읽어도 바뀜이 오므로 — 연달은 바뀜에 목록을 여러 번 받지 않게). 그래서 새 메일은 "1초 안"이 아니라 2초 안팎에 목록에 나타난다.

발송 · 부재중 결함 수정 (6단계 앞, 2026-10-28 — 캔버스에는 없다)

  • 받는 사람 일부가 받지 못했을 때(없는 주소 · 받는 쪽 거절 · 지금은 받을 수 없음): 보낸 것으로 처리해 쓰기 화면을 닫고, "보냈습니다" 알림 대신 오류 알림 모양(흰 카드)에 "보냈지만 N명은 받지 못했습니다. 주소 (까닭)"와 [보낸편지함 보기](보냈다는 알림과 같은 링크) — 두 명까지 주소 · 까닭을 적고 나머지는 " 외 N명". 까닭 문구 "없는 주소" · "받는 쪽이 거절" · "지금은 받을 수 없음".
  • 모두 받지 못했을 때: 보내지 않은 것이다 — 없는 주소 · 거절(MAIL_010)은 받는 사람 오류(MAIL_002)와 같은 안내 띠 · 같은 초점으로, 지금은 받을 수 없음(MAIL_011)은 오류 알림으로. 쓰던 메일은 그대로 둔다.
  • 부재중 자동 응답 길이: 제목 150자 · 내용 600자(입력칸 한도(maxlength)도 이 값, 켤 때 검사 — 꺼 둔 채로는 4단계 한도까지 저장된다) — 엔진이 바이트로 막아 한글로는 4단계의 200자 · 4,000자가 저장되지 않았다(계약 2절 "부재중 자동 응답").

6단계 구현하며 정한 보완 (2026-10-28 — 캔버스에는 아직 없다)

  • 되돌리기: 지우기 전 목록을 통째로 되돌리지 않고, 지운 필터만 지운 자리에 다시 넣어 저장한다 — 10초 안에 다른 필터를 켜거나 옮긴 것이 사라지지 않게(보드는 "지우기 직전의 목록 그대로"). 자리는 앞 · 뒤 필터로 기억한다(앞 필터 뒤 → 없으면 뒤 필터 앞 → 없으면 지운 때의 차례) — 여러 개를 어떤 차례로 되돌려도 원래 차례가 된다. 여러 알림의 [되돌리기]는 누른 차례대로, 돌고 있는 저장이 끝난 뒤에 보낸다. 다시 넣으면 50개를 넘을 때는 보내지 않고 50개 안내를 오류 알림으로. 필터 화면을 떠난 뒤에 실패하면(다른 곳에서 바뀜 포함) 알림으로 알린다.
  • 다른 곳에서 바뀜: 고치기 창 안(단추 줄 위)에도 목록 위 줄과 같은 문구와 [다시 불러오기](36 — 밀도 예외)를 둔다 — 창이 모달이라 목록 위 줄을 누를 수 없다. 창에서 다시 불러오면 창과 쓴 내용은 그대로이고 초점은 [저장]으로 간다. 다음 [저장]은 다시 받은 목록에 넣는다 — 같은 필터의 자리, 그사이 다른 곳에서 지워졌으면 맨 아래(그때 50개를 넘으면 창 안에 50개 안내). 안내는 다시 받기 전까지 남는다(칸을 고쳐도). 다시 받은 목록에 이미 없는 필터에서 [이 필터 지우기]를 누르면 보내지 않고 창을 닫는다. 목록 위 줄의 [다시 불러오기]는 보드대로 창이 닫혀 있을 때 쓴다.
  • 켜기 스위치: 이름은 "‘…’ 필터 사용" 하나 — 켜짐 · 꺼짐은 aria-checked 가 알린다(보드는 이름을 끄기 · 켜기로 바꿨다). 그림은 36×20, 누르는 곳은 40×44(밀도 예외).
  • 누를 수 없는 단추는 aria-disabled(누른 단추 · 창이 돌아올 단추가 초점을 잃지 않게): 맨 위 ▲ · 맨 아래 ▼, 저장 중의 스위치 · ▲▼, 창의 [저장] · [이 필터 지우기], 50개일 때의 [새 필터](까닭 글을 aria-describedby). 마우스를 올려도 모양이 바뀌지 않는다. 메뉴의 꺼진 항목도 모두 그렇다 — 편집기 메뉴 · 이동 · 메일함 ⋯ · 읽기 화면 ⋯ 에서 화살표로 가서 초점은 받되 고를 수 없다(WAI-ARIA 메뉴). 이름 아래 까닭 글은 aria-describedby.
  • 저장은 한 번에 하나: 저장이 도는 동안 누른 다른 행의 스위치 · ▲▼ 는 보내지 않는다(모양은 그대로, 커서만 바뀐다). 창으로 저장할 때 켜짐은 그 순간 목록의 값이다 — 창은 켜기를 고치지 않으므로, 창을 연 뒤 실패해 되돌려진 켜기가 창의 저장으로 살아나지 않게.
  • 앞뒤 공백: 이름 · 찾을 글 · 전달 주소는 앞뒤 공백을 빼고 보낸다(서버도 뺀 값으로 저장한다).
  • 문구(보드에 없던 것): "내 주소로는 전달할 수 없습니다." · "전달할 주소를 입력해 주세요." · "전달할 주소 형식이 올바르지 않습니다." (@ 앞은 RFC 5322 dot-atom · 64자 — 서버의 검사보다 좁게 봐서, 화면이 통과시킨 주소를 서버가 거절하지 않게). 길이 · 제어 문자 · 이름 오류는 사전의 VAL_001 문구. 이름 없는 필터의 이름(조건으로 만든 것)은 알림 · 단추 이름에서 50자로 자르고 "…"를 붙인다. 작은따옴표가 든 이름은 큰따옴표로 묶는다.
  • 읽기 화면 ⋯: 50개인지 알려고 메뉴를 열 때 필터를 알림 없이 받는다(받아 둔 것이 없거나 50개일 때). 결과가 오기 전에는 항목을 켜 둔다. 50개면 항목을 끄고 이름 아래에 까닭 한 줄. 보낸 사람 주소가 없는 메일에는 항목이 없다. ?from= 으로 왔는데 50개면 창을 열지 않고 주소의 쿼리만 지운다.
  • 화면 폭: 공용 설정 틀 그대로 최대 800 · 묶음 사이 22(보드는 880 · 18 — 다른 설정 화면과 맞춘다). 머리의 [새 필터]는 틀의 actions 자리. 안내 상자 700, 창 620.
  • 메일함: 받아 둔 메일함 목록을 보이며 뒤에서 다시 받는다(다른 탭에서 지웠을 수 있다). 요약에 메일함 이름이 들어가 필터와 메일함을 둘 다 받은 뒤 목록을 그린다. 옮길 곳 고르기는 받은편지함 → 내 메일함 → 보관함 → 스팸메일함 → 휴지통(보관함과 그 아래 — 보드에 없었다. 서버가 받고, mailcow 에서 옮겨 온 계정에 있다). 지워진 메일함은 "지워진 메일함이니 …"가 꺼진 채 골라져 보이고, 그대로 저장해도 막지 않는다(서버가 받는다 — 받은편지함에 들어온다).
  • 초점: 조건 더하기 → 새 줄의 "어디에서", 조건 빼기 → 이웃 줄의 찾을 글, 필터 지우기 → 다음 행(마지막이면 앞 행), 목록 위 [다시 불러오기] · [다시 켜기] · ?from= 으로 연 창의 [취소] · 이미 없는 필터 지우기 → 머리의 [새 필터], 창 안 [다시 불러오기] → [저장].
  • 그 밖: [다시 켜기]가 성공해도 알림은 없다(줄이 사라진다). 목록 아래 줄 끝은 "… 줄을 누르면 고칠 수 있습니다."(2026-11-05 — 보드의 글은 "… 행을 누르면 고칩니다."). 전달 칸은 type=email · 254자이고, 보기 글(받는 사람 칸 · 전달 칸)은 로그인한 직원 주소의 도메인으로 만든다. 저장하는 사이 화면을 떠나면 칸 안에 보일 거절을 알림으로 띄운다(부재중 화면과 같다). 목록에 aria-busy 를 걸지 않는다(칸마다 aria-disabled).
  • 하지 않은 것: 고치기 창에 쓰다 만 채로 떠날 때 묻기(나가기 확인) — 보드에 없다. 창을 연 채 브라우저 뒤로 가기를 하면 쓴 것을 잃는다(알려진 한계 — 7단계에서 고쳤다, 위 "7단계").

7단계 구현하며 정한 보완 (2026-10-29 — 캔버스에는 아직 없다)

  • 「…에도 적용」 줄 높이: 할 일 칸과 같은 44(보드는 40) — 고치기 창의 밀도 예외는 조건 칸 · 단추 40 뿐이라 새 예외를 만들지 않는다.
  • 고를 수 없는 경우를 넓혔다(보드는 전달만 · 적용 중 둘): 옮길 메일함이 지워짐 · 받은편지함으로 옮기기만 하는 필터도 이미 받은 메일에 할 일이 없다(서버도 옮기지 않는다). 까닭은 이 차례로 하나만 — 이 탭에서 적용 중 → 지워진 메일함 → 받은편지함으로 옮기기만 → 전달만. 할 일을 아직 하나도 고르지 않았으면 막지 않는다(저장할 때 칸 아래 "할 일을 하나 이상 골라 주세요."). 고를 수 없게 되면 골라 둔 것을 쓰지 않고, 다시 고를 수 있게 되면 돌아온다. 같은 판정으로 저장한 뒤에도 시작하지 않는다 — 그때는 "필터를 저장했습니다."
  • 꺼 둔 필터도 적용한다 — 사용자가 이번 저장에 고른 일이다. 적용 칸만 고르고 다른 칸을 고치지 않아도 바꾼 것으로 본다(나가기 확인 · [저장]).
  • 전체 수: 첫 응답 전에는 받은편지함의 메일 수(메일함을 아직 받지 않았으면 수를 뺀 문구), 그 뒤로는 "확인 + 남은 수"(최근 메일부터라 그사이 온 메일 · 옮겨진 메일로 조금 바뀔 수 있다).
  • 실패 알림: 요청은 인터셉터 알림을 끄고 보내고, 응답이 온 때 세션이 그대로일 때만 사전 문구로 알린다(세션 만료 대화상자는 그대로) — 그래서 처음 요청부터 실패하면 사전 문구 알림과 진행 알림의 결과 "필터는 저장했지만 …"가 함께 보인다(4단계 오래 걸리는 처리와 같다). 다른 창 · PC 에서 적용 중(429)은 결과 문구 하나만. 도중의 429 는 이어 할 자리가 있으니 [다시 시도]를 붙인다(처음 요청의 429 는 보드처럼 단추 없음).
  • 이 탭에서 적용 중에 [다시 시도]: 시작하지 않고 "다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요." + 같은 자리 · 같은 수의 [다시 시도].
  • 나아가지 못한 응답(확인 0 인데 이어 할 자리가 있다)이 세 번 이어지면 멈춘다(MAIL_FILTER_RUN_MAX_STALLS) — 같은 요청을 끝없이 보내지 않게.
  • 맞추기: 끝났거나 이번에 나아간 뒤 멈췄으면 개수 · 사용량을 다시 받고, 보이는 목록이 받은편지함이나 옮긴 메일함이면 그 목록도 맞춘다(실시간 연결이 없는 탭에서 1분까지 옛 개수가 보이지 않게).
  • 닫기 확인과 고치기 창: 브라우저가 Esc 를 거듭 누르면 고치기 창을 막지 못하고 스스로 닫는다 — [계속 고치기]면 창을 다시 띄우고 초점은 닫히기 전에 있던 칸으로 돌려준다. 닫기 확인이 떠 있는 동안 다른 화면으로 가려 하면(뒤로 가기) 같은 확인 창이 "이 화면을 나가면 …" [나가기]로 바뀐다. 저장 · 지우기를 보내는 중에는 묻지 않는다(결과는 알림으로).
  • 부재중: 메일함 패널 아래의 부재중 안내는 그대로 둔다(저장한 값으로 보인다 — 동작하지 않음은 설정 화면에서만 알린다).

8단계 구현하며 정한 보완 (2026-10-29 — 캔버스에는 아직 없다)

  • 받는 사람 추천: 주소록 줄의 설명은 주소 종류로 정한다 — 회사 주소는 회사 이름(없으면 "회사"), 개인 주소는 "개인", 그 밖은 "기타". 계약에 대표 표시가 없어 보드("대표 주소는 회사")와 두 경우에서 다르다 — 대표 주소가 개인이면 "· 개인", 대표가 아닌 회사 주소에는 회사 이름이 나온다. 구성원이 0명인 그룹은 추천하지 않는다. 초성 · 조합 중 글자도 받아 둔 것으로 모자라면 서버에 묻는다(서버가 같은 맞추기를 쓴다 — 계약 2절). 그룹을 고른 뒤 칸 아래 글(role=status)은 불러오는 중 "‘G’ 그룹의 구성원을 불러오는 중…" → "‘G’ 그룹의 N명을 받는 사람에 넣었습니다." / "‘G’ 그룹에서 받는 사람에 더 넣을 주소가 없습니다." + 뺀 것이 있으면 "주소가 없는 N명 · 이미 넣은 N명은 뺐습니다." — 칸에 다시 치면 지운다. 그룹을 이어 고르면 그룹마다 따로 받아 끝나는 대로 넣는다 — 칸 아래 글은 받는 중인 그룹이 있으면 마지막으로 고른 그룹의 "불러오는 중", 없으면 마지막에 끝난 그룹의 결과.
  • 쓰기 화면을 받는 사람을 채워 열기: 새 메일에만 쓰고(라우터 상태 composeTo — 연 직원의 주소를 함께 싣고, 지금 직원과 다르면 쓰지 않는다), 잘못된 주소는 버린다. 채운 받는 사람은 "바꾼 것"이 아니다 — 손대지 않으면 자동 저장 · 나가기 확인이 없다. 초점은 제목 칸으로. 새 탭으로 연 쓰기 링크(가운데 클릭)는 빈 쓰기 화면이다 — 라우터 상태는 새 탭으로 가지 않는다(한계).
  • 그룹: [그룹에 메일 쓰기]는 받는 동안 바쁜 표시(aria-busy)이고 거듭 누르면 무시한다. 받는 사이 다른 목록 · 화면으로 갔거나 세션이 바뀌면 쓰기 화면으로 가지 않는다. 주소가 있는 구성원이 없으면 오류 알림 "‘G’ 그룹에는 메일 주소가 있는 구성원이 없습니다." — 빈 그룹이면 목록 머리 단추와 그룹 ⋯ 의 같은 항목이 함께 꺼지고(aria-disabled) 까닭은 "아직 구성원이 없습니다.". 그룹이 100개면 [새 그룹]을 끄고 "그룹은 100개까지 만들 수 있습니다.". [되돌리기]는 서버가 기억한 원본으로 다시 만든다(계약 3절 "되돌리기").
  • 구성원 넣기 창: 서버가 이미 구성원인 사람을 빼고 준다(연락처 · 사내 둘 다, [더 보기]도 — excludeGroupId). 한 번에 100명까지 — 다 고르면 나머지 칸을 끄고 알린다(role=status). 전환은 눌림 단추 둘(role=group + aria-pressed — 보드의 radiogroup 대신, 새 .segmented--outline). 목록은 높이 18rem 안에서 넘기고 [더 보기]가 있다. 아바타는 없다(보드대로). 넣은 뒤 알림의 수는 서버가 넣었다고 한 수(addedCount)다 — 0명이면 "‘G’ 그룹에 새로 넣은 사람이 없습니다. 이미 구성원이거나 그룹에 넣을 수 없는 연락처입니다."(다른 프로그램이 uid 없이 만든 카드 — 계약 3절).
  • 연락처 창(추가 · 고치기):
    • [고치기]는 그 연락처를 새로 받아 연다(받는 동안 aria-busy, 거듭 누르면 무시, 받는 사이 다른 연락처를 골랐으면 열지 않는다 — 다른 탭 · 휴대폰에서 바꾼 것을 덮지 않게). 찾을 수 없으면(RES_001) 알림 없이 목록에서 빼고 상세가 "찾을 수 없음" + [목록으로]를, 그 밖의 실패는 알림만 띄운다(창을 열지 않는다).
    • 저장은 바꾼 칸만 보낸다 — 보낼 값으로 견주어 앞뒤 공백 · 빈 줄 · 제어 문자 · 그룹의 차례는 바꾼 것이 아니다. 바꾼 것이 없으면 보내지 않고 닫는다. 나가기 확인도 같은 기준이다.
    • 검사도 바꾼 칸만 — 손대지 않은 남의 값(21번째 주소 · "내선"이 든 번호 · 긴 메모 · 100자 넘는 한 줄 칸)은 그대로 둔다. 이메일 · 전화번호는 고치면 그 칸 전부를 본다. 보드에 없던 문구: "이메일은 20개까지 넣을 수 있습니다. 몇 개를 빼 주세요." · "전화번호는 20개까지 넣을 수 있습니다. 몇 개를 빼 주세요."([더하기] 아래 — 초점은 그 묶음의 첫 줄) · "100자까지 쓸 수 있습니다."(그 칸 아래) · "같은 주소를 두 번 넣었습니다." · 전화번호 형식 · "메모는 1,000자까지 쓸 수 있습니다."(고쳤을 때만).
    • 이메일은 서버보다 좁게 본다 — @ 앞은 dot-atom 64자, 도메인 칸마다 앞뒤 하이픈 없이 63자 · 끝 점 없음 · 마지막 칸은 글자 2개 이상이거나 xn--, 한글 도메인은 받는다, 254자(필터의 전달 주소와 같은 검사). 이메일 칸은 type="text" + inputmode="email" — 브라우저가 한글 도메인을 punycode 로 바꾸지 않게(받는 사람 칸과 같다).
    • 같은 주소 경고의 괄호는 그 연락처의 첫 그룹(없으면 회사)이다. 칸을 고치면 경고를 내리고, 채워 연 창(읽기 화면 · 사내 목록)은 열 때 바로 본다. 늦게 뜨는 안내는 늘 있는 role=status 영역 안에 뜬다.
    • 필수 안내("이름이나 이메일 가운데 하나는…")는 빨간 글로 바뀌고 이름 칸에 초점을 둔다. 서버의 형식 거절(VAL_001)은 단추 줄 위에 사전 문구로. 저장 중에 창을 닫았으면(또는 다시 열었으면) 창 안에 보일 거절도 알림으로 알린다(그룹 이름 창도 같다).
  • 보이지 않는 제어 문자는 거절하지 않고 고친다 — 한 줄 칸의 탭 · 제어 문자는 띄어쓰기로, 메모의 CR LF · CR 은 줄바꿈으로. 열 때 · 보낼 때 고치고, 고친 것만으로는 바꾼 것이 아니다(표에서 붙여 넣은 탭 · 다른 프로그램의 CR LF 는 사용자에게 보이지 않아 고칠 수 없기 때문).
  • 크기 · 제목: 그룹 체크 줄 · [더 보기] · [새 그룹]은 44(보드 40 — 밀도 예외를 새로 만들지 않는다). 패널의 그룹 ⋯ 는 마우스를 올리거나 초점이 갈 때 보인다(메일함 행과 같다). 제목 수준은 메일 화면과 같다 — 패널 묶음 이름은 글 + aria-labelledby, 목록 제목은 h1, 상세 이름은 h2.
  • 알림 문구(보드에 없던 것): "연락처를 되돌렸습니다." · "그룹을 되돌렸습니다." · "‘X’ 그룹을 만들었습니다." · "그룹 이름을 바꿨습니다." · "주소를 복사했습니다." / "주소를 복사하지 못했습니다." · 구성원 없는 그룹을 지웠으면 "‘X’ 그룹을 지웠습니다.". 서버가 원본을 기억하지 못한 지우기(표 없음)는 같은 문구를 [되돌리기] 없이 띄운다.
  • 지운 뒤: 보던 연락처를 지우면 그 목록으로(고른 것 없이, 초점은 목록), 보던 그룹을 지우면 전체 연락처로. 이미 지워진 것(RES_001)은 지운 것으로 본다. 주소 복사는 대표 주소만(주소가 없으면 항목을 끄고 까닭).
  • 목록 맞추기: 만들기 · 고치기 · 되돌리기는 응답을 서버의 차례(frontend compareContacts — backend contact-search.ts 와 짝)로 받은 쪽 안의 자리에 넣는다(받은 쪽 끝보다 뒤면 넣지 않고 수만 고친다). 응답으로 자리를 알 수 없을 때만 첫 쪽을 한 번 다시 받는다 — 찾은 목록에 새 연락처가 생겼을 때 · 구성원 넣기로 보는 그룹이 바뀌었을 때 · 사내 직원을 넣어 전체 목록에 새 연락처가 생겼을 때.
  • 찾기: 목록을 바꾸면 찾기를 푼다. 사내 목록은 칸 안내 · 빈 문구가 따로("사내 직원이 없습니다" · "공용 주소가 없습니다"). Enter 는 바로 찾고, Esc · ✕ 는 푼다. 글자와 자모가 섞인 조합 중 글은 보내지 않는다(초성만이면 보낸다). 찾는 글은 254자까지(서버와 같다).
  • 상세: 연락처를 찾을 수 없으면(RES_001) [목록으로], 그 밖의 실패는 [다시 시도]. 목록을 받지 못해도 고른 연락처는 따로 받는다. 주소로 연 사내 항목은 그 항목 하나를 받는다. [메일 쓰기]는 주소가 없으면 꺼지고(aria-disabled) 까닭 "메일 주소가 없는 연락처입니다.", 사내 [내 주소록에 추가]는 연락처가 3,000개면 꺼지고 3,000개 안내.
  • 읽기 화면 ⋯: 원문 아래에 구분선을 둔다(보드대로). 「주소록에서 보기」 아이콘은 책. 보낸 사람이 바뀌면(다른 메일) 다시 판단한다 — 254자 넘는 주소면 묻지 않고 "없음"으로. 연락처 추가 창은 메일 레이아웃에 둔다 — 메일을 바꾸거나 목록으로 돌아가도 쓴 그대로 남고, 메일 화면을 떠날 때만 창의 나가기 확인이 묻는다. 닫으면 ⋯ 가 아직 있을 때만 초점을 돌려준다. 채우는 주소 종류는 회사.
  • 창의 자리: 주소록의 연락처 · 그룹 · 구성원 창은 주소록 화면의 틀에 둔다 — 주소록 안에서 옮겨 다녀도 닫히지 않고, 주소록을 떠나면 나가기 확인을 묻고, 세션이 바뀌면 닫는다(메일 화면의 창도 같다).
  • 서버 쪽에서 생긴 한계(계약 3절): 엔진이 만든 뒤 uid 를 바꾸지 못하게 해서 다른 프로그램이 uid 없이 만든 카드는 그룹에 넣을 수 없다 — 고치기 창에서 그 저장은 창 안 사전 문구. 다른 프로그램이 넣은 값이 우리 규칙 밖이면 그 칸을 고칠 때만 그 값도 규칙에 맞춰야 한다(다른 칸은 그대로 고칠 수 있다).

9단계 구현하며 정한 보완 (2026-10-29 — 캔버스에는 아직 없다)

  • 외부 이미지: [이미지 보기]는 받는 동안 꺼진다(aria-disabled · aria-busy). 받지 못하면 인터셉터가 알리고 줄은 그대로다. 그 메일의 본문 칸만 바꾸고(받는 사이 다른 메일을 열었으면 버린다), 줄이 사라져 초점을 잃으면 본문 틀로 옮긴다. 본문 틀의 CSP 는 보일 때만 그림에 우리 출처의 중계 경로(/api/mail-images/)를 연다 — 경로까지 좁힌다(2026-10-29 리뷰). 중계 주소로 인정하는 모양은 /api/mail-images/<base64url 한 칸> 뿐이다(짝: backend TOKEN_PATTERN). 답장 · 전달 인용에는 중계 그림도 실리지 않는다.
  • 인쇄: 라우트 mail-print(앱 틀 없음 · 로그인 필요), 탭 제목은 메일 제목(인쇄 머리글 · PDF 이름으로 쓰인다). window.open 은 누름 처리 안에서 바로 부른다(팝업 차단을 피하려고).
    • Ctrl+P(⌘P)는 메일이 다 열려 있을 때만 가로챈다 — 본문 틀 안에서 눌러도 같고, 한글 조합 중 · 다른 수식키 · 불러오는 중이면 브라우저 인쇄 그대로다. 라틴 글자가 아닌 배열은 code === 'KeyP' 로도 잡는다.
    • 종이 210mm · 여백 16mm 15mm 는 @page 와 같은 값이다(둘을 함께 고친다). 본문 틀의 load(그림까지) · 글꼴 준비 · 한 프레임 뒤에 인쇄 창을 한 번만 띄운다 — 그사이 다시 받기 시작했으면 띄우지 않고, [인쇄]는 다 잰 뒤에만 눌린다. [닫기]로 닫히지 않는 탭(주소로 바로 들어온 탭)은 메일 화면으로 간다.
    • 첨부는 목록에 보이는 것만 "이름 (크기)", 날짜는 "2026년 10월 26일 (월) 오전 10:42". 받기 실패는 화면이 그린다(알림 없음). 문구: "(제목 없음)" · "(보낸 사람 없음)" · 받는 중 "메일을 불러오는 중".
  • 보내기 취소 알림: 마감은 설정한 초를 알면 "지금 + 그 초 − 1초", 모르면 min(sendAt, 지금 + 30초)이다(설정은 메일 화면에 들어올 때 받아 둔다 — 아는 값이 0 인데 서버가 붙잡았으면 sendAt). 이 PC 시계로 이미 지났으면 지금처럼 [보낸편지함 보기] 알림이다. 마감이 있는 알림은 마우스 · 초점으로 멈추지 않고, 알림이 넘쳐도 먼저 닫히지 않는다. 막대는 <progress>(aria-hidden)다.
    • 알림 단추는 한 번만 돈다(닫히는 전환 중에 두 번 눌러도 — takeAction). 같은 메일의 취소는 요청 하나로 묶는다. 초점이 든 알림이 닫히면 알림에 들어오기 전 요소로(없으면 본문 영역) 돌려준다.
    • 쓰기 화면으로 가지 못하면(그 화면의 나가기 확인에서 머묾 등) 그 자리에 두고 "보내기를 취소하고 임시보관함으로 옮겼습니다." + [고치기].
    • 일부 거절 문장은 붙잡은 발송이면 "받지 못합니다"(현재형)다. 예약에서 일부 거절: "예약했지만 N명은 받지 못합니다. 주소 (까닭)" + [예약됨 보기].
  • [보내기 ▾]: ▾ 이름 "보내기 방법 더 보기", 메뉴 이름 "보내기 방법". 메뉴는 아래가 모자라면 위로 연다(usePopupPlacement — 메일함 ⋯ 과 같은 규칙). 「예약 발송…」은 [보내기]와 같은 사전 검사를 먼저 한다(걸리면 창을 열지 않고 그 칸으로).
  • 예약 창: 「오늘 오후」 6:00 은 5분 뒤 이후일 때만, 「다음 주 월요일」은 오늘 다음의 첫 월요일(오늘이 월요일이면 7일 뒤)이다. 예약할 수 없는 시각은 빼고, 같은 시각이 겹치면 앞의 것만 둔다. 직접 고르기는 오늘 ~ 7일 뒤 · 30분 단위, 처음 값은 내일 오전 9:00, 검사는 [예약]을 누를 때(role=alert). 시간대 이름을 모르면 "이 PC의 시간으로 보내고, 7일 안에서 고를 수 있습니다.". 창이 열린 동안 보낼 수 없게 되거나 세션이 바뀌면 닫는다. 서버가 sendAt 을 VAL_001 로 거절하면 창을 다시 열어 그 시각을 「직접 고르기」에 채우고 까닭(5분 · 7일 문구)을 보인다 — 이 화면의 검사로는 맞는 시각(PC 시계가 어긋남)이면 가까운 한도로 본다. 쓰기 화면을 이미 떠났으면 알림으로.
  • 「예약됨」: 수는 GET /scheduled-messages?limit=1 의 total 이다 — 메일 화면에 들어올 때 · 예약하거나 취소한 뒤 · 새 메일 확인이나 실시간 알림이 바뀜을 알릴 때 다시 묻는다. 예약됨을 보는 동안은 0통이어도 패널 줄을 남긴다(숫자 없이). 패널 줄은 보낸편지함(과 그 하위) 바로 아래이고 ⋯ 는 없다.
    • 목록: 분할 보기 2줄(받는 사람 · 시각 / 제목), 목록 보기 한 줄 세 칸(받는 사람 150 · 제목 · 보낼 시각 150). 고르는 칸 · 별 · 여러 통 처리 · 비우기 · 알림 권하기 줄은 없다. 빈 상태 "예약한 메일이 없습니다" / "메일을 쓸 때 [보내기 ▾] → [예약 발송…]으로 보낼 시각을 정할 수 있습니다.".
    • [고치기] · [예약 취소]는 하는 동안 두 단추를 함께 막고, 성공해 줄이 사라지면 초점을 옮긴다. [고치기] 뒤 "예약을 취소했습니다. 고친 뒤 다시 보내 주세요." — 쓰기 화면으로 가지 못하면 "예약을 취소하고 임시보관함으로 옮겼습니다." + [고치기]. [예약 취소] 뒤에는 삭제처럼 다음 메일(분할) · 목록(목록 보기)으로.
  • 엔진이 발송을 풀 때: 새 메일 확인 · 실시간 알림의 상태는 바뀌지 않으므로(계약 2절) 보이는 목록 · 열린 메일 · 가장 이른 예약의 시각 + 2초에 셋을 알림 없이 다시 받고, 시각이 지난 채 남으면 5초마다 3번 더 본다(usePendingSendRefresh). 검색 결과는 기다리는 메일이 보일 때만, 열린 메일은 pendingSend 만 고친다. MAIL_014 를 받아도 바로 같은 셋을 다시 받는다.
  • 붙잡힌 메일(보내는 중 · 예약) — 취소로만 보낸편지함을 떠난다(2026-10-29 리뷰, 계약 2절 MAIL_015):
    • 목록(분할 · 표 · 검색)의 고르기 칸은 꺼지고(aria-disabled) title 이 "보내기를 기다리는 메일은 고를 수 없습니다. 메일을 열어 취소하거나 삭제해 주세요." — 하나 · 범위 · 모두 고르기가 건너뛰고 여러 통 처리에도 들지 않는다(고른 뒤 붙잡히면 고른 것에서 뺀다). 보낸편지함 표의 날짜 칸은 title 에도 같은 글.
    • 읽기 화면은 두 종류 모두 스팸 신고 · 이동 · ⋯ 의 차단 항목이 없다. [삭제]는 확인 "보내지 않은 메일을 삭제할까요?" / "보내지 않고 휴지통으로 옮깁니다." [삭제] [취소](처음 초점 「취소」) → 발송 취소 → 휴지통 → "보내지 않고 휴지통으로 옮겼습니다.". 창을 연 채 발송이 풀리면 창을 닫는다(보낸 메일의 영구 삭제 확인으로 바뀌지 않게).
    • 서버도 막는다(MAIL_015 — 화면이 막지 못한 때만 보인다): 여러 통 처리는 그 메일만 남기고(failedIds · skippedIds — 지금처럼), 메일함 지우기 · 비우기는 붙잡힌 메일만 남으면 진행 알림이 멈추고 인터셉터 알림 "보내기를 기다리는 메일은 옮기거나 지울 수 없습니다. 먼저 보내기를 취소해 주세요."(IMAP 으로 옮긴 메일에서만 생긴다 — 4단계 처리의 실패와 같은 모양).
  • 기다린 뒤 다음 메일로: 삭제 · 스팸 · 차단 · 예약 취소처럼 응답을 기다린 뒤 옮기는 일은 그사이에도 같은 목록 · 같은 메일을 보고 있을 때만 한다(3 · 4단계의 단추도 함께 고쳤다 — 2026-10-29 리뷰).
  • 차단:
    • 항목이 없는 때: 보낸 사람이 없을 때 · 내 주소 · 사내 도메인(그 하위 포함). 차단 목록은 메일 화면에 들어올 때 알림 없이 받고, 못 받았으면 ⋯ 를 열 때 다시 받는다(모르는 동안은 「보낸 사람 차단」). 500개면 항목을 끄고(aria-disabled) "차단한 주소는 500개까지 둘 수 있습니다. 쓰지 않는 차단을 풀어 주세요.".
    • 창: 고르기 묶음 이름 "차단할 범위", 도메인을 고를 수 없는 까닭 "여러 사람이 쓰는 메일 서비스라 도메인으로 막을 수 없습니다.". 「이 메일도 스팸메일함으로 옮기기」는 이미 스팸메일함이면 숨긴다.
    • 옮겼으면 삭제처럼 다음으로 간다. [되돌리기]는 차단을 풀고 원래 메일함으로 되돌린다(보드의 "받은편지함" 대신 — 받은편지함 밖에서 막았을 수 있다). 도메인으로 막힌 보낸 사람의 「차단 풀기」는 그 도메인 차단을 푼다. 풀기가 404 면(이미 풀림) 알림 없이 푼 것으로 본다. 차단 · 풀기 뒤 세션이 바뀌었으면 옮기기 · 알림을 하지 않는다.
    • 조사는 objectParticle 로 — 영문 주소는 "을(를)", 따옴표 뒤에 붙여 쓴다(보드의 "‘주소’ 을" 과 다르다). 문구: 옮기지 않은 차단 "‘주소’을(를) 차단했습니다." · 되돌리기 뒤 "‘주소’ 차단을 풀고 메일을 되돌렸습니다."(옮기지 않았던 차단이면 "‘주소’ 차단을 풀었습니다.") · 풀기의 되돌리기 뒤 "‘주소’을(를) 다시 차단했습니다.".
  • 설정 › 보내기 · 읽기: 머리 설명 "고르는 대로 바로 저장됩니다. 이 설정은 어느 PC에서나 같습니다.", "저장했습니다"는 머리 오른쪽(SettingsPageFrame 의 title-aside 슬롯). 칸마다 저장은 한 번에 하나 — 보내는 동안 고른 값은 기억했다가 앞 저장이 끝난 뒤 보낸 값 · 서버의 답과 다르면 보낸다. "저장했습니다"는 다 끝나고 마지막이 성공했을 때. 끝내 실패하면 서버가 확인한 값으로 되돌린다.
  • 설정 › 차단한 주소: 검사 차례는 형식 → 내 주소 · 사내 도메인 → 메일 서비스 도메인 → 이미 차단 → 500개. 서버 거절(VAL_001 · MAIL_013)은 칸 아래 사전 문구(알림 없음). 성공하면 칸을 비우고 칸 아래에 "‘…’을(를) 차단했습니다."(role=status — 비어 있어도 영역은 늘 둔다). Enter 는 [차단]과 같다(isImeComposing). 줄을 풀면 초점은 다음 줄 → 앞 줄 → 칸. 엔진 규칙이 꺼져 있으면(isActive: false) 머리 아래 노란 줄(단추 없음) "다른 곳에서 메일 설정이 바뀌어 지금은 차단이 꺼져 있습니다. 주소를 차단하거나 풀면 차단이 다시 켜집니다." — 차단 · 풀기가 성공하면 켜진 것으로 본다(서버가 다시 켠다 — 계약 2절).
  • 새 토큰 · 아이콘: --color-on-brand-track · --color-on-brand-progress · --color-on-brand-divider · --color-warning-border, 아이콘 printer · clock · block(원 + 빗금).

11단계 구현하며 정한 보완 (2026-10-30 — 캔버스에는 아직 없다)

데이터 · 값은 계약 4절 "올리기 › 클라이언트"의 "구현하며 정함"이 같은 내용이다.

  • 올리기 차례: 고르자마자 서버에 올리기를 모두 만든다(임시 저장본에 바로 실려 나가기 확인 글이 그대로 맞다). 조각은 파일 3개까지 함께 보내고, 나머지 줄은 오른쪽에 "차례를 기다리는 중"(처음 몇 초의 "시간 계산 중" 자리).
  • 끝내기: 조각을 다 보냈으면 끝내기를 기다리는 동안 오른쪽 글은 "마무리하는 중"이다. 끝내기가 오래 진행 중이면 2 · 5 · 15 · 30초 간격으로 다시 묻고, 1분 넘게 그대로면 "오래 끊김"처럼 멈추고 [다시 시도].
  • 서버가 올리기를 잊었을 때(404): 멈춤 + "올리던 파일을 찾을 수 없습니다. [다시 시도]를 누르면 처음부터 올립니다." — [다시 시도]를 누를 때만 처음부터 올린다(사전 RES_001 문구로는 [다시 시도]의 뜻을 알 수 없어 화면 글로 둔다). 고른 파일이 없는 줄(저장본에서 이어 온 것)이나 보내기의 MAIL_016 뒤 다시 물어 404 면 "파일을 찾을 수 없습니다. 빼고 다시 올려 주세요."
  • 서버 거절 문구: 서버가 준 코드(FILE_001 · FILE_002 · FILE_005 · FILE_006 · VAL_001 · FILE_003)는 사전 문구다. 위 "올리기 전 검사"의 화면 글은 서버에 묻기 전(크기 · 개수 · 보관량)에만 쓴다 — "지금은 서버에 공간이 모자라 …"는 쓰지 않고 FILE_006 사전 문구.
  • 「링크로 보내기」: 대용량 사전 검사(개수 · 보관량)를 먼저 보고 통과할 때만 일반 첨부에서 뺀다. 걸리면 첨부는 그대로 두고 그 글을 오류 알림으로(보관량이면 + [대용량 첨부 보기]).
  • 다시 고르기: 이름 · 크기 · 고친 시각이 다르면 줄 안에 알리고 기다린다(보드). 같은데 맨 앞 조각의 SHA-256 이 다르거나 서버가 맨 앞 조각을 아직 못 받았으면 앞 올리기를 지우고 새로 올린다(계약). crypto.subtle 이 없는 곳(https · localhost 밖)에서는 지문 없이 보내고 다시 고르기는 늘 새 올리기다 — 운영은 https 라 해당 없다.
  • 인용 경계: 다시 연 답장 · 전달 저장본(보내기 취소로 되돌린 초안 포함)은 "맨 위 인용의 첫 자식이 ‘원본 메일’ 머리 문단 + 머리 표"로 원문 인용을 알아본다 — 그래야 덩이가 인용 위에 들어가고, 저장본에서 돌지 않던 인용 줄이기도 함께 돈다(2026-10-30 리뷰).
  • 검사 상태 다시 묻기: 쓰기 화면 · 읽기 화면은 검사 중인 파일이 있는 동안 15 · 30 · 60초 간격으로 최대 30번, 내려받기 화면은 20번. 읽기 화면은 막힌 [다운로드]를 누르면 한 번 바로 묻고, 메일을 바꾸거나 닫거나 세션이 바뀌면 멈춘다.
  • 보내기 직전 인용 줄이기: 서버가 넣을 덩이 몫(파일 수 · 이름 길이로 어림)을 더해 잰다.
  • 설정 › 대용량 첨부: 보낸 링크 머리의 메일 제목이 없으면(지웠거나 읽지 못함 — subject: null) 글자만, 빈 제목은 메일이 있는 것이라 링크다. [지우기]가 FILE_003(그사이 보내져 링크에 실림)이면 목록 · 사용량을 다시 받고 사전 문구로 알린다.
  • 내려받기 화면: 보낸 사람 이름이 비어 있으면 주소를 쓴다. 불러오는 동안은 role="status" "불러오는 중"(자리 막대는 aria-hidden).

14단계 구현하며 정한 보완 — 검색 (2026-11-06 — 캔버스에는 아직 없다)

주소 · 이어 찾기의 값은 계약 2절 "검색 — 14단계"의 클라이언트 줄이 같은 내용이다.

  • 크기: [조건] 단추는 높이 32 — 목록 표 별 · 보기 전환의 밀도 예외(--size-compact-control)를 다시 쓴다. 패널의 칸 · 단추 40(--size-filter-control), 조건 줄 40 · 줄 안 [조건 지우기] 36(--size-notice-action). 새 크기 토큰은 없다.
  • [조건] 패널
    • 단추의 title "조건으로 찾기", 조건이 걸려 있으면 화면 낭독에 ", 조건이 걸려 있음"을 붙인다.
    • 칸 안내는 "이름이나 메일 주소"(보낸 사람 · 받는 사람) · "제목에 든 말" · "내용에 든 말"(칸 이름 "내용"과 같게 — 2026-11-07)이고, 날짜 칸은 "시작 날짜" · "끝 날짜"다.
    • 날짜가 거꾸로면 알림 대신 칸 아래 "끝 날짜를 시작 날짜와 같거나 그 뒤로 골라 주세요.", 고를 수 있는 날짜 밖이면 "날짜는 1970년 1월 1일부터 9999년 12월 30일 사이에서 골라 주세요."(날짜 고르기는 이미 막고, 해를 손으로 칠 때만 보인다).
    • 패널의 [조건 지우기]는 패널 안의 칸만 비운다 — 보낸 사람 · 받는 사람 · 제목 · 내용 · 기간(전체 기간으로) · 날짜 · 첨부 체크와 칸 아래 알림까지(2026-11-07 사용자 확정 — 보드와 다르다). 패널은 열린 채이고 찾지 않으며 초점은 그 단추에 남는다. 걸린 조건 · 조건 줄 · 결과는 [찾기]를 누를 때까지 그대로이고, [찾기] 없이 닫았다 다시 열면 걸린 조건으로 다시 채운다. 비운 채 [찾기]를 누르면 검색어가 있으면 조건만 빼고 다시 찾고, 검색어도 없으면(조건만 걸린 검색) 검색을 닫는다. 걸린 조건을 바로 빼는 것은 결과의 조건 줄 [조건 지우기]다(조건만 걸린 검색이면 검색을 닫는다).
    • 패널이 열린 채 검색 칸에서 Esc 를 누르면 패널만 닫고 초점은 검색 칸에 남는다.
  • 기간: "최근 N"은 고른 순간 날짜로 바꾼다 — 그만큼 앞의 같은 날부터 오늘까지(1주는 7일 전, 달은 말일을 넘지 않는다).
  • 조건 줄 글: "조건: " 뒤에 쉼표로 잇는다 — "{이름} 님이 보낸 메일" · "{이름} 님이 받은 메일" · "제목에 ‘{말}’이/가 든 메일" · "내용에 ‘{말}’이/가 든 메일" · "최근 1달" 또는 "2026. 10. 1.부터 11. 5.까지"(같은 해면 끝 날짜의 해를 뺀다) · "…부터" · "…까지" · "첨부 있음". 시작과 끝이 같은 날이면 "2026. 11. 6. 하루", 보낸 사람 · 받는 사람 값이 내 이름이나 주소와 통째로 같으면(앞뒤 공백 · 대소문자 무시) "내가 보낸 메일" · "내가 받은 메일"(2026-11-07).
  • 결과 줄: 분할 보기는 보드대로 세 줄 64 다. 목록 보기(표)는 행 38 그대로 두고 제목만 강조한다(미리 보기 줄 없음). 결과 줄에도 별 · 고르기 칸을 남겼다 — 3단계부터 검색 결과에서도 여러 통을 고르고 처리한다(보드의 결과 줄에는 없다).
  • 글
    • 조건만으로 찾았을 때 머리 글은 "N통" · "찾는 중", 결과 없음은 "조건에 맞는 메일이 없습니다" / "조건을 바꾸거나 줄여 보세요.". 결과 없음의 도움말은 검색어 + 조건이면 "다른 말로 찾거나 조건을 줄여 보세요.", 검색어만(걸린 조건 없음)이면 "다른 말로 찾아 보세요."(2026-11-07 — 조건이 없는데 "조건을 줄여"라고 했다).
    • 옛 한계 안내("낱말 단위로 찾습니다 …")는 색인을 채우는 동안(building)만 결과 없음 아래에 더한다.
    • 한 글자 안내: "한 글자로만 찾을 때는 본문은 빼고 제목, 보낸 사람과 받는 사람, 첨부 파일 이름에서 찾습니다. 본문까지 찾으려면 두 글자 이상으로 찾아 주세요." — 색인이 준비됐고 본문을 찾을 말(검색어나 내용 조건)이 있을 때만.
    • 준비 상태를 받지 못하면(요청 실패) 안내 줄 · 옛 한계 안내 · 한 글자 안내를 모두 보이지 않는다.
  • [더 찾기]: 한 번에 다 찾지 못하면 "아직 찾은 메일이 없습니다" / "메일이 많아 한 번에 다 찾지 못했습니다. [더 찾기]를 누르면 이어서 찾습니다." + [더 찾기](찾는 동안 "찾는 중…"). 하나도 못 찾았으면 "찾는 중"을 이어 보이고, 하나라도 찾으면 먼저 보이면서 뒤에서 잇는다. 목록 보기의 [다음]은 이어 받는 동안 기다렸다가 넘기고, 그동안 꺼짐 · aria-busy 다.
  • 낱말이 너무 많은 주소(손으로 고친 주소): "찾을 말이 너무 많습니다" / "검색 칸과 [조건]에 넣은 낱말은 모두 합쳐 8개까지입니다. 낱말을 몇 개 줄여 주세요." — [전체 메일함에서 찾기] · 알림 없이, 조건 줄의 [조건 지우기]는 남긴다.
  • 강조 색: 노랑 #FCE7A4 는 색 토큰 --color-search-mark 하나로 두고, 본문 틀 안 CSS 는 같은 값을 직접 적는다(짝). 보낸 사람이 본문에 쓴 <mark> 도 같은 노랑으로 보인다(드물어 가르지 않는다). 미리 보기 줄(회색 --color-text-muted)의 맞은 말만 글자를 --color-text-secondary(#4A5363)로 진하게 한다 — 회색 그대로면 노랑 위 대비가 4.05:1 로 4.5:1 에 못 미친다(2026-11-07 사용자 확정 — 보드와 다르다, 앱도 같다). 제목 · 읽기 화면 · 본문 틀의 강조는 진한 글 위라 글자색 그대로다.
  • 긴 안내 글(결과 없음 · [더 찾기] · 안내 줄)은 낱말 가운데서 줄을 바꾸지 않는다(word-break: keep-all).

14단계 구현하며 정한 보완 — 같은 대화 (2026-11-06 — 캔버스에는 아직 없다)

받기 · 다시 받기의 값은 계약 2절 "대화 — 14단계"의 클라이언트 줄이 같은 내용이다.

  • 크기: 줄 48 · 머리글자 28, 펼친 줄의 [답장] · [이 메일만 열기] 36(안내 줄 단추의 밀도 예외 --size-notice-action 재사용), [더 보기] · [다시 시도] 44. 새 크기 토큰은 없다.
  • 보드와 다르게 한 것
    • 지금 보는 메일 줄의 바탕은 가장 가까운 기존 토큰 --color-brand-tint(#F3F8F8 — 보드 #F2F8F8, 시각 글자 대비 4.6:1).
    • 칸 제목은 h3 다 — 읽기 화면 제목이 h2 · 첨부 칸이 h3 이라 같은 층으로(보드는 제목을 h1 로 써서 h2 였다).
    • 펼친 본문은 읽기 화면과 같은 본문 틀(14px — 보드 13.5px)이다.
    • 펼친 줄에는 외부 이미지 안내 · 첨부 목록을 두지 않는다 — 본문을 줄였거나 HTML 을 보이지 못하면 읽기 화면과 같은 노란 안내 줄만 두고, 나머지는 [이 메일만 열기]로 연 읽기 화면에서 본다. 다만 첨부가 있으면 본문 아래 · [답장] 위에 클립과 "첨부 파일 N개는 [이 메일만 열기]를 누르면 볼 수 있습니다."(12.5 회색 — 수는 읽기 화면과 같은 셈: 본문 그림 빼고 대용량 첨부 더해)를 둔다(2026-11-07 — 본문은 첨부했다는데 표시가 없었다).
  • 보드에 없어서 정한 것
    • 칸 머리의 통 수는 다 셀 수 있을 때만 "같은 대화 N통"이고, 아직 다 받지 못해 수를 모르면 "같은 대화"다.
    • 남은 메일이 있으면 상자 아래 [더 보기](받는 동안 "불러오는 중…" · aria-busy).
    • 받지 못하면 칸 자리에 "같은 대화를 불러오지 못했습니다." + [다시 시도] + 문의 코드. 이 경로가 없는 옛 서버(404)면 칸이 없다.
    • 시각은 "오늘 오전 9:30" · "어제 …" · "10월 29일 (목) 오전 9:30" · 다른 해는 "2025. 10. 29. (수) …" 꼴이다.
    • 내가 보낸 메일 · 임시 저장본은 이름 자리에 "나" + 회색 메일함 이름("보낸편지함" · "임시보관함"), 다른 메일함에 든 내 메일은 "나"만. 지금 보는 메일 줄에는 회색 글을 두지 않는다(보드 그대로).
    • 미리 보기가 비면 제목을 보인다. 우리 웹 · 앱의 원문 인용 머리 "> 원본 메일"(끝에서 잘린 "> 원…" 포함)부터 뒤는 잘라 내고 남는 글이 없으면 제목이다 — 다른 메일 프로그램의 인용 꼴은 그대로(2026-11-07).
    • 줄 차례는 서버 차례(받은 시각 — 내 메일은 저장한 시각) 그대로이고, 보내기를 기다리는 메일(보내기 취소 · 예약)만 받은 줄의 맨 끝에 보낼 시각 차례로 둔다(2026-11-07 — 예약 줄이 시각 차례를 거슬렀다).
    • 임시 저장본 줄은 [답장]이 없고 [이 메일만 열기]가 쓰기 화면으로 연다. 예약한 메일은 [답장]이 없다(보내기 취소를 기다리는 메일은 읽기 화면처럼 있다).
    • 붙잡힌 메일의 시각 자리는 목록과 같은 보내기 취소 · 예약 표시(청록 굵게)다.
  • 펼치기: 안 읽은 메일을 펼치면 읽음이 된다(읽기 화면에서 연 것과 같다). 접힌 줄의 받아 둔 본문은 최근 10통까지만 기억한다. 펼친 본문 안에서 Ctrl+P 는 그 펼친 메일의 인쇄 화면을 연다.
  • 안 읽은 줄(2026-11-07 사용자 확정 — 보드와 다르다): 목록 줄과 같은 굵기 · 색이다 — 이름 650, 미리 보기 · 시각 600(붙잡힌 메일의 시각은 청록 그대로). 화면 낭독은 이름 앞에 목록과 같은 "안 읽음, ". 임시 저장본은 목록처럼 읽음 여부를 보이지 않는다. 이 탭에서 바꾼 읽음(줄을 펼침 · 읽기 화면에서 연 메일 · 목록과 여러 통 처리의 읽음 · 안 읽음 표시 · 메일함 ⋯ › 모두 읽음)은 줄에 바로 보이고, 바꾸지 못해 되돌리면 줄도 되돌린다. 받는 동안 바꾼 것과 받기를 시작할 때 보내던 것은 받은 값보다 이 탭의 값을 믿는다. 다른 탭 · 기기에서 바꾼 읽음은 다음에 칸을 다시 받을 때 보인다(그 줄을 펼치면 받은 본문대로 바로 풀린다). 읽은 줄의 이름(600)과 안 읽은 줄(650)의 차이는 작아 눈에 띄는 차이는 미리 보기 · 시각이다 — 읽은 줄 이름을 낮추는 것은 보드를 바꾸는 일이라 두었다.
  • [이 메일만 열기]: 분할 보기는 방문 기록을 쌓고(목록 줄을 누른 것과 같다), 목록 보기는 바꿔 끼운다(이전 · 다음과 같다 — [목록으로]가 기록을 쌓지 않게). 지금 목록에 든 메일은 그 목록 안에서, 검색 결과 · 예약됨은 받아 둔 줄에 있을 때만 그 목록 안에서, 그 밖은 그 메일이 든 메일함에서 연다.
  • 초점: 누른 단추가 사라지면(칸의 [다시 시도]가 성공 · [더 보기]로 끝까지 받음 · 다시 받아 줄이 빠짐) 그 줄의 펼침 단추 → 새 [다시 시도] → 칸 제목 차례로 옮기고, 칸이 통째로 사라지면 본문으로 옮긴다. [이 메일만 열기] 뒤에는 삭제 뒤 다음 메일로 갈 때처럼 따로 옮기지 않는다(두 길을 함께 고칠 거리).
  • 다시 받기: 새 메일 · 지움 · 옮김으로 메일함 통 수가 바뀔 때와 쓰기 화면에서 돌아올 때만 다시 받는다 — 읽음 표시만 바뀐 소식으로는 받지 않는다(받으면 메일을 열 때마다 대화를 한 번 더 받는다). 칸의 읽음은 위 "안 읽은 줄"처럼 이 탭에서 바꾼 것만 바로 적는다.

14단계 구현하며 정한 보완 — 목록 대화로 묶어 보기 (2026-11-06 — 캔버스에는 아직 없다)

받기 · 맞추기 · 줄에 하는 일의 규칙은 계약 2절 "대화 — 14단계"의 클라이언트 줄(목록을 대화로 묶어 보기)이 같은 내용이다.

  • 크기 · 모양
    • [대화로 묶기]는 36 — 안내 줄 단추의 밀도 예외(--size-notice-action)를 다시 쓴다. 새 크기 토큰은 없다. 분할 보기는 아이콘만(36×36 — 휴지통 [비우기]와 같은 결, 이름은 title · 화면 낭독), 목록 보기는 이름까지 보인다. 분할 보기에 이름까지 넣으면(99) 1440 에서 개수 글 자리가 25px 남짓이라 아이콘으로 두고, 설정 설명이 말풍선 모양이라고 알린다(2026-11-07 잼).
    • 켠 모양은 옅은 청록 바탕 + 진한 청록 글자이고 테두리는 그대로다(보드의 #9FD3D7 테두리는 새 색 토큰이라 두지 않았다).
    • 설정 › 메일 › 보내기 · 읽기의 한 줄은 외부 이미지 아래 가는 선 뒤 — 끔 · 켬 전환(.segmented--outline — 보내기 취소와 같은 모양)과 "켜면 답장과 전달로 이어진 메일을 메일함 목록에서 한 줄로 묶습니다. 메일함 목록 위의 말풍선 모양 [대화로 묶기] 단추로도 켜고 끌 수 있습니다." — 줄 이름은 단추와 같은 "대화로 묶기"(2026-11-07).
  • 줄
    • 고르기 칸 · 별을 남겼다(보드의 줄에는 없다 — 검색 결과 줄과 같은 판단, 3단계부터 목록에서 여러 통을 고른다). 메일이 모두 보내기를 기다리는 줄은 고르기 칸 · 별이 꺼짐(aria-disabled) + 까닭이고, 목록 보기에서 Ctrl · Shift + 클릭으로도 고르지 않는다.
    • 참여자: 다른 사람은 받은 차례대로, 나는 끝에 "나"(보드 "이도윤, 나" · "인사팀, 김민수, 나"). 보낸편지함 · 임시보관함에서 참여자가 나뿐이면 메일 목록처럼 "받는 사람: …"이다. 임시보관함의 제목 없는 줄은 흐린 "(제목 없음)"이다(메일 줄과 같다).
    • 목록 보기(표)는 고르기 · 별 · 참여자(+ 회색 "N통" — 머리 글은 모든 메일함에서 "참여자", 2026-11-07) · 제목 · 날짜다 — 크기 칸은 뺐다(대화의 크기는 마지막 메일의 크기가 아니다). 표 제목은 "{메일함} 대화 목록", 머리 고르기는 "불러온 대화 모두 고르기", 고른 수는 "대화 N개 선택됨"이다(분할 보기의 도구 줄은 "N개 선택됨" — 화면 낭독은 "대화 N개 선택됨", 단추 일곱과 한 줄 53 이 되게, 2026-11-07).
    • 목록 머리가 좁으면 개수가 먼저 줄고 메일함 이름은 남는다(개수에 마우스를 올리면 다 보인다). 묶지 않았을 때의 머리 글도 보드 글로 바꿨다("전체 9 · 안 읽음 2" → "메일 9통, 안 읽은 메일 2통"). 분할 보기(좁은 머리)는 안 읽은 메일이 있으면 "안 읽은 메일 N통"만, 없으면 "메일 N통" · "대화 N개"를 보이고 전체 글은 title · 화면 낭독에 둔다 — 머리의 칸 사이는 이름↔개수 6 · 머리↔단추 4 · 단추끼리 4(2026-11-07 — 1440 에서 안 읽은 수가 늘 잘렸다).
    • 줄에 한 옮기기 · 삭제 · 스팸 신고 · 스팸 아님 알림은 "{메일함}에 있는 메일 N통을 {갈 곳} 옮겼습니다." 꼴이다(줄의 통 수는 대화 전체라 "4통" 줄에서 "메일 2통"이 헷갈렸다 — 2026-11-07). 휴지통의 영구 삭제도 같은 꼴이다 — 확인 창 "휴지통에 있는 메일 N통을 영구 삭제할까요?" · 알림 "휴지통에 있는 메일 N통을 영구 삭제했습니다."(일부만 지웠으면 뒤의 "N통은 그사이 …"는 그대로). 묶지 않은 목록 · 읽기 화면은 그대로다.
    • 메일 화면은 탭이 다시 보일 때 설정을 마지막으로 받은 지 60초가 지났으면 조용히 다시 받는다 — 다른 탭 · PC 에서 바꾼 묶기면 목록을 다시 열고, 보내기 취소 초 · 외부 이미지도 받은 값을 쓴다(받는 동안 단추를 숨기지 않고, 세션이 끝났으면 받지 않는다 — 2026-11-07).
    • "새 메일 N통 · 맨 위로"는 맨 앞에 새로 생긴 줄을 1통, 새 메일이 든 기존 줄을 새 메일 수로 센다.
  • 줄의 별: 중요 표시가 하나라도 있으면 모두 풀고, 없으면 모두 표시한다. 보내기를 기다리는 메일만 중요 표시가 남으면 "보내기를 기다리는 메일은 목록에서 중요 표시를 바꿀 수 없습니다. 메일을 열어 바꿔 주세요."
  • 읽기 화면
    • [이전] · [다음]은 대화 사이를 오간다(앞뒤 줄의 마지막 메일). 위치는 "n / 대화 수"이고, 같은 대화 칸의 [이 메일만 열기]로 연 더 오래된 메일도 그 메일이 든 줄을 기준으로 한다. [목록으로]는 그 줄로 초점을 돌려준다.
    • [삭제](한 통) 뒤 열 메일(분할 보기 — 목록 보기는 지금처럼 목록으로): 그 줄에서 지운 메일의 다음 칸(더 오래된 메일), 없으면 앞 칸 → 줄이 비면 다음 줄의 마지막 메일, 없으면 앞 줄의 마지막 메일. 아직 다시 받기 전이라 줄에 남아 있는 옛 마지막 메일이면 남은 메일의 첫 칸이다(메일 목록의 삭제 뒤와 같은 결).
  • 첫 진입: 설정을 받기 전에는 이 브라우저가 그 직원에게서 마지막으로 안 값(처음이면 끔)으로 연다 — 서버 값이 다르면 받는 즉시 바꾼다. 설정을 받지 못했으면 그 값대로 두고 [대화로 묶기]도 누를 수 있다(받는 동안에는 단추를 숨긴다). 로그아웃해도 그 값은 남고, 다른 직원이 로그인하면 그 직원의 값이다.
  • 알려진 한계: 여러 통인 줄의 마지막 메일만 읽으면 다음 맞추기까지 줄이 굵은 채다. 마지막 메일이 아닌, 보내기를 기다리는 메일은 풀린 뒤에도 다음 맞추기까지 고를 수 없다(계약 2절). 분할 보기 머리는 [비우기] 단추가 있는 휴지통 · 스팸메일함, 세 자리 이상 안 읽은 수, "받은편지함"보다 긴 메일함 이름, 1120~1220 폭에서는 1440 에서도 개수 글이 잘린다(title 로 다 보인다). 분할 보기 도구 줄의 "N개 선택됨"은 세 자리부터 두 줄로 넘친다(메일 목록의 "123통 선택됨"도 같다).

휴대폰 앱 받기 구현하며 정한 보완 (2026-11-08 — 캔버스에는 아직 없다)

데이터 · 경로는 위 "휴대폰 앱 받기" 머리말(서버와 맞춘 약속 — 계약 1절 "휴대폰 앱 받기 — 사내 배포")과 같다.

  • 올린 날은 다른 설정 화면처럼 브라우저 시간대의 날짜다("2026년 11월 18일" — 2단계 인증의 "…부터 켜짐"과 같은 표기). 대용량 첨부의 끝나는 날(한국 날짜 — kst-date.ts)과 달리 직원만 보는 날이다. 크기는 첨부와 같은 표기("31 MB")이고, 읽을 수 없는 값(빈 버전 · 날짜가 아님 · 음수 크기)은 그 칸째 뺀다.
  • 받지 못함은 다른 설정 화면의 불러오기 실패와 같은 모양(MailLoadError — 빨간 아이콘 · 사전 문구 · 문의 코드 · [다시 시도])을 칸 자리에 둔다(/app 은 카드 자리). 알림은 띄우지 않는다. 받는 동안은 빈 자리(aria-busy)다 — 다른 설정 화면과 같다.
  • 앱 정보는 들어올 때마다 새로 받는다(운영자가 새 판을 올렸을 수 있다). 세션이 바뀌면(로그아웃 · 만료 · 다른 계정) 늦게 온 응답을 버리고 받아 둔 값도 지운다.
  • 문의 줄은 로그인 화면의 LoginSupportLine 을 그대로 쓴다. /app 은 본문이 14 라 문의 줄 글만 13 이다(size="base").
  • 앱 그림의 모서리는 한 변의 1/4 이다(48 은 12, 52 는 13 — 보드 값).
  • 폭 기준: 설정의 QR 줄을 빼는 좁은 폭은 480 이하(공개 내려받기 화면의 좁은 폭과 같은 값), /app 의 넓은 화면은 그 반대(481 이상)다.
  • 글자 크기 17(/app 의 앱 이름 · 받을 앱이 없을 때의 제목)은 토큰 --font-size-title-md 를 더했다(크기 · 밀도 예외 토큰은 아니다).

15단계 구현하며 정한 보완 (2026-11-09 — 캔버스에는 아직 없다)

메일함 · 차단 · 예약 쪽을 먼저, 읽기 화면 · 목록 · 상단 바 쪽을 뒤에 적는다.

  • 메일함 나무 도우미(shared/utils/mailbox.ts): 패널 차례로 편 나무 mailboxTree(깊이 · 위 메일함 · 하위가 있는지 · 묶음), 화면 낭독 이름 mailboxSpokenName("거래처 안의 2025" — 더 깊으면 "거래처 안의 2025 안의 정산"), 알림 이름 mailboxDisplayName(같은 이름이 다른 곳에도 있을 때만 "거래처 › 2025" — 위 메일함까지 같으면 가를 수 있을 때까지 올라간다, 맨 위의 메일함은 이름 그대로). [이동 ▾] · 끌어다 놓기 · 옮기기 창이 함께 쓴다. 알림의 이름은 글자 모양으로 견주고(NFC — 대소문자는 가린다), 같은 자리 겹침은 대소문자를 가리지 않는다(엔진과 같다).
  • 옮기기 창
    • 옮길 곳은 ⋯ 「하위 메일함 만들기」가 있는 메일함(받은편지함 · 내 메일함)과 맨 위다. 하위를 둘 수 없는 메일함(보낸편지함 등 · 보관함) 아래의 메일함은 그 위까지 모두 둘 수 있는 곳일 때만 넣는다 — 다른 메일 프로그램이 보낸편지함 아래에 만든 메일함은 옮길 곳에 없다.
    • 화살표는 꺼진 줄에도 초점을 옮긴다(까닭을 읽게 — 고른 것은 그대로). 처음과 끝에서 돌고, Home · End 도 된다. 처음 초점은 고를 수 있는 첫 줄이다(로빙 tabindex).
    • 맨 위에서 옮긴 것을 되돌린 글은 "‘2025’ 메일함을 다른 메일함 밖으로 되돌렸습니다."(보드에 없다 — 맨 위로 옮긴 글과 같은 결). [되돌리기]가 실패하면(그사이 원래 자리에 같은 이름 · 원래 상위가 지워짐) 사전 문구 알림만.
    • 옮길 수 없는 자리였으면(400 VAL_001 — 그사이 나무가 바뀜) 알림을 본 뒤 메일함 목록을 다시 받고 창은 둔다(다시 고를 수 있게). REQ_001(그 메일함을 정리하는 중) 등도 알림만 · 창은 그대로.
    • 옮기는 동안 [옮기기]는 aria-disabled + aria-busy(누른 단추가 초점을 잃지 않게), 고르기 전에는 disabled.
  • 하위까지 지우기: 지우는 동안 나무의 모든 메일함(하위 포함)을 "지우는 중"으로 둔다 — 그 메뉴들이 꺼지고, 나무 안의 메일함을 따로 지우기 시작하지 않는다. 하위 메일함을 보고 있었어도 끝나면 받은편지함으로 간다. 하위가 없는 메일함은 지금처럼 withChildren 없이 보낸다.
  • 차단할 때 받은 메일 옮기기
    • 「이 메일도 …」가 켜져 있으면 그 메일을 먼저 옮긴 뒤 받은편지함 정리를 시작한다. 그 메일이 받은편지함에 있었으면 옮긴 수에 넣는다. 옮긴 뒤 읽기 화면은 다음 메일을 열지 않고 목록으로 간다 — 분할 보기는 읽기 칸이 비고 목록 보기는 목록이다(2026-11-09 실사용 시험: 다음 메일이 같은 보낸 사람의 것이면 열자마자 읽음이 되고, 곧 정리가 스팸메일함으로 옮겨 받은편지함 주소에 [스팸 아님] · 차단 안내가 뜬 채 남았다). 목록은 정리가 끝나면 다시 맞춘다. 새 칸을 켜지 않은 차단은 지금처럼 삭제와 같이 다음 메일이다.
    • 이 탭에서 받은편지함을 정리하는 중에 [다시 시도]를 누르면 "‘…’을(를) 차단했습니다. 받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요." + 같은 [다시 시도](보드에 없다 — 필터 적용의 busy 와 같은 결).
    • 그사이 차단을 풀었으면(404 RES_001) 진행 알림을 닫고 사전 문구만 보인다.
    • 끝났거나 나아간 뒤 멈췄으면 개수를 다시 받고, 받은편지함 · 스팸메일함 목록을 보고 있으면 그 목록도 맞춘다(필터 적용과 같다).
    • 되풀이(cursor · 25초 · 나아가지 못한 응답 3번)는 필터 적용과 함께 쓴다(stores/inbox-cleanup-run.ts).
    • 이 탭에서 받은편지함 정리(필터 적용 · 차단 정리)가 하나라도 도는 동안은 필터 고치기 창의 「받은편지함에 이미 있는 메일도 정리하기」도 끈다 — 차단 창의 새 칸과 같은 규칙(inboxBusy)이다. 두 칸의 까닭 글은 같은 공용 글 "받은편지함을 정리하는 중이라 지금은 고를 수 없습니다."(Mailbox15 보드의 차단 칸 글)이고, 7단계 칸 글 "다른 필터로 받은편지함을 정리하는 중입니다. 끝난 뒤에 고를 수 있습니다."도 이 글로 바꿨다 — 칸을 끈 까닭이 필터가 아니라 차단 정리일 수도 있어서다(메인이 정함). 같은 까닭으로 필터 적용 알림의 [다시 시도]도 이 탭의 차단 정리를 보고(inboxBusy), 그때 글은 공용 글 "받은편지함을 정리하는 중입니다. 끝난 뒤 다시 시도해 주세요."다(7단계 "다른 필터로 …"에서 바꿨다).
    • 이 탭에서 차단 정리가 도는 동안은 스팸메일함 비우기를 시작할 수 없다(메뉴 · 목록 머리 [비우기] · 확인 단추가 같은 판정 — 휴지통 비우기와 메일함 지우기를 함께 돌리지 않는 규칙과 같은 결). 옮겨 온 메일이 들어오자마자 영구 삭제되지 않게다. 반대로 이 탭에서 스팸메일함을 비우는 동안은 차단 창의 새 칸을 끄고(까닭 "스팸메일함을 비우는 중이라 지금은 고를 수 없습니다.") 차단 정리를 시작하지 않는다 — 알림의 [다시 시도]를 누르면 "‘…’을(를) 차단했습니다. 스팸메일함을 비우는 중입니다. 끝난 뒤 다시 시도해 주세요." + 같은 [다시 시도](2026-11-09 재리뷰). 필터 적용은 스팸메일함에 넣지 않으므로 막지 않는다(inboxBusy 는 그대로이고 차단 정리만 mail-block-run.store 의 startBlock 을 본다). 다른 창과는 서버가 요청마다 스팸메일함 처리 잠금을 잡아 겹친 요청을 429 로 막지만, 잠금은 요청 하나씩이라 두 창의 비우기와 차단 정리가 요청 사이에 번갈아 돌 수 있다 — 차단 정리가 옮겨 넣은 메일을 다른 창의 다음 비우기 요청이 지울 수 있다(알려진 한계 — 다른 창끼리의 휴지통 비우기와 메일함 지우기와 같다, 계약 2절 "보낸 사람 차단").
  • 지금 보내기
    • 서버가 혹시 409 MAIL_014(이미 보냄)로 답해도 보낸 것으로 다룬다("메일을 보냈습니다." — 약속은 200 이다).
    • 503 MAIL_001 · SYS_001 의 갈래(임시보관함 · 예약 그대로 · 예약만 풀림 · 다시 낸 발송이 기다림 · 다시 받지 못함)는 위 "예약한 메일 [지금 보내기]"에 적었다 — 사전 문구 알림은 끄고 화면 글 하나로 알린다. 판정은 shared/utils/pending-send.ts 의 unsentReleaseState 하나다(임시보관함을 먼저 보고, 다음으로 기다리는 발송의 종류). 다시 낸 발송이 보내기 취소를 기다리는 갈래는 리뷰에 없던 것이라 구현하며 정했다 — 같은 요청을 다시 보내도 서버가 그 상태로 200 을 주므로 보낸 것과 같게 알린다(받는 사람별 결과는 모르니 일부가 받지 못한 문장은 없다). 받는 사람이 모두 받지 않았거나(400 MAIL_010 · 503 MAIL_011) 예약이 그사이 사라졌으면(404) 사전 문구 알림만 보이고 그 자리에 두며, 예약 표시 · 예약됨 수 · 보이는 목록을 서버 기준으로 다시 맞춘다.
    • 보내기 취소 알림의 마감은 쓰기 화면과 같은 규칙이다(설정한 초를 알면 "지금 + 초 − 1초", 모르면 보낼 시각까지 · 30초를 넘지 않게 — undoToastDeadline).
    • 안내 줄이 좁으면(글이 12rem 보다 좁아질 때) 단추 셋을 묶음째 다음 줄 오른쪽으로 보낸다 — 단추가 셋이 되며 360 폭에서 글이 한 글자 폭으로 눌리지 않게(보드는 넓은 폭만 그렸다).
  • 모달 창의 제목 · 글(모든 창 — ModalDialog): 낱말 가운데서 줄을 바꾸지 않는다(word-break: keep-all — "지울까/요?"가 나왔다, 15단계 전부터). 띄어쓰기 없는 긴 글(긴 주소 · 파일 이름)은 창 밖으로 넘치지 않게 아무 데서나 끊는다(overflow-wrap: anywhere). 슬롯에 넣은 칸 · 목록은 쓰는 쪽이 정한다(2026-11-09 실사용 시험).

읽기 화면 · 목록 · 상단 바

  • 좁은 읽기 칸의 기준(보드에 없는 것): 컨테이너는 도구 줄 띠(.reader__toolbar-bar — 칸 전체 폭)다. 목록 보기는 앞에 [목록으로] · 이전/다음이 함께 놓여 이름이 다 보이는 줄이 800(콘텐츠 열)보다 조금 넓다 — 그래서 목록 보기만 단추 줄의 최대 폭을 풀고 접는 폭을 850 · 휴지통 875 로 두었다(분할 보기의 필요 폭에 [목록으로] · 이전/다음 · 위치 글을 더한 어림 — 실사용 시험에서 넉넉했다). 화면 폭 기준은 tokens.css 머리에 한 줄씩 적었다. 분할 보기는 보드의 570 · 595 가 아니라 580 · 605 다 — 실측한 필요 폭(받은편지함 569.19 · 휴지통 594.83)에 여유가 0.81 · 0.17px 뿐이라 글자 · 여백이 1px 만 늘어도 좁은 구간에서 두 줄로 꺾인다(15단계 실사용 시험).
  • 메뉴 폭: 옮길 메일함 메뉴는 이름 길이만큼(200 부터 320 까지 — 좁은 화면은 화면 폭 − 80 까지) 넓어지고 그보다 긴 이름은 줄을 바꾼다(보드는 줄을 바꾸지 않는 짧은 이름만 그렸다).
  • 좌우 바꾸기: 오른쪽 끝에 맞춘 메뉴(⋯)가 왼쪽 밖으로 넘칠 때도 같은 규칙으로 단추 왼쪽 끝에 맞춘다(360 에서 ⋯ 가 둘째 줄 맨 앞으로 내려간다). 양쪽 다 넘치면 덜 넘치는 쪽이다(위아래와 같은 결).
  • 흐린 줄 더: 하위 메일함이 있는 휴지통 · 임시보관함(다른 메일 프로그램이 지운 폴더를 휴지통 아래에 둔다)도 까닭 글 없이 흐린 줄로 남겨 그 하위 메일함을 고를 수 있게 한다. 흐린 줄 아래에 보일 하위 메일함이 하나도 없으면 그 줄도 뺀다.
  • 전체 메일함 검색에서 [이동 ▾] · 끌기: 고른 메일이 모두 든 메일함만 "메일이 있는 메일함"으로 친다(메일마다 다르면 아무 데나 옮길 수 있다 — 지금과 같다).
  • 끌어다 놓기
    • 줄 전체가 끌린다(링크만이 아니라) — 목록 보기 표의 글은 마우스로 끌어 고를 수 없게 된다(두 번 눌러 고르기는 된다).
    • 끄는 표는 메일 화면에 늘 그려 두는 화면 밖 요소를 브라우저가 찍은 것이다(setDragImage) — 운영체제 · 브라우저마다 투명도 · 커서 모양이 조금 다르다.
    • 이 탭에서 시작한 끌기만 받는다(다른 탭 · 바깥에서 끌어 온 같은 꼴의 자료는 놓을 수 없다).
    • 열어 둔 메일을 끌어 옮겼으면 목록 도구 줄로 옮길 때처럼 목록 주소로 바꿔 끼운다.
    • 패널의 메일함이 많아 아래가 가려져 있으면 패널 끝에서 저절로 스크롤되는지는 브라우저가 한다 — 브라우저에서 확인한다.
  • 파일 놓기 무시는 앱 틀(AppShell)에: 처음에는 메일 화면에만 걸어 주소록 · 설정에 놓은 파일은 여전히 브라우저가 열었다(15단계 실사용 시험) — 로그인 뒤 모든 모듈 화면이 쓰는 앱 틀로 옮겼다. 쓰기 화면 · 서명 설정도 앱 틀 안이라 두 창 처리기가 함께 걸린다 — 파일을 받는 화면이 떠 있으면 무시하는 쪽이 아무것도 하지 않는다(어느 쪽이 먼저 걸렸는지와 상관없이 받는 화면이 받는다). 앱 틀 밖의 화면(로그인 · 인쇄 · 휴대폰 앱 받기 · 공개 내려받기)은 그대로다.
  • 상단 바: 줄어든 이름 · 주소의 title 은 이름 · 주소 칸 전체에 둔다(넓은 폭에서 주소가 줄어도 같은 글). 검색 칸(메일 검색 · 비활성 통합 검색)은 최소 폭을 0 으로 둔다 — 입력 칸의 기본 폭에 묶여 360 에서 278 로 바 안쪽(256)을 넘고 화면이 옆으로 10px 밀렸다(14단계부터 — 15단계 실사용 시험).
  • 같은 대화 [모두 펼치기]
    • 손으로 펼친 줄은 지금처럼 누른 그 자리에서 본문을 받고 받는 대로 읽음이다. [모두 펼치기]로 펼친 줄만 화면에 들어올 때 차례로 받는다 — 차례가 오기 전에 화면 밖으로 나간 줄은 건너뛰고 다시 들어오면 다시 줄을 선다.
    • 받는 사이 화면 밖으로 나갔으면 읽음으로 바꾸지 않고, 다시 들어올 때 읽음이다(읽음이 실패해도 드나들 때마다 다시 묻지 않는다).
    • 화면에 들어왔는지 알 수 없는 브라우저(IntersectionObserver 없음)는 펼친 줄이 생기면 들어온 것으로 본다 — 그래도 하나씩 받는다.
    • 펼칠 줄이 없으면(지금 메일만 받고 나머지를 받는 중) 단추가 없다. 그사이 지워진 메일(본문이 404)의 줄에는 머리 [답장]도 없다(아래 [답장]과 같다).

16단계 구현하며 정한 보완 — 주소록 (2026-11-10 — 캔버스에는 아직 없다)

  • 파일은 이 브라우저에서 읽는다(frontend/src/shared/utils/contact-import/ — 순수 함수 · 새 의존성 없음): 글자 text.ts · CSV csv.ts · vCard vcard.ts · zip zip.ts · 엑셀 xlsx.ts · 머리 줄 → 칸 table.ts · 값 다듬기 values.ts · 넣을 · 이미 있는 · 넣지 못하는 records.ts · 요청 묶음 batches.ts · 형식 가르기 read-file.ts. 모두 입력 길이 · 줄 수에 비례하게 읽는다(2026-11-10 리뷰 — 조각은 배열에 모아 한 번에 잇고, 큰 배열을 push(...) 로 펼쳐 넘기지 않고, 구분자 둘레 빈칸은 정규식이 아니라 나눈 뒤 trim).
    • 엑셀은 zip 가운데 목록 → DecompressionStream('deflate-raw') → DOMParser(공유 문자열 · 첫 시트 · 칸 안 문자열). 첫 시트는 workbook 의 첫 <sheet>(관계 파일로 시트 파일을 찾는다). 푼 크기가 항목 하나 64MB · 모두 96MB 를 넘는 순간 멈춘다(압축 폭탄 — 목록에 적힌 크기는 믿지 않는다). DOCTYPE · ENTITY 가 든 XML · 암호를 건 항목 · ZIP64 는 "열지 못했습니다". 열은 XFD(16,384번째)까지만 읽고 넘는 칸은 버린다. 늘어놓는 칸 수(사이의 빈 칸 포함)의 합이 400만을 넘으면 "열지 못했습니다" — 먼 열에 칸 하나씩 둔 줄이 많으면 줄마다 빈 칸 16,384개로 부풀기 때문이다.
    • 옛 오피스 파일(CFB)은 안의 목록 이름으로 가른다 — 암호를 건 .xlsx(EncryptedPackage)는 "열지 못했습니다", Workbook · Book 이 있으면 .xls 글, 그 밖은 "넣을 수 없는 파일".
    • 2.1 QUOTED-PRINTABLE 은 줄 끝 "=" 로 접힌 줄을 먼저 바이트로 이은 뒤 CHARSET 대로 푼다(삼성 안드로이드는 한글 한 글자를 두 줄에 나눈다). 아이폰의 묶음 라벨(item1.X-ABLabel)로 종류를 정한다. 사진 · 로고 · 소리 · 열쇠(PHOTO · LOGO · SOUND · KEY)는 이어지는 줄까지 모으지 않고 건너뛴다(사진 든 vCard 는 이어지는 줄이 수만 개다).
    • 'cp949' 라벨은 쓰지 않는다(RangeError) — EUC-KR 라벨이 브라우저에서 CP949 전체다. Node(ICU)의 euc-kr 은 CP949 확장 글자(똠 등)를 모르므로 시험은 KS X 1001 글자만 쓴다.
  • 머리 줄 알아보기: 머리 이름은 공백 · 기호를 빼고 소문자로 견준다("E-mail 1 - Value" → email1value, "회사·소속명" → 회사소속명). 네이버 열 이름의 띄어쓰기 · 괄호가 조금 달라도 낱말(휴대폰 · 회사 · 집 · Fax · 직접입력 · 생일 · 양력음력 · 기념일 · 우편번호 · 주소 · 메신저 …)로 알아본다. "번호" 한 낱말 열은 전화로 보지 않는다(일련번호 열이 흔하다 — 네이버의 회사번호 · 집번호 · 기타번호는 따로 안다). Outlook 의 "Title"은 경칭(Mr. · Ms.)이라 쓰지 않고 "Job Title"이 직책이다. 네이버 CSV 처럼 값 대부분이 앞에 '를 달았으면 모두 떼고, 그 밖은 엑셀 수식 막기로 붙인 '(뒤가 = + - @)만 뗀다.
  • 이름: 성이 한글 · 한자 · 가나면 성 다음 이름(한글끼리는 붙여 쓴다), 그 밖은 이름 · 중간 이름 · 성 차례로 띄운다(영문 "John Smith").
  • 종류: CSV 의 종류를 모르는 이메일 칸은 회사(연락처 추가 창의 처음 값과 같다), vCard 의 종류가 없는 EMAIL 은 기타(우리 내보내기가 기타를 INTERNET 만으로 쓰므로 다시 가져와도 같다). 종류를 모르는 전화 칸(전화번호 · 연락처 · Google 의 빈 라벨)은 번호로 짐작한다 — 국내 휴대전화(01x)면 휴대전화, 아니면 기타. 엑셀이 숫자로 바꿔 앞의 0 이 빠진 휴대전화(1012345678)는 0 을 되살린다.
  • 넣지 못하는 줄의 까닭 차례는 이메일 형식 → 이름과 이메일이 모두 없음 → 한 줄 칸 100자 → 메모 1,000자. 한 줄 칸은 어느 칸인지를 앞에 붙인다("이름은 100자까지 쓸 수 있습니다." — 연락처 창은 칸 아래라 칸 이름이 없지만 목록에서는 가를 수 없어서). 이메일은 연락처 창과 같이 서버보다 좁게 본다. 한 연락처 안의 같은 이메일은 하나만, 이메일 · 전화는 20개까지.
  • 형식에 맞지 않는 전화번호(내선 · 글자가 든 번호 — "02-555-0147 내선 3")는 버리지 않고 가져온 정보의 "전화: …" 줄로 둔다(체크 칸을 끄면 넣지 않는다). 전각 숫자 · 여러 가지 줄표는 보통 글자로 고친다.
  • 가져온 정보 줄: "회사 주소 · 집 주소 · 기타 주소 · 주소(네이버 주소직접입력은 그 이름을 앞에) · 생일 · 기념일 · 홈페이지 · 별명 · 메신저 · 전화". 날짜는 "1988년 3월 14일"(해를 모르면 "3월 14일", 음력이면 뒤에 " (음력)"), 한글 주소는 큰 곳부터 띄어 쓰고 영문은 작은 곳부터 쉼표로, 우편번호는 뒤에 괄호로, 우리나라 이름은 쓰지 않는다. 값은 한 줄 글로 다듬는다(탭 · 줄바꿈 · 제어 문자는 띄어쓰기 — vCard 별명 · 형식이 틀린 전화 등), 붙인 메모는 연락처 창과 같은 메모 정리를 한 번 더 지난다 — 서버는 메모의 줄바꿈 밖 제어 문자를 받지 않아 하나라도 남으면 그 묶음 전체가 400 이다.
  • 미리 보기: 빈 묶음(이미 있는 연락처 0 · 넣지 못하는 줄 0)은 두지 않는다. 넣을 연락처가 없으면 표 · 그룹 · 메모 칸도 두지 않는다. 칸이 없는 정보가 든 연락처가 없으면 메모 체크 칸을 두지 않는다. 그룹 글은 이미 있는 그룹만이면 "파일에 적힌 그룹 거래처, 세무는 이미 있는 그룹에 넣습니다.", 새로 만들 그룹만이면 "파일에 적힌 그룹 동호회, 학교는 새로 만듭니다."(보드는 섞인 꼴만). vCard 의 넣지 못하는 수는 "N명". 그룹이 하나도 없으면 「이미 있는 그룹」을 끈다(aria-disabled).
  • 겹침 열쇠는 서버와 같다(짝: backend modules/contacts/contact-import.ts 의 phoneDigitsKey · namePhonesKey): 이메일이 없으면 이름(앞뒤 공백만 뺌) + 전화 숫자 모음 — 번호마다 숫자만 남기고, 숫자가 없는 번호는 빼고, 겹침을 없앤 뒤 차례로 잇는다(같은 번호를 두 번 적은 줄도 한 번 적은 줄과 같은 사람이다).
  • 요청 묶음: 확인 · 넣기 · 되돌리기 본문은 100명 · JSON 값 8,000개 · 700KB 가운데 먼저 닿는 데서 끊는다(batches.ts — 값은 서버 json-shape.ts 와 같은 셈, 바이트는 UTF-8 길이). 칸을 한도까지 채운 연락처(이메일 · 전화 20개씩) 100명은 값 13,000개 남짓이라 61명 · 39명으로 나뉜다 — 서버 본문 한도(값 10,000 · 1MB)를 넘지 않게.
  • 이미 있는지 묻기: 넣을 수 있는 연락처만 위 묶음으로 서버에 묻는다(POST /contact-import-checks). 남은 자리를 다 채우면 더 묻지 않는다(요청 수 한도를 아끼려고) — 그래서 한도에 걸린 파일이면 묻지 않은 연락처 가운데 이미 있는 것도 "넣을 연락처"와 "나머지 N명"에 세진다(넣지 않으니 결과는 같다). 묻기가 실패하면 사전 문구 알림 뒤 파일 고르기로 돌아간다.
  • 넣기: 새 그룹을 먼저 만든다 — 그사이 같은 이름이 생겼으면(CONTACT_001) 그 그룹을 쓰고, 100개면(CONTACT_002) 그 그룹 없이 넣는다(결과의 "그룹에 넣지 못했습니다"에 센다). 만들기의 응답만 잃은 그룹(응답 없음 · 5xx — 서버에서는 만들어졌을 수 있다)이 [다시 시도]에서 CONTACT_001 이면 이번에 만든 그룹으로 센다([되돌리기]가 지운다). 응답을 잃은 적 없이 CONTACT_001 인 그룹(다른 창 · 기기가 만든 그룹)은 넣기만 하고 [되돌리기]는 지우지 않는다. 넣는 사이 주소록이 찼으면(409 CONTACT_002) 남은 자리를 다시 물어 그만큼만 넣고 나머지는 "넣지 못했습니다", 넣을 그룹이 그사이 지워졌으면(404 RES_001) 그룹을 다시 받아 그 그룹을 빼고 한 번 더 보낸다. 메일 서버가 일부만 받으면 결과에 "N명은 넣지 못했습니다."를 덧붙인다(보드에 없는 글).
  • 멈춘 알림도 ✕까지 남는다: 보드는 결과([되돌리기]) 알림만 적었지만, 멈춘 오류 알림의 [다시 시도]가 남은 연락처를 넣고 [되돌리기]를 받는 유일한 길이라 같이 남긴다(되돌리다 멈춘 알림도). 알림은 넘쳐도 다른 알림부터 닫는다. 결과 알림 위에 다른 결과가 쌓이면 셋까지 보인다.
  • 차례: 한 탭에서 가져오기 · 되돌리기는 하나씩 돈다. 도는 동안 누른 [되돌리기] · [다시 시도]는 버리지 않고 끝난 뒤에 한다 — 누른 뒤 세션이 바뀌었으면 하지 않는다(다른 직원의 주소록을 건드리지 않게).
  • 내보내기: 처음 초점은 [내보내기](내 주소록 전체 · vCard 를 바로 받게), 그룹이 하나도 없으면 「그룹 하나」를 고를 수 없다(disabled). 화면의 파일 이름 날짜는 이 PC 의 날짜이고 서버가 붙이는 이름은 한국 시간이다 — 자정 무렵 바깥 시간대의 PC 에서는 하루 다를 수 있다(알려진 한계).
  • 알림 부품: ✕로 닫을 때까지 남는 알림은 toast.store 의 sticky(진행 알림의 succeed · fail 옵션 — useToast). 성공 알림에도 ✕ 를 단다(브랜드 바탕용 흐린 청록, 마우스를 올리면 흰 글자). 지금 쓰는 알림의 시간 · 모양은 그대로다.

16단계 구현하며 정한 보완 — 서명 (2026-11-10 — 캔버스에는 아직 없다)

  • 서명 자리(components/mail/editor/mail-signature-block.ts): 편집기 안에서는 블록 노드 하나(mailSignature — 안은 서명의 문단)이고, 보낼 때 · 저장할 때 빈 문단 뒤의 속성 없는 <div> 로 나간다 — 앱과 같은 경계(계약 2절 "앱이 만드는 모양")라 웹 · 앱 어느 쪽 저장본을 다시 열어도 그 자리를 찾는다. 안의 문단은 다른 문단처럼 margin: 0 이 붙는다. 다시 열 때는 "맨 위 단계(문서 · 하나뿐인 감싸는 글꼴 div 바로 아래)에서 빈 문단 바로 뒤의 속성 없는 div 이고 글이나 그림이 있다"로 찾는다 — 원문 인용 안 · 꾸민 div · 빈 div(인용 경계 표시가 남은 것)는 서명 자리가 아니다. 쓰기 화면이 처음 넣는 서명은 표시(data-mail-signature — 서버 정화가 지운다)로 안다.
  • 서명 앞의 빈 줄은 늘 둔다(2026-11-10 리뷰 M3 — 빈 줄에 글을 쓰거나 지운 채 저장하면 다시 열 때 서명 자리를 잃었다): 그 빈 줄에 글을 쓰면 쓴 글 아래 · 서명 위에 새 빈 줄이 생기고, Backspace · Delete 로 빈 줄을 지우면 다시 생긴다(커서는 쓰던 자리 · 윗줄 끝, 실행 취소는 그 입력과 함께 — 쓴 글은 지우지 않는다). 한글을 조합하는 중에는 미뤘다가 다음 입력에서 넣고, 보내고 저장하는 모양도 빠진 빈 줄을 한 번 더 넣는다. 알려진 한계: 16단계 앞에 저장한 본문(서명이 문단으로만 들어 있다)은 서명 자리를 찾지 못한다 — 그 서명은 글로 남고, [서명 ▾]는 ✓ 없이 열리며 고르면 처음 넣는 자리에 새로 넣는다.
  • 서명 자리는 하나: 서명 안에서 복사해 붙여 넣으면 편집기가 서명 자리째 붙이는데, 그때 처음부터 있던 것만 두고 나머지는 글로 푼다(쓰기 화면의 편집기에서만 — 붙여 넣기 · 끌어 놓기 모두).
  • 어느 서명이 들었나: 서명 자리의 글(공백 · nbsp 를 하나로 줄여)과 그림 주소를 저장한 서명(편집기로 읽은 모양)과 견준다 — 만든 차례로 먼저 맞는 서명에 ✓. 본문이 바뀔 때마다 다시 잰다(서명 안을 고치면 ✓ 가 빠진다).
  • 글(보드에 없어 지었다): 넣지 않음을 고른 뒤 낭독은 같은 틀 "서명을 ‘넣지 않음’으로 바꿨습니다." — 방향 조사는 이름 끝소리로 고른다("‘영문’으로" · 영문 이름은 "(으)로"). 새 서명(저장하지 않은 줄)을 고치다 다른 서명을 고르면 "다른 서명을 고르면 새 서명에 쓴 내용이 사라집니다.", 쓴 것이 있을 때의 [취소]는 "취소하면 새 서명에 쓴 내용이 사라집니다."("‘새 서명’ 서명에서"가 되지 않게).
  • 서버 거절: 저장이 400 VAL_001 이면 목록을 조용히 다시 받아 그사이 다른 창에서 같은 이름을 만들었으면 이름 칸의 빨간 글, 아니면 서명 칸 아래 사전 문구(본문 길이 · 그림 3장 등 — 지금과 같다). 지우려던 서명이 이미 없으면(404) 지운 것으로 본다. 고치던 서명이 다시 받은 목록에서 사라졌으면(다른 창에서 지움) 쓰던 것은 두고 새 서명으로 본다([저장]하면 새로 만든다). 한 사람이 보관하는 그림 20장을 넘는 올리기 거절(VAL_001)은 지금처럼 알림이다.
  • 쓰기 화면이 서명을 못 받았으면 서명 없이 열고 알리지 않는다(지금과 같다). [서명 ▾]를 열 때 다시 받아 본다.
  • 공용 편집기(SignatureEditor — 부재중 자동 응답의 내용 칸이 함께 쓴다): prop bodyId(편집 영역 id · 툴바 aria-controls) · toolbarLabel(툴바 이름) · allowImages(false 면 그림 단추 · 그림 노드가 없고 붙여 넣거나 끌어 놓은 파일은 받지 않는다) · invalid(aria-invalid · 빨간 테두리) · disabled(읽기만 · 툴바 끔 · 흐리게). 편집 영역 최소 높이는 CSS 변수 --signature-editor-min-height(기본 132), 보이는 글은 getText(). 기본값은 서명 칸 그대로다.

16단계 구현하며 정한 보완 — 부재중 · 휴대폰 바꾸기 (2026-11-10 — 캔버스에는 아직 없다)

  • 휴대폰을 바꿨는지 모를 때(서버가 503 MAIL_001 — 엔진이 새 비밀을 가려 다시 읽어도 가를 수 없다 · 시간 초과 · 연결 끊김 · 앞단 5xx 의 SYS_001): 바꿨다고도 못 바꿨다고도 하지 않는다(보드에 없는 새 글). 사전 문구 알림은 끄고 창은 ② 그대로 · 지금 휴대폰의 코드 칸만 비운 채 창 위 빨간 상자에 "휴대폰을 바꿨는지 확인하지 못했습니다. 지금 휴대폰에 다음 코드가 뜨면 넣고 [바꾸기]를 다시 눌러 주세요. 코드가 맞지 않다고 나오면 이미 바뀌었을 수 있으니, 다음에 로그인할 때 새 휴대폰의 코드를 넣어 주세요." — 이미 바뀌었으면 지금 휴대폰의 코드는 막혀 [바꾸기]가 AUTH_006 으로 거절하므로, 그 상자를 남겨 둔 채 칸 아래 글이 함께 보인다. 창을 닫은 뒤에 왔으면 알림 "휴대폰을 바꿨는지 확인하지 못했습니다. 다음에 로그인할 때 새 휴대폰의 코드가 맞지 않으면 지금 휴대폰의 코드를 넣어 주세요."
  • 닫은 창에 늦게 온 거절(지금 비밀번호 · 지금 코드)은 칸 대신 사전 문구 알림, 다시 연 창에는 넣지 않는다(켜기 · 끄기 창과 같다). ①의 늦은 확인 결과는 버린다(엔진을 바꾸지 않는다).
  • 켜기 창과 바꾸기 창이 한 화면에 함께 있어 키 이름표 id 가 겹치지 않게 QR · 키 부품(TwoFactorEnrollmentScan)이 머리 글 · 닫을 때의 안내 · 키 이름표 id 를 받는다.
  • 부재중 내용의 글자 수는 켜 둔 동안 600을 넘는 순간부터 빨갛게 보인다(보드는 [저장]을 누른 모습만 — 칸 아래 글은 지금처럼 [저장]을 눌렀을 때만). 꺼 둔 채로는 넘어도 회색이다(켤 때만 센다).
  • 부재중 양식은 편집기와 함께 지연 로딩한다 — 청크를 받는 동안은 칸이 비어 있고, 못 받으면 서명 설정과 같은 사전 문구 + [다시 시도].
  • 회사 밖 칸을 쓴 적 없으면 제목 · 내용을 null 로 보낸다(빈 글을 남기지 않는다 — 서버도 빈 글을 null 로 저장한다).