generated from ttipo/project-base
fix(docker): mail setup — 받는 사람 단위 받는 쪽 한도를 인증 없는 세션에만
- 엔진 기본 "Sender address to recipient throttle"(같은 보낸 도메인 → 한 받는 사람, 시간당 25)의 match 를 is_empty(authenticated_as) 로 — 사내 메일이 동료 모두 합쳐 한 사람에게 25통을 넘으면 웹메일 발송이 452 로 거절되던 문제. 바깥에서 들어오는 메일에는 그대로 - "Sender IP throttle"(초당 연결 5개)은 그대로 — 연결 단계 한도라 웹메일 발송에는 걸리지 않고, 조건을 붙이면 MAIL FROM 단계로 옮겨진다 - mail/README.md "엔진 한도" — 기본값 · 바꾸는 까닭 · 확인 결과 · 운영 전에 정할 것 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+52
-3
@@ -103,12 +103,14 @@ node --env-file=.env.development mail/scripts/setup.mjs
|
||||
5. **게이트웨이** — 대리 접근 전용 역할 + 서비스 계정. 비밀번호는 env 값, 자격 증명의 허용 IP 는 신뢰 대역만
|
||||
6. **잠금** — 직원(`User`) 역할에서 Principal 조회 권한 13개(`jmapPrincipal*`·`davPrincipal*`)를 뺀다(→ `InvalidateCaches`).
|
||||
공개 443(https 리스너)의 HTTP 는 `/.well-known/mta-sts.txt` 만 열고 나머지는 403 (`x:Http` 의 `allowedEndpoints`) — [보안](#보안)
|
||||
7. **설정 리로드 → 반영 확인** — 메일 컨테이너의 네트워크 주소로 `https://…:443/jmap/session` 이 403, 내부 8080 이 200 인지 본다.
|
||||
7. **엔진 한도** — 받는 사람 단위 받는 쪽 한도(같은 보낸 도메인 → 한 받는 사람, 기본 시간당 25)를 인증 없는 세션에만 건다 —
|
||||
로그인한 직원의 발송(웹메일)에는 걸지 않고 바깥에서 들어오는 메일에는 그대로. 설명·key 로 찾고, 없으면 만들지 않고 `[주의]` ([엔진 한도](#엔진-한도))
|
||||
8. **설정 리로드 → 반영 확인** — 메일 컨테이너의 네트워크 주소로 `https://…:443/jmap/session` 이 403, 내부 8080 이 200 인지 본다.
|
||||
운영은 바깥에서 쓰는 주소 그대로(`MAIL_PUBLIC_URL/jmap/session`, 이 스크립트를 돌리는 호스트에서)도 403 인지 본다 —
|
||||
200 이면 실패(앞단 프록시가 8080 으로 넘기는 구성), 연결 자체가 안 되면 경고와 함께 바깥에서 확인할 `curl` 명령을 보여 준다.
|
||||
게이트웨이로 관리자를 대리 접근해 관리 API 가 열리는지도 데이터 없는 호출로 본다 — 열리면 매번 `[주의]` ([보안](#보안))
|
||||
8. **[개발] 외부 발송 경로** — Relay `mailpit:1025` + 발송 전략(사내 도메인은 로컬, 나머지는 mailpit) — 7 의 리로드로 반영된 것을 확인한 뒤에만 9
|
||||
9. **[개발] 테스트 직원 계정** → 게이트웨이 대리 접근, 직원의 계정 목록 조회 차단(`Principal/get` → forbidden)까지 자체 확인
|
||||
9. **[개발] 외부 발송 경로** — Relay `mailpit:1025` + 발송 전략(사내 도메인은 로컬, 나머지는 mailpit) — 8 의 리로드로 반영된 것을 확인한 뒤에만 10
|
||||
10. **[개발] 테스트 직원 계정** → 게이트웨이 대리 접근, 직원의 계정 목록 조회 차단(`Principal/get` → forbidden)까지 자체 확인
|
||||
|
||||
그 밖의 안전장치:
|
||||
|
||||
@@ -157,6 +159,7 @@ node --env-file=.env.development mail/scripts/setup.mjs
|
||||
- 아웃바운드 HTTPS — Stalwart 는 기동 때 GitHub 에서 웹 포털을 내려받고, ACME(Let's Encrypt)로 인증서를 받는다
|
||||
- **메일 서버 전용 재귀 DNS** — 공용·ISP DNS(KT·Google·Cloudflare 등)로는 Spamhaus 조회가 막혀 스팸 판정이 틀어진다 ([DNS 리졸버와 스팸 차단 목록](#dns-리졸버와-스팸-차단-목록-dnsbl))
|
||||
- **스팸 차단 목록 이용 조건** — Spamhaus 는 회사용 무료 DQS 키를 쓰거나 해당 항목을 끈다. 나머지 목록도 조건 확인 (같은 절)
|
||||
- **엔진 한도** — 받는 쪽 한도 값과 직원마다 내보내는 양의 한도를 정한다 ([엔진 한도](#엔진-한도))
|
||||
- **포털(웹메일) 앞의 외부 리버스 프록시** — 요구사항은 [docker 스킬](../.claude/skills/docker/SKILL.md) "운영 환경" (같은 주소 배포 · `X-Forwarded-For` · IPv6 · 본문 한도·시간 제한)
|
||||
- **`APP_NETWORK_SUBNET`·`EDGE_NETWORK_SUBNET`** 이 그 호스트의 다른 도커 네트워크와 겹치지 않는다 (겹치면 `up` 이 "Pool overlaps" 로 실패한다)
|
||||
- **rootful 도커 + iptables** (도커 기본값). 게시 포트(25·465·993·443)로 들어온 접속이 원래 주소 그대로 컨테이너에 보여야 한다 —
|
||||
@@ -303,6 +306,50 @@ sudo docker exec "${PROJECT_NAME}-backend" nslookup 2.0.0.127.zen.spamhaus.org <
|
||||
[Spamhaus — KT 경유 조회 안내](https://submit.spamhaus.org/resources/query-the-legacy-dnsbls-via-korea-telecom/),
|
||||
[Stalwart DNSBL 문서](https://www.stalw.art/docs/spamfilter/dnsbl)
|
||||
|
||||
### 엔진 한도
|
||||
|
||||
받는 쪽 한도(`x:MtaInboundThrottle` — 관리 화면 Settings › MTA › Rates & Quotas › Inbound Rate Limits)는 엔진을 처음 설치하면 아래 두 개다.
|
||||
한도를 넘은 바깥 메일 서버는 거절이 아니라 미루기(`452 4.4.5 Rate limit exceeded` 또는 연결 끊김)를 받고 나중에 다시 보낸다 — 늦게 받을 뿐 잃지는 않는다.
|
||||
|
||||
| 한도 (엔진의 설명 · key) | 세는 것 | 기본값 | 넘으면 | setup |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `Sender address to recipient throttle` · `senderDomain`·`rcpt` | 같은 보낸 도메인 → 같은 받는 사람마다 받는 사람 수 (RCPT TO 때) | 시간당 25 | 그 받는 사람만 `452 4.4.5` | **인증 없는 세션에만** 건다 |
|
||||
| `Sender IP throttle` · `remoteIp` | 접속 IP 마다 연결 수 (연결할 때) | 초당 5 | 인사 없이 연결을 끊는다 | 그대로 둔다 |
|
||||
|
||||
- **바꾸는 까닭** — 기본 조건(`match`)은 모든 세션(`{"else":"true"}`)이라 로그인한 직원의 발송에도 걸린다 (엔진 문서 [Rate limits](https://stalw.art/docs/mta/inbound/rate-limit/) "Default throttles").
|
||||
사내 메일은 보낸 도메인이 모두 같아서, 동료 모두 합쳐 한 사람에게 시간당 25통을 넘으면 그 뒤의 웹메일 발송(JMAP 제출)은 그 받는 사람이 `452 4.4.5` 로 거절됐다
|
||||
(2026-10-28 개발 엔진 — 로그 `smtp.rate-limit-exceeded … id = "MtaInboundThrottle with id …"`). 바깥 서버와 달리 **엔진은 그 메일을 다시 보내지도 반송하지도 않는다** —
|
||||
제출 기록의 `deliveryStatus` 에만 남는다 (백엔드가 읽어 알린다 — [계약](../docs/mail/api-contract.md) 2절 "받는 사람별 결과").
|
||||
- **setup 이 하는 일** — 설명과 key 로 찾아(id 는 설치마다 다르다) `match` 를 `{"match":{},"else":"is_empty(authenticated_as)"}` 로 맞추고 설정 리로드로 반영한다.
|
||||
엔진 기본값이 스팸 필터를 인증 없는 세션에만 거는 식(`x:MtaStageData` 의 `enableSpamFilter`)과 같다.
|
||||
- `authenticated_as` 는 인증한 계정 이름이고, 인증 없는 세션(AUTH 없이 들어온 SMTP — 바깥 서버가 25 로 넣는 메일)이면 빈 값이다. 이 식(RCPT TO 단계)에서 쓸 수 있는 변수다
|
||||
([MtaInboundThrottle](https://stalw.art/docs/ref/object/mta-inbound-throttle/) · [MtaRcptToVariable](https://stalw.art/docs/ref/expression/variable/mta-rcpt-to-variable/) ·
|
||||
[is_empty](https://stalw.art/docs/configuration/expressions/functions/)). 엔진은 저장할 때 식을 검사한다 — 모르는 변수·이 맥락에 없는 변수(HTTP 의 `path` 등)는 거부한다.
|
||||
- 웹메일 발송(JMAP 제출)은 엔진 안에서 그 직원 계정으로 인증된 SMTP 세션으로 돌아 빠진다. 메일 프로그램의 SMTP 제출(465, AUTH 뒤)도 같은 까닭으로 빠진다 (465 는 시험하지 않았다).
|
||||
- 찾지 못하면 만들지 않고 `[주의]` 만 낸다 — 지웠다면 그대로 두고, 이름을 바꿨다면 그 한도의 `match` 를 직접 본다.
|
||||
- 한도 값(`rate`)·켜짐(`enable`)은 건드리지 않는다. `match` 는 실행할 때마다 맞추므로, 받는 사람별 예외처럼 조건을 따로 둬야 하면 이 한도를 끄고(`enable: false`)
|
||||
다른 설명으로 새 한도를 만든다 (setup 은 그 한도를 건드리지 않는다).
|
||||
- **바깥에서 들어오는 메일(인증 없는 SMTP)에는 그대로 건다** — 같은 바깥 도메인 → 한 사람 시간당 25통.
|
||||
- **보낸 IP 한도는 그대로 둔다** — 연결할 때 세는 한도라 웹메일 발송에는 처음부터 걸리지 않는다 (JMAP 제출은 엔진 안의 세션이라 연결 단계를 거치지 않는다 —
|
||||
0.16.27 소스 [`smtp/src/inbound/spawn.rs`](https://github.com/stalwartlabs/stalwart/blob/v0.16.27/crates/smtp/src/inbound/spawn.rs)).
|
||||
조건에 `authenticated_as` 를 쓰면 엔진이 그 한도를 MAIL FROM 단계로 옮겨(한도는 key 와 식이 쓰는 변수를 모두 아는 첫 단계에서 센다 — 같은 문서 "Evaluation stages",
|
||||
소스 [`common/src/config/smtp/queue.rs`](https://github.com/stalwartlabs/stalwart/blob/v0.16.27/crates/common/src/config/smtp/queue.rs) 의 `parse_inbound`)
|
||||
연결이 아니라 MAIL FROM 을 세게 되고, 바깥에서 몰려오는 연결은 더는 막지 못한다.
|
||||
- 확인 (2026-10-28, 0.16.27): 바꾼 뒤 웹메일과 같은 방식(대리 접근 JMAP 제출)으로 ara.kim → doyun.lee 30통(초당 40통 남짓)이 모두 `250 2.1.5 Queued` (엔진 로그에 한도 기록 없음),
|
||||
인증 없는 SMTP 는 같은 바깥 도메인(`example.net`) → doyun.lee 26번째부터 `452 4.4.5` (로그 `id = "MtaInboundThrottle with id …"`, `limit = [25, 3600000ms]`).
|
||||
setup 을 이어서 두 번 돌리면 첫 번째는 `[변경]`, 두 번째는 `[유지]` 다(엔진이 돌려주는 `match` · `key` 모양이 그대로 비교된다 — 바뀌면 매번 `[변경]` 이 나온다).
|
||||
|
||||
운영 전에 정할 것:
|
||||
|
||||
- **메일을 많이 받는 주소** — 같은 바깥 도메인에서 한 사람이 시간당 25통 넘게 받으면 그 뒤는 바깥 서버가 미뤘다가 다시 보낸다(늦게 받는다).
|
||||
알림 메일(모니터링·결제·배포 알림 등)처럼 한 곳에서 같은 주소로 많이 오는 주소를 먼저 꼽고, 한도를 올리거나(관리 화면 — setup 은 `rate` 를 건드리지 않는다) 위처럼 따로 한도를 둔다.
|
||||
- **한 IP 초당 연결 5개** — 큰 메일 서비스가 여러 연결로 한꺼번에 배달하면 넘을 수 있다(인사 없이 끊기고 바깥 서버가 다시 시도한다 — 늦게 받는다).
|
||||
메일 프로그램(465)으로 보내는 직원이 사무실 NAT 뒤에 많아도 한 IP 로 센다(연결은 인증 전에 센다 — 시험하지 않았다).
|
||||
두 한도 모두 넘으면 로그 `smtp.rate-limit-exceeded` 가 남으니 운영을 시작한 뒤 이 로그로 정한다.
|
||||
- **직원마다 내보내는 양의 한도(`x:MtaOutboundThrottle`)는 비어 있다** — 이제 웹메일 발송에는 엔진의 받는 쪽 한도도 걸리지 않으므로, 남는 것은 백엔드의 요청 수 한도(루트 env `THROTTLE_*` — IP 당)뿐이다.
|
||||
계정이 털리거나 잘못 쓰여 한꺼번에 많이 보내면 바깥 서버들의 한도에 걸려 서버 평판이 떨어진다. 보낸 사람마다 한도(key `sender` — 넘으면 거절이 아니라 대기열에서 미뤄진다,
|
||||
[Outbound rate limits](https://stalw.art/docs/mta/outbound/rate-limit/))를 둘지 정한다 — 사내 배달에도 걸리는지는 확인하지 않았다.
|
||||
|
||||
---
|
||||
|
||||
## arasystem.it.kr 전환 절차 (mailcow → Stalwart)
|
||||
@@ -491,5 +538,7 @@ PROXY protocol(2 — 리스너별 `overrideProxyTrustedNetworks`, 검증 필요)
|
||||
| `… 에 복구 관리자(STALWART_RECOVERY_ADMIN)가 남아 있습니다` | `unset MAIL_RECOVERY_ADMIN` 후 `docker compose --env-file .env.production up -d mail` |
|
||||
| `백엔드 주소 … 가 신뢰 대역(…) 밖입니다` | `MAIL_TRUSTED_NETWORKS` 를 비우고(= `APP_NETWORK_SUBNET`) setup 재실행 |
|
||||
| `공개 443 차단 확인 실패` | setup 이 `ReloadSettings` 까지 했는지, `x:Http` 의 `allowedEndpoints` 가 위 식인지 (관리 화면 › Settings › HTTP) |
|
||||
| `[주의] 받는 쪽 한도 "Sender address to recipient throttle"(…)을 찾지 못해 만들지 않았습니다` | 엔진 기본 한도를 지웠거나 이름을 바꿨다. 지웠다면 그대로 두고, 이름을 바꿨다면 그 한도의 `match` 가 `{"match":{},"else":"is_empty(authenticated_as)"}` 인지 관리 화면에서 본다 ([엔진 한도](#엔진-한도)) |
|
||||
| 웹메일 발송의 받는 사람이 `452 4.4.5 Rate limit exceeded` 로 거절된다 | setup 출력에 `받는 쪽 한도 … 인증 없는 세션에만` 이 있는지 본다. 받는 쪽 한도를 새로 만들었다면 그 `match` 도 — 엔진 로그 `smtp.rate-limit-exceeded` 의 `id` 가 어느 한도인지 알려 준다 ([엔진 한도](#엔진-한도)) |
|
||||
| `Password is too weak` | 더 길고 무작위인 값으로 (예: `change-me-in-local-env` 는 통과, `devpassword1` 은 거부) |
|
||||
| 외부로 보낸 메일이 Mailpit 에 없다 | setup 의 `[확인] 설정 리로드 완료` 가 나왔는지. 로그: `docker logs "${PROJECT_NAME}-mail"` 의 `delivery.*` |
|
||||
|
||||
+53
-4
@@ -34,9 +34,11 @@
|
||||
* 공개 443(https 리스너)의 JMAP·관리 API·웹 포털을 막는다 (MTA-STS 정책 파일만 연다)
|
||||
* 남은 위험 표시 — 게이트웨이로 관리자를 대리 접근하면 관리 API 가 열리는지 매번 확인해 [주의] 로 보여 준다
|
||||
* (Stalwart 0.16 은 대리 접근 대상을 제한하지 못한다 — 막는 일은 백엔드가 한다, mail/README.md "보안")
|
||||
* 7. [개발] 외부 발송 경로 → Mailpit, 설정 리로드, 반영 확인
|
||||
* (리로드 전에는 외부 주소로 보낸 메일이 실제 인터넷으로 나간다 — 그래서 8 보다 먼저 한다)
|
||||
* 8. [개발] 테스트 직원 계정
|
||||
* 7. 엔진 한도 받는 사람 단위 받는 쪽 한도(같은 보낸 도메인 → 한 받는 사람)를 인증 없는 세션에만 건다 (설정 리로드 뒤 반영)
|
||||
* — 로그인한 직원의 발송(웹메일)에는 걸지 않고, 바깥에서 들어오는 메일에는 그대로 (mail/README.md "엔진 한도")
|
||||
* 8. [개발] 외부 발송 경로 → Mailpit, 설정 리로드, 반영 확인
|
||||
* (리로드 전에는 외부 주소로 보낸 메일이 실제 인터넷으로 나간다 — 그래서 9 보다 먼저 한다)
|
||||
* 9. [개발] 테스트 직원 계정
|
||||
*
|
||||
* 종료 코드: 0 성공 · 1 실패 · 3 후속 조치 필요 (안내대로 한 뒤 다시 실행하면 이어서 진행)
|
||||
* — 개발에서 docker 를 쓸 수 없는 환경의 재시작, 운영 --recovery 실행 뒤 복구 관리자 끄기
|
||||
@@ -97,6 +99,20 @@ const HTTP_ENDPOINT_POLICY = {
|
||||
const USER_ROLE_DESCRIPTION = 'User';
|
||||
/** 직원 역할에서 뺄 권한 — Principal 조회(JMAP·DAV). 백엔드는 Principal 메서드를 쓰지 않는다 */
|
||||
const PRINCIPAL_PERMISSION_PATTERN = /^(jmapPrincipal|davPrincipal)/;
|
||||
/**
|
||||
* 엔진 기본 받는 쪽 한도 가운데 받는 사람 단위의 것 (x:MtaInboundThrottle — 같은 보낸 도메인 → 한 받는 사람, 기본 시간당 25).
|
||||
* id 는 설치마다 다르므로 설명·key 로 찾는다. 기본 조건은 모든 세션이라, 보낸 도메인이 모두 같은 사내 메일은 동료 모두 합쳐 한 사람에게
|
||||
* 시간당 25통을 넘으면 웹메일 발송(JMAP 제출)의 그 받는 사람이 452 4.4.5 로 거절됐다 — 엔진은 다시 보내지도 반송하지도 않는다 (2026-10-28 확인).
|
||||
*/
|
||||
const RECIPIENT_THROTTLE = { description: 'Sender address to recipient throttle', key: ['senderDomain', 'rcpt'] };
|
||||
/**
|
||||
* 그 한도를 걸 세션 — 인증 없는 세션(바깥에서 들어오는 메일)만. 엔진 기본값이 스팸 필터에 쓰는 식(x:MtaStageData 의 enableSpamFilter)과 같다.
|
||||
* authenticated_as 는 인증한 계정 이름이고 인증 없는 세션이면 빈 값이다 — 웹메일 발송은 엔진 안에서 그 직원으로 인증된 세션이라 빠진다.
|
||||
* 엔진은 저장할 때 식을 검사한다 (모르는 변수·이 맥락에 없는 변수는 거부 — Stalwart 0.16.27 에서 확인).
|
||||
* ⚠️ 다른 기본 한도 "Sender IP throttle"(보낸 IP 마다 초당 연결 5개)은 그대로 둔다. 연결할 때 세는 한도라 웹메일 발송에는 걸리지 않고,
|
||||
* 식에 authenticated_as 를 쓰면 엔진이 그 한도를 MAIL FROM 단계로 옮겨(식의 변수를 아는 첫 단계) 바깥 접속의 연결 수를 더는 세지 않는다.
|
||||
*/
|
||||
const UNAUTHENTICATED_SESSIONS_ONLY = { match: {}, else: 'is_empty(authenticated_as)' };
|
||||
/**
|
||||
* 계정 아이디(주소의 @ 앞) 형식 — 영문·숫자·. _ -, 앞뒤는 영문·숫자, 64자 이하, 대소문자 무시 (소문자로 맞춰 검사).
|
||||
* ⚠️ 백엔드 backend/src/modules/auth/employee-address.ts 의 LOCAL_PART_PATTERN(isLocalPart)과 같은 규칙이다 —
|
||||
@@ -1296,7 +1312,7 @@ function preflight(cfg) {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 잠금 · 반영 확인
|
||||
// 잠금 · 엔진 한도 · 반영 확인
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
@@ -1337,6 +1353,37 @@ async function ensureHttpEndpointPolicy(client) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 받는 사람 단위 받는 쪽 한도를 인증 없는 세션(바깥에서 들어오는 메일)에만 건다 — ReloadSettings 뒤에 반영된다.
|
||||
* 설명·key 로 찾고, 없으면 만들지 않고 알린다 (지웠다면 운영에서 정한 것이고, 이름을 바꿨다면 사람이 그 한도를 본다).
|
||||
* 한도 값(rate)·켜짐(enable)은 건드리지 않는다 — 운영에서 정할 것 (mail/README.md "엔진 한도").
|
||||
*/
|
||||
async function ensureRecipientThrottle(client) {
|
||||
const label = `받는 쪽 한도 "${RECIPIENT_THROTTLE.description}"(같은 보낸 도메인 → 한 받는 사람)`;
|
||||
const scope = '인증 없는 세션에만 (로그인한 직원의 발송 제외)';
|
||||
const throttles = (await listAll(client, 'MtaInboundThrottle', ['description', 'key', 'match', 'enable'])).filter(
|
||||
(item) => item.description === RECIPIENT_THROTTLE.description && sameTrueSet(item.key, RECIPIENT_THROTTLE.key),
|
||||
);
|
||||
if (throttles.length === 0) {
|
||||
warn(
|
||||
`${label}을 찾지 못해 만들지 않았습니다. 이름을 바꿔 쓰고 있다면 그 한도의 match 가 ${JSON.stringify(UNAUTHENTICATED_SESSIONS_ONLY)} 인지 ` +
|
||||
'관리 화면 › Settings › MTA › Rates & Quotas › Inbound Rate Limits 에서 확인하세요 — 아니면 로그인한 직원의 발송도 그 한도에 걸린다 (mail/README.md "엔진 한도").',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const stale = throttles.filter((item) => stableJson(item.match) !== stableJson(UNAUTHENTICATED_SESSIONS_ONLY));
|
||||
if (stale.length > 0) {
|
||||
await setObjects(client, 'MtaInboundThrottle', {
|
||||
update: Object.fromEntries(stale.map((item) => [item.id, { match: UNAUTHENTICATED_SESSIONS_ONLY }])),
|
||||
});
|
||||
}
|
||||
for (const item of throttles) {
|
||||
const disabled = item.enable === false ? ' · 꺼져 있음(enable: false)' : '';
|
||||
if (stale.includes(item)) log(`[변경] ${label} — ${scope}${disabled} · 전 match ${JSON.stringify(item.match)}`);
|
||||
else log(`[유지] ${label} — ${scope}${disabled}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 저장한 설정을 적용한다. 지난 실행이 리로드 전에 멈췄을 수도 있으므로 매번 부른다 */
|
||||
async function reloadSettings(client) {
|
||||
await setObjects(client, 'Action', { create: { reload: { '@type': 'ReloadSettings' } } });
|
||||
@@ -1615,6 +1662,8 @@ async function main() {
|
||||
// 잠금 — 직원의 계정 목록 조회, 공개 443 의 JMAP·관리 API
|
||||
await ensureUserRoleWithoutPrincipals(admin);
|
||||
await ensureHttpEndpointPolicy(admin);
|
||||
// 엔진 한도 — 받는 사람 단위 한도는 바깥에서 들어오는 메일에만 (로그인한 직원의 발송에는 걸지 않는다)
|
||||
await ensureRecipientThrottle(admin);
|
||||
// 개발은 외부 발송 경로를 Mailpit 으로 — 메일을 보낼 수 있는 계정을 만들기 전에 리로드까지 끝낸다
|
||||
if (!cfg.isProduction) await ensureDevRelay(admin);
|
||||
await reloadSettings(admin);
|
||||
|
||||
Reference in New Issue
Block a user