템플릿, 규칙, 웹훅
모든 `templates`, `rules`, `webhooks` 명령: 슬러그로 보내는 저장된 본문, 도착하는 메일을 정리하는 규칙, 자체 서버로 보내는 서명된 이벤트.
세 네임스페이스
이 세 네임스페이스가 있으면 아무도 지켜보지 않아도 메일함이 돌아갑니다. templates는 여러 번 보내는 본문을 저장하고, rules는 도착하는 메일을 정리하며, webhooks는 무슨 일이 있었는지 자체 서버에 알립니다. 각 명령은 케밥 케이스 이름을 가진 SDK 메서드이므로 webhooks.rotateSecret은 openemail webhooks rotate-secret이며, 다른 모든 리소스 명령처럼 인수와 플래그를 읽습니다.
| 네임스페이스 | 별칭 | 읽기에 필요한 것 | 변경에 필요한 것 |
|---|---|---|---|
| templates | template | templates:read | templates:write, 그리고 send에는 emails:send도 |
| rules | rule | rules:read, test 포함 | rules:write |
| webhooks | webhook | webhooks:read | webhooks:write, test와 replay-delivery 포함 |
이 페이지는 모든 명령과 스크립트로 만들기 전에 알아 둘 점을 나열합니다. 모든 인수와 플래그를 타입, 필요한 범위, 엔드포인트, 반환값과 함께 보려면 openemail <namespace> <verb> --help를 실행하세요. --json을 붙이면 같은 페이지를 JSON으로 받습니다.
openemail templates --helpopenemail rules create --helpopenemail webhooks replay-delivery --help --json템플릿
한 번 저장해서 여러 번 보내는 본문으로, 버전, 미리 보기, 타입이 있는 props를 갖습니다. <id-or-slug>를 받는 모든 명령은 tpl_ ID나 슬러그를 받습니다. 템플릿 이름을 바꿔도 슬러그는 바뀌지 않으므로 스크립트에서는 슬러그를 고정하세요.
| 명령 | 하는 일 |
|---|---|
| openemail templates list | 템플릿을 최근 업데이트순으로 나열합니다. --status는 초안, 활성, 보관된 것 중 하나만 남기고, --search는 이름, 슬러그, 제목을 검색하며, --sort로 순서를 고릅니다 |
| openemail templates get <id-or-slug> | 템플릿을 본문을 포함한 head 버전 전체와 함께 읽습니다 |
| openemail templates create --name <value> | 템플릿과 첫 버전을 만듭니다. --publish를 주지 않으면 초안으로 남으며, --starter는 스타터 디자인으로 시작합니다 |
| openemail templates update <id-or-slug> | 이름, 슬러그, 설명, 상태, 또는 초안 본문을 고칩니다. 게시하기 전까지 보내기는 게시된 버전을 계속 씁니다 |
| openemail templates duplicate <id-or-slug> | head 버전을 새 템플릿으로 복사하며, 새 템플릿은 초안으로 시작합니다 |
| openemail templates replace-content <id-or-slug> | 본문을 스타터의 것(--starter)이나 다른 템플릿의 것(--from-template-id)으로 바꿉니다. 확인을 요청합니다 |
| openemail templates delete <id-or-slug> | 템플릿과 모든 버전을 삭제합니다. 확인을 요청합니다 |
| openemail templates list-versions <id-or-slug> | 버전을 최신순으로, 본문 없이 나열합니다 |
| openemail templates get-version <id-or-slug> <version> | 초안을 건드리지 않고 버전 하나를 본문과 함께 읽습니다 |
| openemail templates publish <id-or-slug> | 초안을 게시해 보내기가 그것을 쓰게 합니다. 이미 게시된 head를 게시하면 아무것도 바뀌지 않습니다 |
| openemail templates restore-version <id-or-slug> <version> | 이전 버전의 본문을 초안으로 되돌립니다. 확인을 요청합니다 |
| openemail templates delete-version <id-or-slug> <version> | 버전 하나를 삭제합니다. 게시된 버전, head, 유일한 버전은 거부됩니다. 확인을 요청합니다 |
| openemail templates list-starters | 기본 제공 스타터 디자인을 나열합니다 |
| openemail templates get-starter <slug> | 스타터 하나를 블록 트리와 렌더링된 미리 보기까지 전부 읽습니다 |
| openemail templates list-fonts | 템플릿이 불러올 수 있는 웹 글꼴을 나열합니다 |
| openemail templates render | 어디에도 저장되지 않은 본문을 --html이나 --document로 렌더링합니다 |
| openemail templates preview <id-or-slug> | 저장된 템플릿을 초안까지 포함해 --props와 --slots로 보내지 않고 렌더링합니다 |
| openemail templates get-analytics <id-or-slug> | 기간 동안의 보내기, 열람, 클릭을 일별, 출처별, 버전별로 |
| openemail templates list-sends <id-or-slug> | 템플릿이 보낸 개별 메시지를 최신순으로 한 페이지씩 |
| openemail templates send <id-or-slug> --from <value> --to <a,b> | 게시된 버전, 또는 --template-version이 고정한 버전으로 렌더링한 이메일을 보냅니다 |
템플릿에는 head 버전과 게시된 버전이 있습니다. head 버전은 게시되지 않은 수정이 있는 동안 초안이며, 게시된 버전은 --template-version 없는 보내기가 쓰는 버전입니다. --publish 없는 create, update로 한 본문 수정, replace-content, restore-version은 모두 초안에 쓰므로, publish하기 전까지 받는 사람에게는 새로운 것이 보이지 않습니다.
- 보관된 템플릿은
template_archived로 보내기를 거부합니다.publish하면 다시 활성이 됩니다. - 워크스페이스에는 보관된 것을 포함해 템플릿을 최대 200개까지 둘 수 있으므로, 자리를 만들려면 삭제하는 수밖에 없습니다.
- 예약되었거나 대기 중인 브로드캐스트가 템플릿을 가리키는 동안
delete는template_in_use로 거부됩니다.
규칙
도착하는 메일에 대해 rules list가 보여 주는 순서대로 평가되는 조건과 동작입니다. 규칙은 켜져 있는 동안 도착한 메일에만 작동합니다. 이미 메일함에 있는 메일에 규칙을 적용하는 명령은 없으며, 무엇을 잡을지 보려면 rules test를 씁니다. 규칙 ID는 rul_로 시작합니다.
| 명령 | 하는 일 |
|---|---|
| openemail rules list | 규칙을 실행 순서대로 나열합니다. --enabled나 --no-enabled는 한 종류만 남깁니다 |
| openemail rules get <id> | 규칙 하나를 matchCount, lastMatchedAt과 함께 읽습니다 |
| openemail rules create --name <value> --conditions <json|@file|-> --actions <json|@file|-> | 순서의 맨 끝에 규칙을 만듭니다. --no-enabled를 주지 않으면 켜진 상태입니다 |
| openemail rules update <id> | 규칙을 바꿉니다. --conditions와 --actions는 목록 전체를 바꾸고, --position은 이 규칙만 옮깁니다 |
| openemail rules delete <id> | 규칙을 삭제합니다. 이미 한 일은 list-runs에 남습니다. 확인을 요청합니다 |
| openemail rules reorder <rule-ids...> | 모든 규칙의 순서를 한 번에 정하며, 각 규칙을 정확히 한 번씩 적습니다 |
| openemail rules test <id> | 이미 메일함에 있는 메일로 규칙을 시험 실행합니다. 아무것도 바꾸지 않으며, 꺼진 규칙에도 동작합니다 |
| openemail rules list-runs | 규칙이 도착한 메일에 실제로 한 일, 최신순. --rule-id와 --thread-id로 좁힙니다 |
--conditions는 { field, op, value } 객체의 목록으로, --match all이나 --match any로 묶이며, value는 항상 문자열이고 negate: true는 조건 하나를 뒤집습니다. --actions는 { type, value } 객체의 목록으로, 순서대로 적용됩니다. 규칙에는 조건 1개에서 20개, 동작 1개에서 10개가 들어가며, 메일함에는 규칙을 최대 100개까지 둘 수 있습니다.
- 조건 필드:
from,from_domain,envelope_from,to,cc,bcc,recipient,reply_to,delivered_to,subject,body,header,list_id,attachment_name,attachment_type,has_attachment,attachment_size,message_size,spam,hour,weekday. - 연산자:
matches,contains,equals,starts_with,ends_with,gt,lt.gt와lt는 숫자 필드에서만 동작하며,has_attachment와spam은true나false와 함께equals만 받습니다. - 동작 종류:
label,remove_label,archive,mark_read,star,spam,trash,forward,reply,block_sender,reject.label과remove_label은USER_RECEIPTS같은 라벨 ID를,forward는 주소를,reply는 템플릿 ID나 슬러그를 받습니다. from_domain은 하위 도메인도 맞추며,hour와weekday는 UTC 기준으로 읽고 일요일은0입니다.reject동작이 있는 규칙은envelope_from도 검사해야 하며, 그렇지 않으면reject_needs_envelope로 거부됩니다.
웹훅
서명된 메일함 이벤트를 받는 자체 서버의 엔드포인트로, 서명 시크릿, 전달 로그, 모든 변경의 감사 로그가 함께 있습니다. 엔드포인트 ID는 whe_, 전달 ID는 whd_로 시작합니다.
| 명령 | 하는 일 |
|---|---|
| openemail webhooks list | 워크스페이스의 엔드포인트를 최신순으로, 상태와 함께 나열합니다 |
| openemail webhooks get <id> | 엔드포인트 하나를 읽습니다. 서명 시크릿은 읽기에 절대 포함되지 않습니다 |
| openemail webhooks create --url <value> | HTTPS 엔드포인트를 등록합니다. 서명 시크릿을 출력하며, 그 시크릿을 볼 수 있는 유일한 때입니다 |
| openemail webhooks update <id> | URL, 이벤트, 허용 목록, 사용 여부를 바꿉니다. 각 목록은 저장된 목록을 대체합니다 |
| openemail webhooks delete <id> | 엔드포인트와 전달 로그를 삭제합니다. 확인을 요청합니다 |
| openemail webhooks rotate-secret <id> | 새 서명 시크릿을 발급합니다. 이전 시크릿은 즉시 작동을 멈춥니다. 확인을 요청합니다 |
| openemail webhooks test <id> | 서명된 합성 email.sent 이벤트를 보내고 전달이 어떻게 되었는지 보고합니다 |
| openemail webhooks list-deliveries <id> | 엔드포인트 하나의 전달 시도, 최신순. --status, --since, --until로 좁힙니다 |
| openemail webhooks get-delivery <id> <delivery-id> | 시도 하나의 전체 내용: 보낸 본문, 서버의 응답, 이벤트의 모든 시도, 재전송이 받아들여질지 여부 |
| openemail webhooks replay-delivery <id> <delivery-id> | 저장된 이벤트 하나를 지금 엔드포인트로 다시 보냅니다 |
| openemail webhooks list-workspace-deliveries | 모든 엔드포인트, 또는 --endpoint-ids가 지정한 엔드포인트의 전달 시도 |
| openemail webhooks list-activity <id> | 엔드포인트 하나의 감사 로그: 누가 만들고, 바꾸고, 테스트하고, 재전송하고, 제거했는지 |
| openemail webhooks list-workspace-activity | 제거된 것을 포함한 모든 엔드포인트의 감사 로그 |
--event-types를 빼면 엔드포인트는 기본 집합, 즉 email.replied를 제외한 email.* 이벤트를 받습니다. email.replied, domain.* 이벤트, suppression.* 이벤트는 지정할 때만 전달됩니다. --address-allowlist와 --domain-allowlist는 API 키를 좁히는 것과 같은 방식으로 엔드포인트를 일부 주소나 도메인으로 좁힙니다.
- 지원팀이 한도를 올리지 않았다면 워크스페이스에는 엔드포인트를 10개까지 둘 수 있습니다.
- 전달에 100번 연속 실패한 엔드포인트는 서버가 끄며,
webhooks update <id> --enabled로 되살립니다. - 브라우저 로그인에서는 워크스페이스 소유자만
get-delivery로 전달을 읽을 수 있습니다. 그 밖의 사람은owner_only와 종료 코드4를 받습니다.
템플릿을 확인한 뒤 게시하기
templates preview는 같은 값으로 보냈을 때 나올 결과를 초안까지 포함해 정확히 렌더링하며, templates:read만 필요하므로 읽기 전용 키로도 실행할 수 있습니다. send라면 거부했을 필수 prop 누락을 경고로 보고하므로, 경고가 하나라도 있으면 빌드를 실패시키세요. 이미 게시된 head를 게시해도 아무것도 바뀌지 않으므로 publish는 배포할 때마다 해도 안전합니다.
draft=$(openemail templates get order-shipped --json | jq .latestVersion)openemail templates preview order-shipped --template-version "$draft" \ --props '{"orderId":"AC-4192","customer":"Ada"}' --json | jq -e '.warnings == []'openemail templates publish order-shipped템플릿으로 보내기
버전을 고정해서 내일 게시되는 수정본이 이 코드가 보내는 내용을 바꾸지 않게 하고, 보내기를 일으킨 원인에서 멱등 키를 가져와 넘겨서 응답을 잃은 뒤의 재시도가 두 번째 메시지를 보내는 대신 첫 메시지를 재생하게 하세요. --dry-run은 메서드, URL, 자격 증명을 가린 헤더, 본문을 출력하고, 아무것도 보내지 않고 코드 0으로 종료합니다. 보내려면 --dry-run 없이 다시 실행하세요.
openemail templates send order-shipped \ --from 'Acme <[email protected]>' \ --to [email protected] \ --template-version 5 \ --props '{"orderId":"AC-4192","customer":"Ada"}' \ --idempotency-key order-shipped:AC-4192 \ --dry-run규칙을 실행하기 전에 시험하기
규칙을 꺼진 상태로 만들고, 최근 메일로 시험 실행한 뒤, 의도한 것을 잡으면 켜세요. 브라우저 로그인에서는 rules create와 rules update가 스크립트는 입력할 수 없는 인증 코드를 요구하므로, 먼저 openemail verify를 실행하세요. 이후 60분 동안 그 프로필은 묻지 않고 실행합니다.
[ { "field": "from_domain", "op": "equals", "value": "stripe.com" }, { "field": "has_attachment", "op": "equals", "value": "true" }][ { "type": "label", "value": "USER_RECEIPTS" }, { "type": "archive" }]openemail verifyrule=$(openemail rules create --name 'Stripe receipts' \ --conditions @conditions.json --actions @actions.json --no-enabled --json | jq -r .id)openemail rules test "$rule" --days 30 --limit 100openemail rules update "$rule" --enabledrules test의 일치 결과보다 경고를 먼저 읽으세요. field_unevaluable은 조건이 저장된 메일에 더 이상 없는 무언가를 읽어서 테스트가 판단하지 못했다는 뜻이고, forward_unverified는 전달 대상이 여기서 호스팅되지 않는다는 뜻입니다. wouldApply는 규칙이 선언한 것을 나열합니다. 확인하지 않은 주소로의 전달은 실제 메일이 도착하면 여전히 실패합니다.
규칙을 맨 앞에 두고, 메시지가 왜 옮겨졌는지 보기
rules reorder는 메일함의 모든 규칙을 정확히 한 번씩 받습니다. 빠지거나 두 번 적힌 규칙이 있으면 거부되고 아무것도 움직이지 않습니다. rules list는 실행 순서대로 ID를 반환하므로, 맨 앞에 둘 규칙을 나머지 앞에 놓으세요.
first=rul_4f1c9a2b7d3e8f6a0b5c1d2eopenemail rules reorder "$first" $(openemail rules list --all --ndjson \ | jq -r --arg first "$first" 'select(.id != $first) | .id')openemail rules list-runs --thread-id CAHk7pQ2x9LmZ4 --json | jq '.items[] | {ruleName, actions, failures}'list-runs는 실제로 일어난 일의 기록입니다. 각 행은 규칙 하나가 메시지 하나에 일치한 것으로, 효과가 있었던 동작과 failures에는 메일함이 거절한 동작이 있습니다. 예를 들어 그날 이미 답장한 보낸 사람에게 하는 답장입니다. 각 행은 그 시점의 규칙 이름을 보관하므로, 이후 삭제한 규칙에도 --rule-id가 동작합니다.
웹훅을 등록하고 동작 증명하기
webhooks create는 서명 시크릿을 한 번 보여 주며, 이후 어떤 명령도 다시 보여 주지 않습니다. --json을 주면 시크릿은 stdout의 JSON에 들어 있고, 저장하라는 안내는 stderr로 가므로 출력은 여전히 파싱됩니다. webhooks test는 엔드포인트가 무엇을 구독하든 서명된 합성 email.sent 이벤트를 보내며, 메일은 보내지 않습니다.
openemail verifyopenemail webhooks create --url https://hooks.acme.com/openemail \ --event-types email.received,email.bounced,email.complained \ --description 'Support desk sync' --json > endpoint.jsonjq -r .secret endpoint.jsonopenemail webhooks test "$(jq -r .id endpoint.json)" --json | jq .deliveryrm endpoint.json파일을 지우기 전에 시크릿을 시크릿 저장소에 넣으세요. test는 서버가 실패해도 코드 0으로 종료하므로 delivery.status를 읽으세요. 2xx 응답이면 delivered, 그 밖에는 리디렉션을 포함해 모두 failed입니다. 리디렉션은 절대 따라가지 않기 때문입니다. responseCode가 null이면 응답이 아예 오지 않은 것입니다.
실패한 전달을 찾아 다시 보내기
자체 서버에 장애가 있었다면, 모든 엔드포인트에서 실패한 것을 나열하고, 재전송이 받아들여질지 확인한 뒤 이벤트를 다시 보내세요. 재전송은 같은 이벤트 ID를 담으므로, 이미 처리한 ID를 버리는 수신기는 이를 이미 아는 이벤트로 취급합니다.
openemail webhooks list-workspace-deliveries --status failed --since 2026-09-26T00:00:00Z --all --ndjson \ | jq -r '[.endpointId, .id, .eventType, (.responseCode // "no answer")] | @tsv'openemail webhooks get-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28 --json | jq .replayRefusalopenemail webhooks replay-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28--since와--until은 ISO 8601 시각을 받습니다.nextAttemptAt에 시각이 있는 실패 행에는 아직 자동 재시도가 남아 있습니다.- 재전송이 나갈 수 있으면
replayRefusal은null이고, 그렇지 않으면 엔드포인트가 꺼져 있을 때의webhook_disabled처럼 거부될 이유를 알려 줍니다. - 재전송은 이벤트 하나씩 합니다. 실패한 전달을 모두 다시 보내는 명령은 없습니다.
인증 코드
브라우저 로그인에서는 이 명령 중 네 개가 웹 앱처럼 무엇이든 바꾸기 전에 인증 코드를 요구합니다. rules create, rules update, webhooks create, webhooks update입니다. API 키는 요구받지 않습니다. 이 페이지의 다른 모든 명령은 삭제와 webhooks rotate-secret을 포함해 코드 없이 실행됩니다.
- 터미널에서는 CLI가 여섯 자리 코드를 이메일로 보내거나, 2단계 로그인이 켜져 있으면 인증 앱의 코드를 요구한 뒤 명령을 한 번 실행합니다.
--json이나--no-input을 주었거나, CI이거나, 터미널이 없는 무인 실행에서는 아무도 코드를 입력할 수 없으므로 명령이 종료 코드4로 멈추고 아무것도 바꾸지 않습니다. 먼저openemail verify를 실행하면 그 프로필은 60분 동안 코드가 필요 없습니다.--yes는 삭제를 확인하지만 코드를 건너뛰지는 않습니다.
확인과 드라이 런
여기의 일곱 명령은 무언가를 제거하거나 덮어쓰므로 먼저 확인을 요청합니다. templates delete, templates delete-version, templates replace-content, templates restore-version, rules delete, webhooks delete, webhooks rotate-secret입니다. 무인 실행에서는 --yes를 주지 않으면 각각 종료 코드 2로 멈춥니다.
$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --no-input✗ Refusing to run unattended. Pass --yes to confirm.$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --yes--dry-run은 무언가를 바꿀 첫 요청을 출력하고, 보내거나 확인을 묻지 않고 코드 0으로 종료합니다. --json을 주면 { dryRun, request } 문서 하나를 출력합니다. rules test, templates render, templates preview는 아무것도 바꾸지 않지만 POST 요청이므로, 드라이 런은 이를 실행하는 대신 출력합니다.
페이지 넘기기
templates list,templates list-versions,rules list,rules list-runs, 그리고 모든webhooks list…명령은 한 번에 한 페이지를 읽으며,--limit로 최대 100개를 요청하지 않으면 25행입니다. 터미널은 다음 페이지를 위해 넘길--cursor를 보여 줍니다.--all은 모든 페이지를 읽고,--max <n>은 그만큼 읽고 멈추며,--ndjson은 한 줄에 JSON 객체 하나를 출력합니다.--json을 주면 목록은--all일 때도{ items, hasMore, nextCursor }문서 하나를 출력합니다.- 커서는 받을 때와 같은 필터와 정렬로 돌려주세요. 그 밖의 것은
invalid_cursor와 종료 코드7로 거부됩니다. templates list-sends는 대신--page와--page-size로 번호로 페이지를 넘기고,total을 보고하며,--all이 없습니다. 메일이 나가는 동안 페이지 번호가 밀리므로, 깊이 넘기기보다--days나--minutes로 기간을 좁히세요.templates list-starters와templates list-fonts는 전체 목록을 한 번에 반환하며,rules reorder는 모든 규칙을 새 순서의 일반 목록으로 반환합니다.- 메일함에는 규칙을 최대 100개까지 둘 수 있으므로,
rules list --limit 100은 항상 모든 규칙을 한 페이지로 반환합니다.
다시 볼 만한 플래그
--template-version은 본문 필드version인데,--version이 CLI 버전을 출력하기 때문에 이름이 바뀌었습니다.get-version,restore-version,delete-version의<version>인수는tplv_ID가 아니라 버전 번호입니다.--conditions,--actions,--document,--slots,--props를 비롯한 JSON 플래그는 JSON을 인라인,@path로 파일에서,-로 stdin에서 받습니다.--data는 본문 전체를 같은 방식으로 받으며, 함께 준 플래그는 해당 키를 덮어씁니다.--html은 파일이 아니라 마크업 자체를 받으므로,--html @page.html은@page.html이라는 텍스트를 보냅니다.--html "$(cat page.html)"을 넘기거나,--data에 주는 파일에html을 넣으세요.rules update --conditions와--actions는 목록 전체를 바꾸며,webhooks update --event-types,--address-allowlist,--domain-allowlist도 마찬가지입니다. 현재 값을 읽고, 바꾼 뒤, 전부 보내세요.- 빈
--event-types는 사용 오류입니다. 엔드포인트를 기본 집합으로 되돌리려면--data '{"eventTypes":[]}'를 보내고, 전달을 멈추려면--no-enabled를 넘기세요. templates update,replace-content,restore-version의--expected-version은 읽어 둔 head 버전을 받습니다. 그 사이 다른 사람이 head를 옮겼다면 명령은 종료 코드6과version_conflict로 멈추고 아무것도 쓰지 않습니다.rules update <id> --no-enabled는 규칙을 끄면서 순서의 자리는 유지하므로, 규칙을 삭제하지 않고 일시 중지하는 방법입니다.