diff --git a/docs/design/webmail/MailFilterApply.dc.html b/docs/design/webmail/MailFilterApply.dc.html new file mode 100644 index 0000000..4b34442 --- /dev/null +++ b/docs/design/webmail/MailFilterApply.dc.html @@ -0,0 +1,561 @@ + + + + +설정 · 필터 · 이미 받은 메일에 적용 + + + + + + + +
+ + + +
+ +
+ 설정 +
+ + +
+
+ +
+ 김아라 + ara.kim@arasystem.it.kr +
+ + + 로그아웃 + +
+
+ +
+ + + +
+
+ +
+
+

필터

+

받은 메일을 조건에 따라 메일함으로 옮기거나, 읽음 · 중요로 표시하거나, 동료에게 전달합니다. 새로 오는 메일에 적용하고, 고를 때는 받은편지함에 이미 있는 메일에도 적용합니다.

+
+ +
+ +
+
    + +
  1. + {{ r.no }} + + +
    + + +
    +
  2. +
    +
+

필터 {{ count }}개 · 50개까지 만들 수 있습니다. 행을 누르면 고칩니다.

+
+ +
+

위에서부터 차례로 적용합니다. 메일함으로 옮기는 필터에 맞으면 그 아래 필터는 적용하지 않습니다 — 순서는 ▲ ▼ 로 바꿉니다.

+

이미 받은 메일에도 적용하려면 필터를 저장할 때 「받은편지함에 이미 있는 메일에도 적용」을 고릅니다. 받은편지함의 메일만 보고, 그 필터 하나만 적용합니다.

+

스팸으로 판정되어 들어오는 메일에는 필터를 적용하지 않습니다 — 그대로 스팸메일함으로 갑니다.

+

전달은 사내 주소로만, 새로 오는 메일만 합니다. 메일 한 통은 한 곳으로만 전달됩니다(위의 필터가 먼저). 전달해도 내 메일함에 그대로 남습니다.

+

부재중 자동 응답은 필터와 함께 동작합니다.

+
+ +
+
+
+
+ + +
+
+

{{ dialogTitle }}

+ +
+ + +
+ +
+ 어떤 메일에 + + +
    + +
  • + + + + +
  • +
    +
+
+ + 5개까지 · 글자는 대소문자를 가리지 않습니다 +
+
+ +

비어 있는 조건이 있습니다. 찾을 글을 적거나 그 조건을 지워 주세요.

+
+
+ +
+ 할 일 +
+ + +
+

옮기면 이 아래 필터는 적용하지 않습니다.

+ + +
+ + +
+

{{ fwdHelp }}

+ +

할 일을 하나 이상 골라 주세요.

+
+
+ +
+ +

{{ applyHelp }}

+
+

스팸으로 판정되어 들어오는 메일에는 적용하지 않습니다.

+ +
+ + + +
+ + +
+
+
+
+
+ + +
+
+

저장하지 않은 필터가 있습니다

+

창을 닫으면 고친 내용이 사라집니다.

+
+ + +
+
+
+
+ + +
+ + {{ runText }} · {{ runCount }} +
+
+ + +
+ + + + + + + {{ toastText }} + + {{ toastLinkLabel }} + + + + +
+
+
+
+ + + diff --git a/docs/design/webmail/MailFilterApplyStates.dc.html b/docs/design/webmail/MailFilterApplyStates.dc.html new file mode 100644 index 0000000..8416d80 --- /dev/null +++ b/docs/design/webmail/MailFilterApplyStates.dc.html @@ -0,0 +1,178 @@ + + + + +이미 받은 메일에 필터 적용 · 상태 + + + + + + + +
+ +
+

이미 받은 메일에 필터 적용

+

7단계에서 새로 생기는 것입니다. 「설정 · 필터 · 이미 받은 메일에 적용」 보드에서 필터를 열고 「받은편지함에 이미 있는 메일에도 적용」을 골라 저장해 볼 수 있습니다(오른쪽 위 Tweaks 에서 결과를 바꿉니다). 이 보드는 그 다른 상태와 함께 고치는 두 가지 — 고치기 창의 나가기 확인, 부재중 화면의 안내 — 를 모았습니다.

+
+ +
+ +
+
+

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

+

할 일 아래 · 창을 열 때마다 꺼져 있다 · 저장할 때 한 번 쓰고 기억하지 않는다(필터의 설정이 아니다)

+
+ +
+

기본

+
+ +

저장한 뒤 받은편지함에서 조건에 맞는 메일을 찾아 바로 적용합니다.

+
+

스팸으로 판정되어 들어오는 메일에는 적용하지 않습니다.

+
+ +
+

고름 — 전달도 하는 필터

+
+ +

저장한 뒤 받은편지함에서 조건에 맞는 메일을 찾아 바로 적용합니다. 전달은 새로 오는 메일에만 합니다.

+
+
+ +
+

고를 수 없음 — 전달만 하는 필터

+
+ +

전달만 하는 필터는 이미 받은 메일에 할 일이 없습니다 — 전달은 새로 오는 메일에만 합니다.

+
+
+ +
+

고를 수 없음 — 다른 필터를 적용하는 중

+
+ +

다른 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤에 고를 수 있습니다.

+
+
+
+ +
+
+

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

+

오른쪽 아래 · 끝날 때까지 남는다 · 그동안 다른 화면으로 가서 메일을 읽고 써도 된다(탭을 닫으면 멈춘다)

+
+
+ + ‘거래처 메일’ 필터를 받은편지함에 적용하는 중 · 4,200 / 8,412통 확인 +
+
+ + ‘거래처 메일’ 필터를 받은편지함의 메일 37통에 적용했습니다. + 거래처 보기 +
+
+ + ‘결재 알림’ 필터를 받은편지함의 메일 12통에 적용했습니다. +
+
+ + 받은편지함에 ‘거래처 메일’ 필터에 맞는 메일이 없습니다. +
+

숫자는 확인한 메일 / 받은편지함의 메일 수다. 받은편지함의 최근 메일부터 본다. 옮기는 필터의 결과에는 그 메일함으로 가는 단추를 둔다 — 읽음 · 중요만 하는 필터는 단추가 없다.

+
+ +
+ +
+ +
+
+

멈췄을 때

+

서버 · 연결 문제 — [다시 시도]는 남은 메일부터 이어서 본다

+
+
+ + ‘거래처 메일’ 필터 — 8,412통 가운데 4,200통까지 확인하고 멈췄습니다(12통에 적용). 다시 시도해 주세요. + +
+
+ + 필터는 저장했지만 받은편지함에 적용하지 못했습니다. 다시 시도해 주세요. + +
+
+ + 다른 창에서 필터를 받은편지함에 적용하는 중입니다. 끝난 뒤 다시 시도해 주세요. +
+

처음 요청부터 실패해도 필터는 저장되어 있다 — 새로 오는 메일에는 적용된다. 로그아웃 · 세션 만료 · 다른 계정 로그인이면 멈추고 알림을 닫는다.

+
+ +
+
+

저장하지 않고 닫을 때

+

고치기 창의 [취소] · Esc · 다른 화면으로 가기 — 창을 연 뒤 바꾼 것이 있을 때만 묻는다

+
+
+
+

저장하지 않은 필터가 있습니다

+

창을 닫으면 고친 내용이 사라집니다.

+
+ + +
+
+
+

처음 초점은 [계속 고치기] · Esc 도 같다(서명 · 부재중 설정과 같은 모양). 다른 화면으로 가려다 물으면 "이 화면을 나가면…" · [나가기]. 탭을 닫거나 새로 고치면 브라우저가 묻는다. 읽기 화면에서 채워 연 새 필터는 채운 그대로면 묻지 않는다.

+
+ +
+
+

부재중 자동 응답 — 메일 서버에서 규칙이 꺼졌을 때

+

자동 응답을 켜 두었는데 메일 서버의 규칙이 이 화면 밖에서 바뀐 드문 경우 — 머리 아래 줄 (필터 화면의 줄과 같은 뜻)

+
+
+ + 켜 둔 자동 응답이 지금은 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — 아래 [저장]을 누르면 다시 켜집니다. +
+

자동 응답을 꺼 두었으면 보이지 않는다. 저장하면 필터도 함께 다시 켜진다(한 규칙에 들어 있다). 지금까지는 이 화면이 이 상태를 몰라 "켜짐"으로만 보였다.

+
+ +
+
+
+ + + diff --git a/docs/design/webmail/README.md b/docs/design/webmail/README.md index 27c00f8..0028889 100644 --- a/docs/design/webmail/README.md +++ b/docs/design/webmail/README.md @@ -372,6 +372,43 @@ API 와 서버 규칙은 계약 2절 "실시간 알림". - 읽기 화면 ⋯ 메뉴에 「이런 메일로 필터 만들기」(원문 내려받기 아래) — 설정 › 필터의 새 필터 창이 보낸 사람의 **주소**로 채워져 열린다. - 켜기 · 끄기 · 순서 바꾸기는 알리지 않는다(목록이 바로 바뀐다). 저장 "필터를 저장했습니다.", 지우기 "‘거래처 메일’ 필터를 지웠습니다. [되돌리기]". +## 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`) 머리 아래 노란 줄(필터 화면의 줄과 같은 모양): + "켜 둔 자동 응답이 지금은 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — 아래 [저장]을 누르면 다시 켜집니다." +- 단추는 없다 — 저장 단추는 늘 누를 수 있다. 꺼 두었으면 보이지 않는다. 저장하면 필터도 함께 다시 켜진다. + ## 구현하며 정한 보완 (2026-10-26 — 캔버스에는 아직 없다) - 찾지 못한 첨부(보관 시간이 지난 업로드·지워진 원본)는 실패로 표시하고, 전달 원본에는 "빼기"만 둔다(다시 올릴 수 없다). @@ -531,4 +568,22 @@ API 와 서버 규칙은 계약 2절 "실시간 알림". - **그 밖**: [다시 켜기]가 성공해도 알림은 없다(줄이 사라진다). 목록 아래 줄은 보드 글 그대로 "… 행을 누르면 고칩니다."까지. 전달 칸은 `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 를 거듭 누르면 고치기 창을 막지 못하고 스스로 닫는다 — [계속 고치기]면 창을 다시 띄우고 초점은 닫히기 전에 있던 칸으로 돌려준다. + 닫기 확인이 떠 있는 동안 다른 화면으로 가려 하면(뒤로 가기) 같은 확인 창이 "이 화면을 나가면 …" [나가기]로 바뀐다. 저장 · 지우기를 보내는 중에는 묻지 않는다(결과는 알림으로). +- **부재중**: 메일함 패널 아래의 부재중 안내는 그대로 둔다(저장한 값으로 보인다 — 동작하지 않음은 설정 화면에서만 알린다). diff --git a/docs/design/webmail/canvas.json b/docs/design/webmail/canvas.json index 5273b6e..635ddcc 100644 --- a/docs/design/webmail/canvas.json +++ b/docs/design/webmail/canvas.json @@ -24,16 +24,19 @@ "SettingsNotify.dc.html": { "x": 0, "y": 8560, "w": 1440, "h": 900, "title": "설정 · 알림", "expand": "fill", "is_interactive": true }, "MailRealtime.dc.html": { "x": 1520, "y": 8560, "w": 1440, "h": 1520, "title": "실시간 새 메일 · 바탕화면 알림 상태" }, "SettingsFilter.dc.html": { "x": 0, "y": 10680, "w": 1440, "h": 900, "title": "설정 · 필터", "expand": "fill", "is_interactive": true }, - "MailFilterStates.dc.html": { "x": 1520, "y": 10680, "w": 1440, "h": 1420, "title": "메일 필터 상태 · 메일에서 바로 만들기" } + "MailFilterStates.dc.html": { "x": 1520, "y": 10680, "w": 1440, "h": 1420, "title": "메일 필터 상태 · 메일에서 바로 만들기" }, + "MailFilterApply.dc.html": { "x": 0, "y": 12700, "w": 1440, "h": 900, "title": "설정 · 필터 · 이미 받은 메일에 적용", "expand": "fill", "is_interactive": true }, + "MailFilterApplyStates.dc.html": { "x": 1520, "y": 12700, "w": 1440, "h": 1280, "title": "필터 적용 진행 · 결과 · 나가기 확인 · 부재중 안내" } }, - "order": ["Main.dc.html", "Login.dc.html", "Compose.dc.html", "States.dc.html", "MainList.dc.html", "MainListRead.dc.html", "ComposeEditor.dc.html", "Settings.dc.html", "ComposeAssist.dc.html", "Search.dc.html", "BulkSelect.dc.html", "ListStates.dc.html", "SettingsAccount.dc.html", "SettingsVacation.dc.html", "MailboxManage.dc.html", "MailActions4.dc.html", "SettingsNotify.dc.html", "MailRealtime.dc.html", "SettingsFilter.dc.html", "MailFilterStates.dc.html"], + "order": ["Main.dc.html", "Login.dc.html", "Compose.dc.html", "States.dc.html", "MainList.dc.html", "MainListRead.dc.html", "ComposeEditor.dc.html", "Settings.dc.html", "ComposeAssist.dc.html", "Search.dc.html", "BulkSelect.dc.html", "ListStates.dc.html", "SettingsAccount.dc.html", "SettingsVacation.dc.html", "MailboxManage.dc.html", "MailActions4.dc.html", "SettingsNotify.dc.html", "MailRealtime.dc.html", "SettingsFilter.dc.html", "MailFilterStates.dc.html", "MailFilterApply.dc.html", "MailFilterApplyStates.dc.html"], "notes": { "flow": { "x": 0, "y": -300, "text": "arasystem 웹메일 — 1단계 화면 (로그인 → 메일함 → 쓰기)", "kind": "title1", "maxW": 4480 }, "phase2": { "x": 0, "y": 2180, "text": "2단계 화면 — 임시 저장 · 받는 사람 자동 완성 · 서명 (2026-10-27 확정)", "kind": "title1", "maxW": 2960 }, "phase3": { "x": 0, "y": 4180, "text": "3단계 화면 — 새 메일 · 검색 · 여러 통 처리 (2026-10-27 확정)", "kind": "title1", "maxW": 4480 }, "phase4": { "x": 0, "y": 6340, "text": "4단계 화면 — 계정 · 메일 설정 (비밀번호 · 부재중 응답 · 메일함 관리 · 스팸 · 원문) — 2026-10-28 확정", "kind": "title1", "maxW": 6000 }, "phase5": { "x": 0, "y": 8260, "text": "5단계 화면 — 실시간 새 메일 · 바탕화면 알림 — 2026-10-28 확정", "kind": "title1", "maxW": 2960 }, - "phase6": { "x": 0, "y": 10380, "text": "6단계 화면 — 메일 필터 (설정 › 메일 › 필터 · 읽기 화면 ⋯ 「이런 메일로 필터 만들기」) — 2026-10-28 확정", "kind": "title1", "maxW": 2960 } + "phase6": { "x": 0, "y": 10380, "text": "6단계 화면 — 메일 필터 (설정 › 메일 › 필터 · 읽기 화면 ⋯ 「이런 메일로 필터 만들기」) — 2026-10-28 확정", "kind": "title1", "maxW": 2960 }, + "phase7": { "x": 0, "y": 12400, "text": "7단계 화면 — 이미 받은 메일에 필터 적용 · 필터 창 나가기 확인 · 부재중 안내 — 2026-10-29 확정", "kind": "title1", "maxW": 2960 } }, "designSystems": [] } diff --git a/docs/mail/api-contract.md b/docs/mail/api-contract.md index b0a1565..0fd1170 100644 --- a/docs/mail/api-contract.md +++ b/docs/mail/api-contract.md @@ -208,10 +208,11 @@ HTML 은 정화하기 **전에** 길이(HTML 부분이 여럿이면 이어 붙 | `DELETE /api/mailboxes/{id}` | — | 200 `MailboxDeletion` — 안의 메일을 휴지통으로 옮긴 뒤 지운다 | 400 `VAL_001`(기본 메일함 · 하위 메일함 있음), 404 `RES_001`, 429 `REQ_001`(같은 메일함의 처리가 진행 중), 503 `MAIL_001` | | `PATCH /api/mailboxes/{id}/messages` | `{ isRead: true }` — 그 메일함 전체를 읽음으로 | 200 `MailboxBulkResult` | 400 `VAL_001`, 404 `RES_001`, 429 `REQ_001`(같은 메일함의 처리가 진행 중), 503 `MAIL_001` | | `DELETE /api/mailboxes/{id}/messages` | — 휴지통 · 스팸메일함 비우기(영구 삭제) | 200 `MailboxBulkResult` | 400 `VAL_001`(휴지통 · 스팸메일함이 아님), 404 `RES_001`, 429 `REQ_001`(같은 메일함의 처리가 진행 중), 503 `MAIL_001` | -| `GET /api/mail-vacation` | — | 200 `MailVacation` (아래 "부재중 자동 응답") | 503 `MAIL_001` | -| `PUT /api/mail-vacation` | `MailVacation` (전체 교체) | 200 `MailVacation`(저장한 값) | 400 `VAL_001` · `MAIL_013`(필터와 함께 만든 엔진 규칙이 한도를 넘음 — 6단계), 503 `MAIL_001` | +| `GET /api/mail-vacation` | — | 200 `MailVacationState` (아래 "부재중 자동 응답" — `MailVacation` + 7단계의 `isActive`) | 503 `MAIL_001` | +| `PUT /api/mail-vacation` | `MailVacation` (전체 교체 — `isActive` 는 보내지 않는다) | 200 `MailVacationState`(저장한 값) | 400 `VAL_001` · `MAIL_013`(필터와 함께 만든 엔진 규칙이 한도를 넘음 — 6단계), 503 `MAIL_001` | | `GET /api/mail-filters` | — | 200 `MailFilterSet` (아래 "메일 필터", 6단계) | 503 `MAIL_001` | | `PUT /api/mail-filters` | `{ rules, version }` (전체 교체) | 200 `MailFilterSet` | 400 `VAL_001` · `MAIL_013`(만든 규칙이 엔진 한도를 넘음), 409 `MAIL_012`(그사이 다른 곳에서 필터가 바뀜 — 저장 안 함), 503 `MAIL_001` | +| `POST /api/mail-filter-runs` | `MailFilterRunRequest` — 받은편지함에 이미 있는 메일에 규칙 하나를 적용한다(아래 "이미 받은 메일에 적용", 7단계) | 200 `MailFilterRun` | 400 `VAL_001`(규칙 검사 · 할 일 없음 · 깨진 cursor), 429 `REQ_001`(같은 직원의 적용이 진행 중), 503 `MAIL_001` | | `GET /api/mail-quota` | — | 200 `MailQuota` | `MAIL_001` | | `GET /api/mail-state` | — | 200 `MailState` (아래 "새 메일 확인") | 503 `MAIL_001` | | `GET /api/mail-events` | — | `text/event-stream` (표준 래퍼 없음 — 아래 "실시간 알림", 5단계) | 401 `AUTH_001`, 429 `REQ_001`(한 직원의 연결 5개 초과), 503 `MAIL_001` | @@ -408,6 +409,10 @@ interface RejectedRecipient { - 메일 한 통의 첨부 합계(전달하는 원본 첨부 포함, 같은 첨부를 두 번 붙이면 두 번 센다) `attachmentTotalMaxBytes` (`MAIL_ATTACHMENT_TOTAL_MAX_SIZE_MB`, 기본 25MB) — 넘으면 발송이 413 `FILE_002`. 클라이언트는 첨부를 추가할 때 이 값으로 **미리 막고** 안내한다 (다 쓰고 보낼 때 알게 하지 않는다). +- **엔진의 임시 업로드 한도**(7단계에 찾음): 계정마다 1시간 동안 올린 크기 합계 · 개수(Stalwart `uploadQuota` · `maxUploadCount` · `uploadTtl`). 엔진 기본값은 50MB · 1,000개 — + 넘으면 올린 것이 만료될 때까지(1시간) 그 직원의 첨부 올리기와 필터 · 부재중 저장(엔진에 규칙 글을 올린다) · 서명 그림이 든 발송이 429 `REQ_001` 로 막힌다 + (2026-10-29 개발 엔진: 1,000개를 넘기자 필터 저장이 막혔고, 20MiB 두 개 뒤 세 번째 업로드가 429 — 크기 · 개수 어느 쪽이든 같은 `REQ_001`, 백엔드 로그 `"call":"upload"`). + 그래서 mail setup 이 크기 합계를 첨부 합계 한도의 20배(기본 25MB × 20 = 524,288,000바이트)로 둔다 — 개수(1,000)는 그대로(mail/README.md "엔진 한도"). - 허용 형식(확장자 + MIME 둘 다 검사): pdf · doc/docx · xls/xlsx · ppt/pptx · hwp/hwpx · txt · csv · png · jpg/jpeg · gif · webp · zip - 다운로드는 `` 이동으로 받는다 — 같은 출처라 쿠키가 함께 간다. 이동은 axios 를 거치지 않아 401 재시도 규칙이 적용되지 않으므로, **이동하기 전에 세션을 확인**한다 @@ -681,6 +686,10 @@ interface MailVacation { subject: string // 켤 때 150자까지(꺼 둔 채로는 200자까지 — 아래 "길이 한도의 근거") — 켤 때(isEnabled true)는 공백이 아닌 글자 1자 이상. 제어 문자(메일함 이름과 같은 범위 — 탭 · 줄바꿈 포함)는 쓰지 않는다(한 줄짜리 머리글) textBody: string // 켤 때 600자까지(꺼 둔 채로는 4,000자까지) — 켤 때는 공백이 아닌 글자 1자 이상. 글자만(서식 없음) — 제어 문자는 탭 · LF · CR 만. 서명은 붙지 않는다 } +interface MailVacationState extends MailVacation { // GET · PUT 의 응답 (7단계) + isActive: boolean // false = 엔진에서 우리 규칙(필터와 한 스크립트)이 꺼져 있어 켜 둔 자동 응답이 지금은 동작하지 않는다 — 저장하면 다시 켜진다. + // 메일 필터의 isActive 와 같은 값(한 번도 저장하지 않은 계정은 true). PUT 본문에는 넣지 않는다(넣으면 400 VAL_001 — 모르는 필드) +} ``` - **길이 한도의 근거 (6단계 앞 수정)**: 엔진이 UTF-8 **바이트**로 막는다 — 제목 511바이트(한글 170자 · 영문 511자), 본문 2,047바이트(한글 682자 · 영문 2,047자) @@ -696,8 +705,9 @@ interface MailVacation { 서식 본문은 없다(글자만). 저장은 전체 교체이고 응답은 저장한 값이다. 꺼도 쓴 제목 · 내용은 남겨 둔다. 필터와 한 스크립트라서 부재중을 저장해도 스크립트를 다시 켠다(필터가 꺼져 있었으면 함께 다시 동작한다). 필터 규칙은 건드리지 않는다. - 6단계부터 바뀐 동작: 엔진이 스팸으로 분류한 메일에는 답하지 않는다(필터와 같은 스팸 막기 — "메일 필터"의 "만드는 Sieve"). - - 우리 스크립트가 엔진에서 꺼져 있으면(필터의 `isActive: false`) 부재중도 동작하지 않는다. 이 API 의 `isEnabled` 는 저장한 값이라 그때도 `true` 로 온다 - (알려진 한계 — 드묾. 화면은 필터 화면의 줄로만 알린다). + - 우리 스크립트가 엔진에서 꺼져 있으면(`isActive: false`) 부재중도 동작하지 않는다 — `isEnabled` 는 저장한 값이라 그때도 `true` 다. + 7단계부터 응답의 `isActive` 로 알린다 — 화면은 켜 두었을 때 머리 아래 줄 "켜 둔 자동 응답이 지금은 동작하지 않습니다. 메일 서버의 규칙이 다른 곳에서 바뀌었습니다 — + 아래 [저장]을 누르면 다시 켜집니다."(단추 없음 — 저장 단추는 늘 누를 수 있다. 6단계의 알려진 한계를 고쳤다). - 저장해 둔 원본이 깨진 계정은 꺼짐(`isEnabled: false`, 빈 제목 · 내용)으로 온다. 원본이 이 서버보다 새 판이면 저장은 503 `MAIL_001` 이다("메일 필터"의 "저장 자리"). - `fromDate` · `toDate` 는 `Z` 로 끝나는 UTC 시각이고, 두 키는 늘 보낸다(값은 null 가능). 엔진에는 초 단위로 저장한다(소수 초는 버린다). 둘 다 있으면 버린 값으로 `fromDate < toDate`(아니면 400 `VAL_001`). 화면은 날짜만 고르고, 브라우저 시간대의 시작일 0시 · 끝나는 날 다음 날 0시를 UTC 로 보낸다. @@ -832,7 +842,7 @@ interface MailFilterActions { 전달은 한 통에 한 곳 — 엔진 한도(사용자 스크립트의 리다이렉트 1)라 두 번째 전달은 엔진이 조용히 버린다(2026-10-28 확인). 사본은 늘 남긴다(`redirect :copy`). **스팸으로 판정된 메일에는 아무 규칙도 적용하지 않는다**(부재중 응답도 하지 않는다) — 엔진은 규칙의 옮기기가 스팸메일함 분류를 이기므로(같은 날 확인) 막는다. 판정은 엔진의 분류 그대로다(지금 엔진 설정은 5점부터 스팸 — 아래 "만드는 Sieve"의 스팸 막기). - 꺼 둔 규칙은 목록에만 있고 적용하지 않는다. 이미 받은 메일에는 적용하지 않는다. + 꺼 둔 규칙은 목록에만 있고 적용하지 않는다. 이미 받은 메일에는 저절로 적용하지 않는다 — 저장할 때 고르면 아래 "이미 받은 메일에 적용"(7단계). - **저장 자리** — 엔진의 Sieve 스크립트 하나(`arasystem`)에 필터와 **부재중 자동 응답을 함께** 둔다. Stalwart 0.16.27 은 계정마다 켜진 스크립트가 하나뿐이고 엔진의 VacationResponse 도 스크립트(`vacation`)라서, 부재중을 켜면 필터가 꺼지고 필터를 켜면 부재중이 꺼진다(같은 날 확인). - 4단계 `/api/mail-vacation` 은 계약 그대로 두고 저장 자리만 이 스크립트로 옮긴다 — 엔진의 VacationResponse 는 더 쓰지 않는다. @@ -878,8 +888,86 @@ interface MailFilterActions { - 검사는 서버와 같은 규칙(이름 50 · 값 200 · 조건 5 · 규칙 50 · `${` · 제어 문자 · 할 일 하나 이상 · 전달은 사내 주소이고 자기 주소 아님) — 칸 아래에만 알린다. 전달 주소의 앞부분은 서버(`IsEmail`)보다 좁게 본다(RFC 5322 dot-atom · 64자) — 화면이 통과시킨 주소를 서버가 거절하지 않게. 규칙이 50개면 [새 필터] · 「이런 메일로 필터 만들기」를 끄고 까닭을 보인다. 지운 메일함을 가리키는 규칙은 목록에 빨간 안내. -- 이 단계에서 하지 않는 것: 이미 받은 메일에 적용, **바깥 주소로 전달**(엔진에 SRS 가 없어 Gmail 등이 DMARC 로 거절할 수 있고, 회사 메일이 개인 메일로 빠지는 통로다 — - 필요해지면 정책과 함께), 크기 · 첨부 · 날짜 조건, 정규식, 맞는 메일 미리 보기, mailcow(SOGo)의 필터 옮겨 오기(운영 전환 때 — imapsync 로는 옮겨지지 않는다). +- 이 단계에서 하지 않는 것: **바깥 주소로 전달** — 전달한 메일은 보낸 사람(From)이 원래의 바깥 도메인인데 우리 서버가 건넨다. 엔진은 봉투 보낸 사람을 + 필터 주인으로 바꿔 SPF 는 통과하지만(SRS 는 없다 — 거절되면 반송은 원래 보낸 사람이 아니라 필터 주인에게 온다), 받는 쪽의 DMARC 는 From 기준이라 + 원래 보낸 쪽의 DKIM 서명에 기댄다. 서명이 없거나 깨진 메일은 그 도메인이 엄격하면(p=reject) Gmail 등이 거절하거나 스팸으로 보낸다 — 전달 메일의 일반적인 한계라 + SRS 가 있어도 같다. 그리고 회사 메일이 개인 메일로 자동으로 빠지는 통로다 — **회사 정책으로 허용하지 않는다**(2026-10-29 결정 — 전달은 사내 주소로만). + 보통 발송(직원이 바깥 주소로 보내는 메일)과는 상관없다. + 크기 · 첨부 · 날짜 조건, 정규식, 맞는 메일 미리 보기, mailcow(SOGo)의 필터 옮겨 오기(운영 전환 때 — imapsync 로는 옮겨지지 않는다). + +### 이미 받은 메일에 적용 (`/api/mail-filter-runs`) — 7단계 + +필터를 저장할 때 고르면(고치기 창 「받은편지함에 이미 있는 메일에도 적용」) 받은편지함에 이미 있는 메일에도 그 규칙 하나를 적용한다. +엔진의 Sieve 는 메일을 받을 때만 돌기 때문에 서버가 받은편지함을 읽어 같은 규칙으로 판정하고 표시 · 옮기기를 한다. + +```ts +interface MailFilterRunRequest { + match: 'all' | 'any' | 'always' // 필터 규칙과 같은 검사(MailFilterRule 의 match · conditions) + conditions: MailFilterCondition[] // all · any 는 1~5개, always 는 빈 배열 + actions: { // 하나 이상 — 전달은 없다(이미 받은 메일은 전달하지 않는다) + moveToMailboxId: string | null // 필터 규칙과 같은 메일함만(보낸편지함 · 임시보관함은 400). 받은편지함이거나 없는 메일함이면 옮기지 않는다 + markAsRead: boolean + flag: boolean + } + cursor: string | null // 처음은 null, 이어 할 때는 앞 응답의 cursor 그대로(해석하지 않는다) +} +interface MailFilterRun { + scanned: number // 이번 요청에서 확인한 받은편지함 메일 수 + matched: number // 그중 조건에 맞아 할 일을 한 메일 수(이미 읽음 · 중요였던 메일도 센다) + remaining: number // 아직 확인하지 않은 메일 수 — 어림(그사이 들어오고 나간 메일이 있다) + cursor: string | null // null = 끝. 아니면 같은 본문에 이 cursor 를 넣어 다시 보낸다 +} +``` + +- 받은편지함만, 받은 시각이 최근인 메일부터 본다. 저장해 둔 필터를 읽지 않고 본문의 규칙을 쓴다(클라이언트는 방금 저장한 그 규칙을 보낸다) — + 그래서 적용하는 동안 다른 필터를 켜고 끄거나 저장해도 멈추지 않는다. +- 한 요청은 25초 안에 끝내고 `cursor` 를 돌려준다 — 4단계 "오래 걸리는 처리"와 같은 상한 · 같은 규칙(다음 차례는 남은 시간이 5초보다 많을 때만, 엔진 호출마다 남은 시간만큼, + 진행한 것이 있으면 거기까지 200, 하나도 확인하지 못했으면 503 `MAIL_001`). 같은 직원의 적용은 한 번에 하나 — 진행 중이면 엔진에 묻지 않고 429 `REQ_001`(다른 탭 · PC). +- **판정은 서버가 한다** — 새로 오는 메일의 Sieve(위 "메일 필터"의 "만드는 Sieve")와 같은 규칙을 따라 한다. 엔진 검색(`Email/query` 의 글자 조건)은 쓰지 않는다 — + 낱말이 통째로 같을 때만 맞고('회의'로 '회의를' · '아라'로 '김아라'를 못 찾는다) `header` 조건은 아무것도 찾지 못한다(Stalwart 0.16.27, 2026-10-28 확인). + - 보낸 사람: From 머리글의 원래 글 — 접힌 줄(줄바꿈 + 공백 · 탭)은 띄어쓰기 하나로 펴고(접지 않은 탭 · 이어진 공백은 그대로), 인코딩된 글(RFC 2047 — 문자셋 그대로)은 + **홀로 선 것만** 풀고(앞이 처음 · 공백 · `"` · `,`, 뒤가 끝 · 공백 · `"` — `abc=?…?=` · `=?…?=` · `(=?…?=)` 는 그대로. 공백만 사이에 둔 인코딩된 글끼리는 붙인다), + 앞뒤 공백을 뺀 것 — 따옴표 · 꺾쇠까지 견준다(`"Kim, Ara" ` 에 `"Kim` 이 맞는다 — 엔진과 같다). 받는 사람: To · Cc 머리글 각각(하나라도 맞으면). 제목: 풀어 쓴 제목. + - 본문: 텍스트 · HTML 부분과 **텍스트 첨부**(text/* — 파일로 붙인 것도. 엔진이 그렇게 본다). 한 부분은 앞 256KiB 까지, 한 통은 모두 합쳐 1MiB 까지 본다 + (글자 부분이 많아 넘으면 부분마다 같은 몫만큼 앞쪽만 — 바깥에서 온 메일 한 통으로 서버가 메모리를 크게 쓰지 않게). HTML 은 글자 부분만 — 엔진과 같게: + - 태그는 `<` 부터 다음 `>` 까지다 — `<` 뒤가 무엇이든 태그이고(`a < b` · `<광고>` 도), 따옴표 안의 `>` 에서도 끝난다. 닫히지 않은 `<` 는 끝까지 삼킨다. + 태그는 속성까지 **빈자리 없이** 뺀다(`회의록` → "회의록", `div` · `li` · `h1` · 표 칸 · 표 줄 · `hr` · 여는 `p` 사이도 이어 붙는다). 주석(``)은 안의 글까지 뺀다 + - `

`(닫는 것만) · `
`(`
` · `
` · `
` · 대문자도)은 줄바꿈이다 — 찾을 글(줄바꿈이 없다)은 이 자리를 건너 맞지 않는다(`

앞

뒤

` 에 "앞뒤" · "앞 뒤" 모두 안 맞음) + - `