feat: 메일 15단계 — 읽기 화면 이동 · 끌어다 놓기 · 메일함 옮기기 · 차단할 때 받은편지함 정리 · 예약 메일 지금 보내기 #23

Merged
ttipo merged 6 commits from feat/mail-handling into main 2026-11-09 22:35:44 +09:00
Owner

무엇을 · 왜

메일 15단계(메일 · 메일함 다루기)입니다. 옛 16단계를 당긴 것으로, 옛 15단계(수신 확인 · 사내 발송 취소)는 계획에서 빼 보류로 옮겼습니다(2026-11-09).
화면은 캔버스 보드로 먼저 확정했습니다(v71 — Reader15 · ListDrag15 · Mailbox15).

  • 읽기 화면 [이동 ▾]: 옮긴 뒤 다음 메일 · [되돌리기]. 도구 줄은 읽기 칸 폭(컨테이너 쿼리)으로 아이콘만 남겨 분할 보기 1144~1233px 에서 두 줄로 꺾이지 않게 했습니다.
  • [이동 ▾] 메뉴: 메일함 들여쓰기, 그 메일이 든 메일함에 하위가 있으면 흐린 줄, 화면 밖으로 넘치면 좌우 바꾸기(같은 부품의 메뉴 모두), 같은 이름이 다른 곳에도 있을 때만 알림에 "상위 › 이름".
  • 끌어다 놓기: 목록 → 메일함 패널(고른 것 · 대화 줄, 휴지통에 놓으면 삭제). 메일 화면에 놓은 파일은 무시합니다(지금은 브라우저가 그 파일을 열어 웹메일을 떠났습니다).
  • 스팸메일함으로 옮기는 세 길 같게: [이동 ▾] · 끌기로 넣어도 스팸 표시를 붙이고, 꺼내면 스팸 아님 표시.
  • 메일함 옮기기(PATCH /api/mailboxes/{id} 의 parentId) · 하위 메일함까지 지우기(DELETE /api/mailboxes/{id}?withChildren=true).
  • 차단할 때 받은편지함 정리(POST /api/mail-block-runs): 차단 규칙(Sieve)과 같은 판정으로 받은편지함의 그 보낸 사람 메일을 스팸메일함으로 옮깁니다(7단계 필터 적용과 같은 cursor · 잠금).
    엔진 검색은 비슷한 주소 · 표시 이름에 쓴 주소까지 잡아서 쓰지 않습니다 — 남의 메일이 옮겨지지 않습니다.
  • 예약 메일 [지금 보내기](POST /api/scheduled-messages/{id}/release): 예약 제출을 취소하고 원래 envelope 으로 다시 제출합니다(보내기 취소 설정만큼 붙잡음).
    결과를 모르는 실패는 다시 내지 않고 503 — 같은 요청이 이어 냅니다(두 통이 나가지 않게). 화면은 503 뒤 그 메일을 다시 받아 가르고, 확인하지 못한 일은 단정하지 않습니다.
  • 상단 바 좁은 폭: [로그아웃]이 꺾이지 않게. 같은 대화: [모두 펼치기](화면에 들어오는 줄부터 본문) · 펼친 줄 머리 [답장].
  • 저장소 소개 정리(마지막 커밋): 루트 README · CLAUDE.md · 점검 목록의 "풀스택 템플릿" 소개를 지금 구성(ARA SYSTEM)으로 바꾸고 docs/roadmap.md 를 더했습니다(16단계 첫 항목: 주소록 가져오기 · 내보내기).
    backend · frontend README 는 프레임워크가 만든 영어 안내 대신 이 저장소의 실행 · 검사 안내로 바꿨습니다.

엔진 실측(개발 Stalwart 0.16.27): parentId 바꾸기 · 고리 거절 · 같은 이름(대소문자 무시) 거절, destroy 는 적은 차례대로, from 검색은 넓게 잡힘, 예약 취소 뒤 다시 제출하면 받는 사람에게 한 통.

리뷰는 세 번 받았습니다. 1차 중간 2 · 낮음 3 · 참고 2 → 재리뷰 중간 1 · 낮음 2 → 마지막 확인 낮음 1 — 모두 고쳤습니다.

확인 방법

  • backend(컨테이너): lint 0, 단위 3,828, e2e 356 통과(15단계 12개 — 새 경로 넷의 401 · 400 · 404 · 429 · 성공 꼴), build 통과
  • 실엔진(개발 엔진 · 개발 계정): 옮기기 · 하위까지 지우기 · 차단 정리(주소 · 도메인 — 비슷한 주소 · 표시 이름 · Reply-To 의 메일은 남음) · 지금 보내기(받는 사람에게 한 통, 다시 불러도 200).
    시험 메일함은 지우고 시험 메일은 휴지통으로 옮겼습니다.
  • frontend: lint 0, type-check, test:unit 2,951(KST — 바꾼 묶음은 UTC 로도)
  • 아직 못 한 것: 브라우저로 직접 띄운 확인(끌어다 놓기의 패널 끝 자동 스크롤 · 끄는 표 모양 · 목록 보기 도구 줄이 접히는 폭 850 / 875 어림) — 이 PR 의 CI 와 함께 실사용 시험으로 봅니다.

영향 범위

  • backend
  • frontend
  • flutter
  • docker / 인프라
  • 규칙 문서(.claude/)

공유 계약: 새 경로 둘(POST /api/mail-block-runs · POST /api/scheduled-messages/{id}/release), 넓힌 경로 둘(PATCH /api/mailboxes/{id} 의 parentId · DELETE /api/mailboxes/{id}?withChildren=true).
새 에러 코드는 없습니다. DELETE /api/mailboxes/{id} 는 모르는 쿼리를 이제 400 VAL_001 로 막습니다. 앱은 이 경로들을 쓰지 않고, 앱 첫 판이 읽는 응답 칸은 바뀌지 않았습니다.

체크리스트

  • 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과
  • 주석 한국어, 매직 넘버·하드코딩 문자열 없음
  • 사용자 노출 실패는 모두 에러 코드와 함께 처리 (BusinessException / error.code 분기)
  • 에러 코드를 바꿨다면 npm run sync:error-codes 실행 후 생성 파일까지 커밋 — 바꾸지 않음
  • 민감정보 미커밋 (*.example 값은 placeholder)
  • 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음 — 주소는 가림(maskEmail), 메일 글 · 엔진 설명 글 없음

🤖 Generated with Claude Code

## 무엇을 · 왜 메일 15단계(메일 · 메일함 다루기)입니다. 옛 16단계를 당긴 것으로, 옛 15단계(수신 확인 · 사내 발송 취소)는 계획에서 빼 보류로 옮겼습니다(2026-11-09). 화면은 캔버스 보드로 먼저 확정했습니다(v71 — `Reader15` · `ListDrag15` · `Mailbox15`). - **읽기 화면 [이동 ▾]**: 옮긴 뒤 다음 메일 · [되돌리기]. 도구 줄은 읽기 칸 폭(컨테이너 쿼리)으로 아이콘만 남겨 분할 보기 1144~1233px 에서 두 줄로 꺾이지 않게 했습니다. - **[이동 ▾] 메뉴**: 메일함 들여쓰기, 그 메일이 든 메일함에 하위가 있으면 흐린 줄, 화면 밖으로 넘치면 좌우 바꾸기(같은 부품의 메뉴 모두), 같은 이름이 다른 곳에도 있을 때만 알림에 "상위 › 이름". - **끌어다 놓기**: 목록 → 메일함 패널(고른 것 · 대화 줄, 휴지통에 놓으면 삭제). 메일 화면에 놓은 파일은 무시합니다(지금은 브라우저가 그 파일을 열어 웹메일을 떠났습니다). - **스팸메일함으로 옮기는 세 길 같게**: [이동 ▾] · 끌기로 넣어도 스팸 표시를 붙이고, 꺼내면 스팸 아님 표시. - **메일함 옮기기**(`PATCH /api/mailboxes/{id}` 의 `parentId`) · **하위 메일함까지 지우기**(`DELETE /api/mailboxes/{id}?withChildren=true`). - **차단할 때 받은편지함 정리**(`POST /api/mail-block-runs`): 차단 규칙(Sieve)과 같은 판정으로 받은편지함의 그 보낸 사람 메일을 스팸메일함으로 옮깁니다(7단계 필터 적용과 같은 cursor · 잠금). 엔진 검색은 비슷한 주소 · 표시 이름에 쓴 주소까지 잡아서 쓰지 않습니다 — 남의 메일이 옮겨지지 않습니다. - **예약 메일 [지금 보내기]**(`POST /api/scheduled-messages/{id}/release`): 예약 제출을 취소하고 원래 envelope 으로 다시 제출합니다(보내기 취소 설정만큼 붙잡음). 결과를 모르는 실패는 다시 내지 않고 503 — 같은 요청이 이어 냅니다(두 통이 나가지 않게). 화면은 503 뒤 그 메일을 다시 받아 가르고, 확인하지 못한 일은 단정하지 않습니다. - **상단 바 좁은 폭**: [로그아웃]이 꺾이지 않게. **같은 대화**: [모두 펼치기](화면에 들어오는 줄부터 본문) · 펼친 줄 머리 [답장]. - **저장소 소개 정리**(마지막 커밋): 루트 README · CLAUDE.md · 점검 목록의 "풀스택 템플릿" 소개를 지금 구성(ARA SYSTEM)으로 바꾸고 `docs/roadmap.md` 를 더했습니다(16단계 첫 항목: 주소록 가져오기 · 내보내기). backend · frontend README 는 프레임워크가 만든 영어 안내 대신 이 저장소의 실행 · 검사 안내로 바꿨습니다. 엔진 실측(개발 Stalwart 0.16.27): `parentId` 바꾸기 · 고리 거절 · 같은 이름(대소문자 무시) 거절, `destroy` 는 적은 차례대로, `from` 검색은 넓게 잡힘, 예약 취소 뒤 다시 제출하면 받는 사람에게 한 통. 리뷰는 세 번 받았습니다. 1차 중간 2 · 낮음 3 · 참고 2 → 재리뷰 중간 1 · 낮음 2 → 마지막 확인 낮음 1 — 모두 고쳤습니다. ## 확인 방법 - backend(컨테이너): lint 0, 단위 3,828, e2e 356 통과(15단계 12개 — 새 경로 넷의 401 · 400 · 404 · 429 · 성공 꼴), build 통과 - 실엔진(개발 엔진 · 개발 계정): 옮기기 · 하위까지 지우기 · 차단 정리(주소 · 도메인 — 비슷한 주소 · 표시 이름 · Reply-To 의 메일은 남음) · 지금 보내기(받는 사람에게 한 통, 다시 불러도 200). 시험 메일함은 지우고 시험 메일은 휴지통으로 옮겼습니다. - frontend: lint 0, type-check, test:unit 2,951(KST — 바꾼 묶음은 UTC 로도) - **아직 못 한 것**: 브라우저로 직접 띄운 확인(끌어다 놓기의 패널 끝 자동 스크롤 · 끄는 표 모양 · 목록 보기 도구 줄이 접히는 폭 850 / 875 어림) — 이 PR 의 CI 와 함께 실사용 시험으로 봅니다. ## 영향 범위 - [x] backend - [x] frontend - [ ] flutter - [ ] docker / 인프라 - [x] 규칙 문서(.claude/) > **공유 계약**: 새 경로 둘(`POST /api/mail-block-runs` · `POST /api/scheduled-messages/{id}/release`), 넓힌 경로 둘(`PATCH /api/mailboxes/{id}` 의 `parentId` · `DELETE /api/mailboxes/{id}?withChildren=true`). > 새 에러 코드는 없습니다. `DELETE /api/mailboxes/{id}` 는 모르는 쿼리를 이제 400 `VAL_001` 로 막습니다. 앱은 이 경로들을 쓰지 않고, 앱 첫 판이 읽는 응답 칸은 바뀌지 않았습니다. ## 체크리스트 - [x] 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과 - [x] 주석 한국어, 매직 넘버·하드코딩 문자열 없음 - [x] 사용자 노출 실패는 모두 에러 코드와 함께 처리 (`BusinessException` / `error.code` 분기) - [x] 에러 코드를 바꿨다면 `npm run sync:error-codes` 실행 후 생성 파일까지 커밋 — 바꾸지 않음 - [x] 민감정보 미커밋 (`*.example` 값은 placeholder) - [x] 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음 — 주소는 가림(`maskEmail`), 메일 글 · 엔진 설명 글 없음 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ttipo added 5 commits 2026-11-09 20:31:40 +09:00
읽기 화면 [이동 ▾] · 예약 메일 지금 보내기 · 같은 대화 모두 펼치기(Reader15),
목록 끌어다 놓기 · 좁은 폭 상단 바(ListDrag15), 메일함 옮기기 · 하위까지 지우기 ·
차단할 때 받은편지함 정리(Mailbox15). 캔버스 v71 에서 확정 — 글은 화면 상수가 기준이다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PATCH /api/mailboxes/{id} 에 parentId — 맨 위(null) · 자기 하위로는 VAL_001 · 새 상위에 같은 이름(대소문자 무시)은 MAIL_009 · 역할 메일함 막기
- DELETE /api/mailboxes/{id}?withChildren=true — 나무를 깊은 것부터 지금 지우기와 같은 차례로(휴지통으로 → destroy)
- POST /api/mail-block-runs — 받은편지함에서 차단한 보낸 사람의 메일을 스팸메일함으로(Sieve 차단 규칙과 같은 판정,
  필터 적용과 같은 cursor · 잠금, 도는 동안 스팸메일함 잠금)
- POST /api/scheduled-messages/{id}/release — 예약 제출을 취소하고 원래 envelope 으로 다시 제출(보내기 취소 설정만큼 붙잡음).
  결과를 모르는 실패면 다시 내지 않고 503 — 같은 요청이 이어 낸다(두 통이 나가지 않게), 엔진이 분명히 거절했을 때만 임시보관함으로

엔진 사실은 개발 엔진(Stalwart 0.16.27)으로 확인했다: parentId 바꾸기 · 고리 거절, destroy 는 적은 차례대로,
from 검색은 비슷한 주소 · 표시 이름의 주소까지 넓게 잡힘, 취소 뒤 다시 제출하면 받는 사람에게 한 통.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 읽기 화면 [이동 ▾](옮긴 뒤 다음 메일 · [되돌리기]), 도구 줄은 읽기 칸 폭(컨테이너 쿼리)으로 아이콘만
- [이동 ▾] 메뉴 들여쓰기 · 그 메일이 든 메일함에 하위가 있으면 흐린 줄 · 넘치면 좌우 바꾸기(EditorPopup 메뉴 모두),
  같은 이름이 다른 곳에도 있을 때만 알림에 "상위 › 이름"
- 목록 → 메일함 패널 끌어다 놓기(고른 것 · 대화 줄, 휴지통은 삭제), 메일 화면에 놓은 파일은 무시
- 스팸메일함으로 옮기는 세 길 같게 — 메뉴 · 끌기도 스팸 표시를 붙이고, 꺼내면 스팸 아님
- 메일함 ⋯ 옮기기 창(같은 이름이 있는 곳은 처음부터 막음) · 하위까지 지우기 확인 창
- 차단 창에 받은편지함 정리 칸(같은 탭의 필터 적용 · 스팸메일함 비우기와 함께 돌지 않음), 진행 알림 하나
- 예약 안내 줄 [지금 보내기] — 결과를 모르는 실패는 그 메일을 다시 받아 가르고 확인하지 못한 일은 단정하지 않는다
- 상단 바 좁은 폭에서 [로그아웃]이 꺾이지 않게, 같은 대화 [모두 펼치기] · 펼친 줄 머리 [답장]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
계약 2절에 메일함 옮기기 · 하위까지 지우기 · 받은편지함 정리 · 지금 보내기(결과를 모르는 실패와 알려진 한계까지),
디자인 README 에 15단계 절과 구현하며 정한 보완, nestjs · vue3 스킬 표에 새 파일과 잠금(여러 대로 늘릴 때 옮길 것).
14단계 "미룬 것"에서 15단계에 한 것(대화째 끌기 · 모두 펼치기 · 줄 머리 답장)을 뺐다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: 저장소 소개를 ARA SYSTEM 의 지금 구성에 맞게 고치고 로드맵을 더한다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m49s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m45s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m55s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m12s
3c9b3727c6
루트 README · CLAUDE.md · 점검 목록이 아직 "풀스택 모노레포 템플릿"으로 소개하고 있었다 — 사내 그룹웨어(메일 운영 중)에 맞게
지금 상태 · 구성(메일 엔진 · 리졸버 · ClamAV · 운영 스크립트 포함) · 문서 지도 · 디렉터리 구조로 다시 쓰고,
백엔드를 호스트에서 npm ci 하라던 옛 안내(컨테이너 안에서만 — 규칙과 반대)를 뺐다.
docs/roadmap.md 에 메일 1~16단계 상태(16단계 첫 항목: 주소록 가져오기 · 내보내기) · 공통 기반 · 2부 · 앱 · 추후 후보 모음을 둔다.
backend · frontend README 는 프레임워크가 만든 영어 안내 대신 이 저장소의 실행 · 검사 · 참고 문서로,
규칙 · 스킬 · 역할 문서의 "템플릿" 표현("템플릿 기본 제공 골격" 등)은 "기본 골격"으로 바꿨다(nginx 설정 템플릿은 그대로).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttipo added 1 commit 2026-11-09 21:47:06 +09:00
fix(frontend): 15단계 실사용 시험에서 나온 다섯 가지를 고친다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m24s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m55s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m1s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m49s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m34s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m31s
CI / Frontend (lint · type-check · test · build) (push) Successful in 12m52s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 11m15s
071fe9a60f
- 폭 360 에서 검색 칸이 상단 바 밖으로 넘쳐 화면이 옆으로 밀렸다(14단계부터) — 메일 검색 칸 · 비활성 통합 검색 칸의 최소 폭을 0 으로 둔다
- 읽기 칸 도구 줄이 이름을 숨기는 폭을 570 · 595 에서 580 · 605 로 올린다 — 실측한 필요 폭(569.19 · 594.83)에 여유가 1px 도 없어 글자 · 여백이 조금만 늘어도 두 줄로 꺾였다
- 잘못 놓은 파일 무시를 메일 화면(MailLayout)에서 앱 틀(AppShell)로 옮긴다 — 주소록 · 설정에 놓은 파일은 브라우저가 열어 웹메일을 떠났다. 쓰기 화면 · 서명 설정은 지금처럼 받는다
- 받은편지함 정리를 켠 차단 뒤에는 다음 메일 대신 목록으로 간다 — 다음 메일이 같은 보낸 사람의 것이면 열자마자 읽음이 되고 곧 정리가 스팸메일함으로 옮겼다
- 모달 창의 제목 · 글은 낱말 가운데서 줄을 바꾸지 않고("지울까/요?") 띄어쓰기 없는 긴 글만 아무 데서나 끊는다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttipo merged commit 071fe9a60f into main 2026-11-09 22:35:44 +09:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ttipo/arasystem#23