도메인 수정
도메인의 커스텀 추적 도메인과 커스텀 파일 도메인을 설정하거나 다시 검사하거나 제거합니다. 이 API가 도메인에 대해 바꿀 수 있는 것은 이 두 가지입니다.
본인 키로 워크스페이스에 실제 호출을 실행합니다.
PATCH /domains/{id}
도메인의 커스텀 추적 도메인과 커스텀 파일 도메인을 설정하거나 다시 검사하거나 제거합니다. 이 API가 도메인에 대해 바꿀 수 있는 것은 이 두 가지입니다.
요청
도메인은 커스텀 추적 도메인 하나와 커스텀 파일 도메인 하나를 가질 수 있습니다. 각각 links.acme.com, files.acme.com처럼 직접 고른 하위 도메인이며, 도메인이 검증되었거나 _openemail-challenge TXT 레코드가 게시되는 즉시 설정할 수 있습니다. 아직 메일을 수신하고 있지 않아도 됩니다. 하나를 설정하면 그 이름 전용 주소가 준비되어 target에 보고되고, record는 그 이름을 해당 주소로 가리키게 하는 CNAME 레코드입니다. 검사가 통과하면 이 도메인에서 나가는 새 메일의 추적 링크와 열람 픽셀은 기본 호스트 대신 https://links.acme.com/t/...을 사용하고, 이 도메인에서 보낸 파일의 다운로드 링크는 https://files.acme.com/f/...을 사용합니다.
매개변수
trackingHoststring | null- 추적 링크와 열람 픽셀에 사용할 하위 도메인이며 최대 512자입니다. 앞뒤 공백을 제거하고 소문자로 바꾸며, 앞의 `https://`나 `http://`, 경로, 끝의 점은 검사 전에 제거됩니다. 새 값은 현재 추적 도메인을 대체하고, 현재 값과 같은 값을 보내면 검사를 다시 실행하며, `null`이나 빈 문자열은 제거하고, 필드를 생략하면 그대로 둡니다.
storageHoststring | null- 파일 다운로드 링크에 사용할 하위 도메인으로, 같은 방식으로 정리되고 같은 512자 제한이 적용됩니다. 새 값은 현재 파일 도메인을 대체하고, 현재 값과 같은 값을 보내면 검사를 다시 실행하며, `null`이나 빈 문자열은 제거하고, 필드를 생략하면 그대로 둡니다.
본문은 키에 대해서는 엄격하고 개수에 대해서는 느슨합니다. trackingHost와 storageHost 외의 키는 422 unknown_parameter이며, 둘 다 없는 본문은 아무것도 하지 않고 현재 상태의 도메인과 함께 200을 응답합니다. 둘을 한 호출에 담을 수 있고, trackingHost가 먼저인 순서로 적용됩니다. trackingHost가 거부되면 storageHost를 건드리기 전에 호출이 중단되고, storageHost가 거부되면 이미 적용된 trackingHost 변경은 그대로 남습니다. 어느 한쪽이 독립적으로 처리되어야 한다면 따로 보내세요.
추적 도메인과 파일 도메인 설정하기
domains:write가 필요합니다. 각 호스트는 같은 호출 안에서 검증되고 저장되고 검사되므로, 응답에는 이미 그 첫 검사 결과가 담겨 있습니다. 본문 형태는 GET /domains/{id}와 동일합니다.
curl -X PATCH "$OE/domains/b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f" -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "trackingHost": "links.acme.com", "storageHost": "files.acme.com" }'{ "object": "domain", "id": "b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f", "domain": "acme.com", "receiving": { "verified": true, "verifiedAt": "2026-08-14T10:02:00.000Z", "catchAll": false, "lastCheckedAt": "2026-08-29T06:00:00.000Z", "error": null }, "sending": { "status": "verified", "canSend": true, "checkedAt": "2026-08-29T06:00:00.000Z", "error": null, "note": "Mail from this domain is signed and can be sent." }, "tracking": { "host": "links.acme.com", "status": "pending", "active": false, "target": "oelinks3f9a1c7e2b8d4a60.edge.openemail.uk", "record": { "type": "CNAME", "name": "links.acme.com", "value": "oelinks3f9a1c7e2b8d4a60.edge.openemail.uk" }, "checkedAt": "2026-08-29T06:05:12.000Z", "verifiedAt": null, "error": "links.acme.com does not resolve yet. Add a CNAME record named links.acme.com with the value oelinks3f9a1c7e2b8d4a60.edge.openemail.uk, then check again." }, "storage": { "host": "files.acme.com", "status": "pending", "active": false, "target": "oefiles81c40d6b2f7e9a35.edge.openemail.uk", "record": { "type": "CNAME", "name": "files.acme.com", "value": "oefiles81c40d6b2f7e9a35.edge.openemail.uk" }, "checkedAt": "2026-08-29T06:05:12.000Z", "verifiedAt": null, "error": "files.acme.com does not resolve yet. Add a CNAME record named files.acme.com with the value oefiles81c40d6b2f7e9a35.edge.openemail.uk, then check again." }, "addresses": [ { "address": "[email protected]", "enabled": true } ], "createdAt": "2026-08-14T09:55:11.000Z"}tracking.record와 storage.record를 DNS 제공업체에 평범한 CNAME으로, 프록시는 끈 채로 게시하세요. 검사는 각 이름을 해석한 뒤 https://links.acme.com/t/v/<nonce> 또는 https://files.acme.com/f/v/<nonce>에 OpenEmail이 서명한 응답을 요구합니다. 리다이렉트는 검사를 실패시키며, 이름 앞에 놓인 프록시도 실패의 원인이 될 수 있습니다.
레코드가 해석되고 나면, 검사는 그 이름이 OpenEmail을 가리키고 있으며 활성화를 기다리는 중이라고 보고할 수 있습니다. 이는 HTTPS 인증서 발급 과정으로, 우리 쪽에서 진행되고 사용자가 할 일은 없으며 시간이 조금 걸릴 수 있습니다. 끝나고 나면 다음으로 통과하는 검사가 status를 active로 바꿉니다.
호출 중에 주소를 준비하지 못하면 record는 null, target은 빈 문자열이 되고 error에 준비 중이라는 내용이 담깁니다. 추가 호출 없이 몇 분 안에 끝나므로, GET /domains/{id}로 도메인을 다시 읽어 레코드를 확인하세요.
두 이름은 서로 독립적입니다. 한 필드만 담은 호출은 다른 객체를 있는 그대로 두므로, 나중에 파일 도메인을 설정해도 이미 동작 중인 추적 도메인은 전혀 흔들리지 않습니다.
다시 검사하거나 제거하기
도메인이 이미 갖고 있는 호스트를 그대로 보내면 다음 예정된 검사를 기다리지 않고 지금 검사를 실행합니다. 예정된 것이든 아니든 마지막 검사가 30초 이내에 실행되었다면, 호출은 저장된 상태를 그대로 반환합니다. 어떤 이름을 제거하려면 해당 필드에 null을 보내고, 유지하려는 이름은 필드 자체를 생략하세요.
curl -X PATCH "$OE/domains/b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f" -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "trackingHost": null }'{ "host": null, "status": "none", "active": false, "target": "", "record": null, "checkedAt": null, "verifiedAt": null, "error": null}이미 발송된 메일 안의 링크는 나갈 때 사용한 호스트를 그대로 유지하며, 이는 추적 링크뿐 아니라 파일의 다운로드 링크에도 똑같이 적용됩니다. 이름을 제거하거나 바꾼 뒤에도 기존 CNAME 레코드가 남아 있는 한 그 링크들은 계속 동작합니다. 같은 이름을 다시 설정하면 record가 달라질 수 있으니, 응답이 알려 주는 레코드를 게시하세요.
tracking 객체
hoststring | null- 추적 도메인이며, 도메인에 설정된 것이 없으면 null입니다.
status'none' | 'pending' | 'active' | 'failed'- `none`은 추적 도메인이 설정되지 않았다는 뜻입니다. `pending`은 설정되었지만 아직 한 번도 검사를 통과하지 못했다는 뜻입니다. `active`는 새 메일이 그것을 사용한다는 뜻입니다. `failed`는 이전에는 검사를 통과했지만 그 뒤로 사용에서 빠졌다는 뜻입니다.
activeboolean- `status`가 `active`일 때에 한해 true이며, 그때가 바로 이 도메인에서 나가는 새 메일의 추적 링크와 열람 픽셀이 그 호스트를 사용하는 때입니다.
targetstring- CNAME 레코드가 가리키는 주소로, 이 추적 도메인 전용으로 준비된 것입니다. `host`가 null인 동안, 그리고 새 호스트용 주소가 아직 준비 중인 동안에는 빈 문자열입니다.
record{ type: 'CNAME'; name: string; value: string } | null- 게시해야 할 레코드로, 이름은 `host`를 따르고 값은 `target`입니다. 추적 도메인이 없을 때, 그리고 새 호스트용 주소가 아직 준비 중일 때는 null입니다.
checkedAtstring | null- 호스트를 마지막으로 검사한 시각, ISO-8601. 첫 검사 전까지는 null입니다.
verifiedAtstring | null- 마지막으로 검사를 통과한 시각, ISO-8601. 한 번도 통과한 적 없는 호스트는 null입니다.
errorstring | null- 마지막 검사가 발견한 내용을, 도메인 소유자가 바로 조치할 수 있는 표현으로 담습니다. 마지막 검사가 통과했거나 아직 검사가 실행되지 않았다면 null입니다. 한두 번 검사에 실패한 호스트는 여전히 `active`이며 그 사유가 여기에 담깁니다.
storage 객체
파일 도메인은 storage에 보고되며, 필드 구성은 tracking과 하나하나 동일합니다. 다른 것은 그 이름이 무엇에 쓰이는지뿐입니다. 여기서 active는 이 도메인에서 보낸 파일의 다운로드 링크가 그 이름을 가리킨다는 뜻입니다.
hoststring | null- 파일 도메인이며, 도메인에 설정된 것이 없으면 null입니다.
status'none' | 'pending' | 'active' | 'failed'- `none`은 파일 도메인이 설정되지 않았다는 뜻입니다. `pending`은 설정되었지만 아직 한 번도 검사를 통과하지 못했다는 뜻입니다. `active`는 새 메일이 그것을 사용한다는 뜻입니다. `failed`는 이전에는 검사를 통과했지만 그 뒤로 사용에서 빠졌다는 뜻입니다.
activeboolean- `status`가 `active`일 때에 한해 true이며, 그때가 바로 이 도메인에서 보낸 파일의 다운로드 링크가 그 호스트를 사용하는 때입니다.
targetstring- CNAME 레코드가 가리키는 주소로, 이 파일 도메인 전용으로 준비된 것입니다. `host`가 null인 동안, 그리고 새 호스트용 주소가 아직 준비 중인 동안에는 빈 문자열입니다.
record{ type: 'CNAME'; name: string; value: string } | null- 게시해야 할 레코드로, 이름은 `host`를 따르고 값은 `target`입니다. 파일 도메인이 없을 때, 그리고 새 호스트용 주소가 아직 준비 중일 때는 null입니다.
checkedAtstring | null- 호스트를 마지막으로 검사한 시각, ISO-8601. 첫 검사 전까지는 null입니다.
verifiedAtstring | null- 마지막으로 검사를 통과한 시각, ISO-8601. 한 번도 통과한 적 없는 호스트는 null입니다.
errorstring | null- 마지막 검사가 발견한 내용을, 도메인 소유자가 바로 조치할 수 있는 표현으로 담습니다. 마지막 검사가 통과했거나 아직 검사가 실행되지 않았다면 null입니다. 한두 번 검사에 실패한 호스트는 여전히 `active`이며 그 사유가 여기에 담깁니다.
호스트는 어떻게 검사되는가
두 이름은 같은 주기로 검사되며, 각각 따로 검사됩니다.
- 아직 검사를 통과하지 못한 호스트는 첫 한 시간 동안 2분마다, 첫 하루 동안 10분마다, 첫 주 동안 매시간, 그 이후에는 6시간마다 검사합니다.
- 활성 호스트는 10분마다 검사하며, 검사에 실패하면 1분 뒤, 그다음 2분 뒤에 재시도합니다.
- 활성 호스트는 검사에 세 번 연속 실패하거나 마지막으로 통과한 검사가 2시간을 넘기면 사용이 중단됩니다. 그러면 새 메일은 기본 호스트로 돌아가고, 검사가 다시 통과할 때까지
status는failed로 읽힙니다. 검사는 계속되며 간격이 점점 벌어져 최대 한 시간 간격이 됩니다.
추적 도메인은 추적 경로만, 파일 도메인은 다운로드 경로만 제공하며, 각각은 그것을 소유한 워크스페이스가 보낸 메일에 대해서만 응답합니다.
오류
| 상태 | type | code | 발생 조건 |
|---|---|---|---|
| 400 | invalid_request_error | malformed_json | 본문이 유효한 JSON이 아닙니다. |
| 403 | permission_error | insufficient_scope | 키에 domains:write가 없습니다. |
| 404 | not_found_error | resource_not_found | 이 워크스페이스에 그 id를 가진 도메인이 없습니다. |
| 409 | conflict_error | domain_not_verified | receiving.verified가 false이고 도메인의 _openemail-challenge TXT 레코드도 아직 게시되지 않은 상태에서 새 호스트를 보냈습니다. param은 값이 들어온 필드, 즉 trackingHost 또는 storageHost입니다. |
| 409 | conflict_error | tracking_host_in_use | 다른 도메인이 이미 그 호스트를 추적 도메인으로 쓰고 있거나, 그 호스트가 이미 파일 도메인으로 사용 중이거나, 이 도메인의 추적 도메인이 다른 OpenEmail 서버에서 관리되고 있습니다. param은 trackingHost입니다. |
| 409 | conflict_error | storage_host_in_use | 파일 도메인에 대한 동일한 세 가지 경우입니다. 다른 도메인이 이미 그 호스트를 파일 도메인으로 쓰고 있거나, 그 호스트가 이미 추적 도메인으로 사용 중이거나, 여기의 파일 도메인이 다른 OpenEmail 서버에서 관리되고 있습니다. param은 storageHost입니다. |
| 422 | validation_error | invalid_tracking_host | 호스트가 유효한 호스트명이 아니거나 허용되지 않습니다. 반드시 해당 도메인의 엄격한 하위 도메인이어야 하며, 반송 경로 호스트 bounce.<domain>, OpenEmail에 속한 이름, 메일 수신용으로 설정된 도메인은 사용할 수 없습니다. param은 trackingHost입니다. |
| 422 | validation_error | invalid_storage_host | 동일한 규칙이 파일 도메인에서 거부된 경우입니다. param은 storageHost입니다. |
| 422 | validation_error | unknown_parameter | trackingHost와 storageHost 외의 본문 키입니다. |
| 422 | validation_error | invalid_parameter | 본문이 JSON 객체가 아니거나, 포함된 필드가 string도 null도 아니거나, 512자를 넘습니다. 두 필드가 모두 없는 본문은 오류가 아니며, 아무것도 바꾸지 않고 200으로 돌아옵니다. |
| 422 | validation_error | capability_unsupported | 키가 이 도메인 전체가 아니라 개별 주소로 좁혀져 있는데, 두 이름은 도메인의 모든 주소에 적용됩니다. domainAllowlist에 이 도메인을 가진 키라면 설정할 수 있습니다. param은 domainAllowlist입니다. |