> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.claw-ops.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.claw-ops.com/_mcp/server.

# 이메일 발송

POST https://api.claw-ops.com/v1/accounts/{accountId}/emails
Content-Type: application/json

이메일 한 통을 발송합니다. `from` 은 **이 계정에 등록되고 검증(`verified`)된 도메인의
주소**여야 합니다 — 도메인 등록·검증은 「이메일 발신 도메인」 API 로 먼저 마치십시오.

### 기본 사용법

```json
{
  "from": "no-reply@example.com",
  "to": ["customer@gmail.com"],
  "subject": "주문이 접수되었습니다",
  "body": "주문번호 1024 접수되었습니다."
}
```

### 첨부

첨부는 **URL 로 전달합니다.** 파일 바이트를 요청 본문에 실을 수 없습니다.

```json
{
  "from": "no-reply@example.com",
  "to": ["customer@gmail.com"],
  "subject": "영수증",
  "body": "영수증을 첨부합니다.",
  "attachmentUrl": ["https://files.example.com/receipt-1024.pdf"]
}
```

우리가 그 URL 을 **서버에서 내려받아** 메일에 붙입니다. 그래서 URL 은 인증 없이
접근 가능한 공개 주소여야 하고, 사설 대역(`10.*`·`192.168.*`·`127.*`)이나 클라우드
메타데이터 주소를 가리키면 `400 attachment_url_not_allowed` 로 거절합니다.

### 수신자와 크기 상한

* **수신자는 to·cc·bcc 를 합쳐 최대 50명**입니다(공급자 제약, 조정 불가).
  넘으면 `400 too_many_recipients`.
* **메시지 전체는 5MB** 입니다. ⚠️ 이 값은 **첨부를 base64 로 인코딩한 뒤** 기준이라
  원본 파일 크기의 약 1.37배로 계산됩니다 — 즉 **원본 파일 기준 약 3.6MB** 까지입니다.
  넘으면 `400 message_too_large`.

  대부분의 거래 메일(영수증·주문확인·계약서 PDF)은 이 안에 들어갑니다. 더 큰 파일은
  첨부 대신 **링크로 보내 주세요** — 받는 사람의 메일 서버가 큰 첨부를 거부하는 경우가
  많아(Gmail 25MB · Outlook 20MB 대) 반송으로 돌아올 가능성이 높습니다.

### 과금 단위는 "통"이 아니라 "수신자"입니다

⚠️ 한 통을 3명에게 보내면 **3건으로 과금**됩니다. to·cc·bcc 를 가리지 않습니다.
첨부가 있으면 전송 데이터량도 함께 과금됩니다.

### 발송 결과

이 API 의 `201` 은 **공급자가 접수했다**는 뜻이지 수신함에 도착했다는 뜻이 아닙니다.
실제 전달·반송·수신거부는 비동기로 확인되며, 그 결과를 보는 API 는 아직 제공하지
않습니다.

Reference: https://docs.claw-ops.com/api-reference/claw-ops-api/emails/send-email

## Authentication

- `Authorization` header (bearer token, required) — API Key를 Bearer 토큰으로 전달

## Request

### Path parameters

- `accountId` (string, required) — 계정 ID

### Body (application/json)

This endpoint expects an object.

- `from` (string, required) — 발신 주소. **이 계정에서 검증된 도메인**의 주소여야 합니다. 검증되지 않았거나 남의 계정 도메인이면 `403 email_domain_not_verified` 입니다. 도메인이 `pending` 인 동안에도 발신할 수 없습니다 — DNS 레코드를 심고 검증 API 로 `verified` 를 만든 뒤에 쓰십시오.
- `to` (list of string, required) — 수신 주소. `cc`·`bcc` 와 **합쳐** 50명을 넘을 수 없습니다.
- `subject` (string, required) — 제목. 한글 등 비ASCII 문자를 그대로 쓸 수 있습니다.
- `cc` (list of string, optional) — 참조. 수신자 수에 포함됩니다.
- `bcc` (list of string, optional) — 숨은 참조. 수신자 수에 **포함되며 과금됩니다.** 다른 수신자에게는 보이지 않지만 발송 이력에는 남습니다.
- `body` (string, optional) — 본문(평문). `html` 과 **둘 중 하나 이상**은 있어야 합니다 — 둘 다 없으면 `400 body_required` 입니다. 둘 다 주면 수신자의 메일 클라이언트가 표시 가능한 쪽을 고릅니다. HTML 만 보내는 것보다 평문을 함께 주는 편이 스팸 판정에 유리합니다.
- `html` (string, optional) — 본문(HTML). `body` 와 둘 중 하나 이상 필요합니다.
- `attachmentUrl` (list of string, optional) — 첨부 파일 URL. 우리가 **서버에서 내려받아** 메일에 붙입니다. - `https` 만 받습니다. 인증 없이 접근 가능해야 합니다. - 사설 대역·루프백·클라우드 메타데이터 주소는 `400 attachment_url_not_allowed`. - 내려받지 못하면(404·타임아웃 등) `400 attachment_fetch_failed` 이고 **메일은 발송되지 않습니다.** - 파일명은 URL 경로에서 가져옵니다. ⚠️ 공급자가 거부하는 확장자가 있습니다(실행 파일 등). 그 거부는 발송 시점에 일어나므로 `502` 로 돌아옵니다 — 요청 검증 단계에서는 걸러지지 않습니다.
- `replyTo` (list of string, optional) — 답장 주소(`Reply-To`). 받는 사람이 답장하면 `from` 이 아니라 이 주소로 갑니다. `no-reply@` 로 보내고 `support@` 로 받는 구성에 씁니다. ⚠️ **스레드와는 무관합니다.** 대화를 잇는 것은 아래 두 필드입니다.
- `inReplyTo` (string, optional) — 답장하는 원본 메일의 `Message-ID`. 조회 응답이나 `email.received` webhook 의 `messageId` 를 그대로 넣으십시오. 꺾쇠(`< >`)는 있어도 없어도 됩니다. ⚠️ 이것만으로는 부족합니다 — 2회차 이후의 답장은 `references` 도 함께 채워야 여러 클라이언트에서 한 대화로 묶입니다. ⛔ **헤더가 완벽해도 제목이 다르면 Gmail·Outlook 은 새 대화로 엽니다.** 두 클라이언트는 정규화한 제목을 함께 봅니다. 원본 제목에 `Re: ` 를 붙인 형태를 그대로 쓰시고, 이미 `Re:` 로 시작하면 덧붙이지 마십시오.
- `references` (list of string, optional) — 대화의 조상 사슬. **오래된 것이 앞**입니다. 원본 메일 `m` 에 답장한다면 `[...m.references, m.messageId]` 입니다. ⚠️ 긴 대화는 공급자 헤더 길이 상한(986자) 때문에 **RFC 5537 §3.4.4 에 따라 줄여서** 보냅니다 — 첫 항목과 최근 항목은 남고 가운데가 빠집니다. 발송은 실패하지 않으며, **응답의 `references` 가 실제로 나간 목록**입니다.
- `idempotencyKey` (string, optional) — 발송 멱등키. 같은 계정에서 같은 키로 다시 요청하면 **발송하지 않고** 1회차 결과를 그대로 돌려줍니다. 재시도 경로가 있는 호출자만 채우십시오. 미지정이면 매번 발송합니다. 빈 문자열은 `400` 입니다 — 템플릿에서 빈 값이 들어가 멱등이 조용히 꺼진 채 중복 발송되는 걸 막습니다. ⚠️ 순차 재시도를 막는 용도입니다. 같은 키로 **동시에** 두 요청이 들어오면 둘 다 발송될 수 있습니다. ⚠️ **본문이 달라도 검사하지 않습니다.** 같은 키로 다른 내용을 보내면 새 메일은 나가지 않고 1회차 결과가 돌아옵니다. 키는 *메일 한 통* 단위로 만드십시오 (`order-1024-접수` 처럼). 만료도 없습니다.

## Response

### 201

발송 접수 성공. 공급자가 받았다는 뜻이며 수신함 도착을 보장하지 않습니다.

- `emailId` (string, required) — ClawOps 이메일 리소스 ID.
- `direction` (enum, required) — `outbound` 는 이 계정이 보낸 메일, `inbound` 는 이 계정의 **수신 도메인으로 들어온** 메일입니다.
  - Allowed values: `outbound`, `inbound`
- `status` (enum, required) — 보낸 메일은 `sent`(공급자 접수) 또는 `failed` 입니다 — `sent` 는 **접수됐다**는 뜻이지 수신함 도착이 아닙니다. 받은 메일은 항상 `received` 입니다.
  - Allowed values: `sent`, `failed`, `received`
- `from` (string, required)
- `to` (list of string, required)
- `recipientCount` (integer, required) — 보낸 메일에서는 **과금 단위**입니다 — 실제로 발송한 To+Cc+Bcc 의 합계이며 한 통을 3명에게 보내면 3입니다. **수신거부로 빠진 주소는 세지 않습니다**(`suppressed` 참고). 받은 메일에서는 **이 계정에 실제로 배달된 주소 수**(`recipients` 의 길이)입니다.
- `createdAt` (datetime, required)
- `cc` (list of string, optional)
- `bcc` (list of string, optional)
- `suppressed` (list of string, optional) — 수신거부 명단에 있어 이 발송에서 **빠진** 주소들. 요청한 표기 그대로 돌려줍니다. ⚠️ 이메일은 차단된 수신자가 있어도 **나머지에게는 보냅니다**(전화·문자가 발신 전체를 거절하는 것과 다릅니다). 그래서 `201` 을 받고도 일부가 안 갔을 수 있고, **이 칸이 그걸 알려주는 유일한 자리**입니다. 비어 있으면 아무도 빠지지 않았다는 뜻입니다. `recipientCount` 에는 포함되지 않습니다 — 요청한 수신자 수는 `recipientCount + suppressed.length` 입니다. 받은 메일에서는 항상 빈 배열입니다.
- `subject` (string, optional, nullable)
- `attachmentCount` (integer, optional)
- `failureCode` (string, optional, nullable) — 실패 사유 코드. `status` 가 `sent` 면 `null` 입니다. `provider_unavailable` 은 **공급자를 부르지 못한** 경우이고, 그 밖의 값은 공급자가 거절한 경우입니다.
- `failureMessage` (string, optional, nullable) — 사람이 읽는 실패 설명. `status` 가 `sent` 면 `null` 입니다.
- `replyTo` (list of string, optional) — 답장 주소(`Reply-To`). 받은 메일은 발신자가 지정한 주소, 보낸 메일은 발송할 때 지정한 값입니다. ⭐ **답장은 `from` 이 아니라 이 주소로 보내십시오.** 비어 있을 때만 `from` 이 답장 주소입니다(RFC 5322 §3.6.2). 뉴스레터·티켓 시스템은 대부분 이 값을 다르게 둡니다.
- `messageId` (string, optional, nullable) — 이 메일 자신의 `Message-ID`(꺾쇠 `< >` 제외). **답장할 때 `inReplyTo` 에 그대로 넣는 값입니다.** ⛔ **보낸 메일은 항상 `null`** 입니다. 이 헤더는 공급자가 발급하며 우리가 지정한 값을 덮어쓰기 때문에 우리도 그 값을 모릅니다. ⚠️ 받은 메일도 `null` 일 수 있습니다 — 이 헤더는 RFC 상 필수가 아니고, 붙이지 않고 보내는 발신자가 실제로 있습니다. 그 메일에는 스레드로 답장할 수 없습니다.
- `inReplyTo` (string, optional, nullable) — 이 메일이 답장한 원본의 `Message-ID`(꺾쇠 제외). 답장이 아니면 `null` 입니다.
- `references` (list of string, optional) — 대화의 조상 사슬. **오래된 것이 앞**입니다. 다음 답장에 넘길 값은 `[...references, messageId]` 입니다. ⚠️ 보낸 메일의 값은 요청하신 목록이 아니라 **실제로 헤더에 실어 보낸 목록**입니다. 긴 대화는 공급자 헤더 길이 상한 때문에 RFC 5537 §3.4.4 에 따라 첫 항목과 최근 항목만 남기고 줄여 보냅니다.
- `recipients` (list of string, optional, nullable) — **실제로 이 계정에 배달된 주소.** 받은 메일에만 있습니다(보낸 메일은 `null`). ⚠️ **`to` 를 보고 라우팅하지 마십시오.** 숨은 참조(BCC)로 온 메일은 `to` 에 그 주소가 **없습니다** — 이 필드에만 있습니다. 한 도메인에 `support@`·`sales@` 처럼 여러 주소를 두고 처리를 나눈다면 반드시 이 값을 쓰십시오.
- `receivedAt` (datetime, optional, nullable) — 공급자가 메일을 받은 시각. 받은 메일에만 있습니다.
- `sizeBytes` (integer, optional, nullable) — 받은 메일의 원문 크기(바이트). ⚠️ 공급자가 덧붙인 헤더(약 3.6KB)가 포함된 값이라 메일 클라이언트가 보여 주는 크기와 다를 수 있습니다. **과금은 이 값 기준입니다.**
- `verdicts` (EmailResponseVerdicts, optional, nullable) — 공급자의 스팸·바이러스·인증 판정. 받은 메일에만 있습니다. ⚠️ **값은 공급자 원문 그대로**입니다(`PASS`·`FAIL`·`GRAY`·`PROCESSING_FAILED` 등). 우리가 해석하지 않으므로 목록이 늘어날 수 있습니다 — 모르는 값을 만나면 안전한 쪽으로 처리하십시오. ⛔ **`from` 만 믿고 자동 처리하지 마십시오.** 발신자는 위조될 수 있고, 그걸 판별하라고 `spf`·`dkim`·`dmarc` 를 함께 드립니다.
- `parseStatus` (enum, optional, nullable) — 받은 메일의 본문 해석 결과. `unparsable` 이면 본문을 읽지 못한 것이며, 그때도 원문은 그대로 보관되어 단건 조회의 `raw` 로 받을 수 있습니다.
  - Allowed values: `parsed`, `unparsable`

## Errors

### 400 Bad Request Error

잘못된 요청. `code` 로 구분합니다. | code | 뜻 | |---|---| | `invalid_email_address` | from·to·cc·bcc·replyTo 형식이 이메일이 아님 | | `body_required` | `body` 와 `html` 이 둘 다 없음 | | `too_many_recipients` | to+cc+bcc 합계가 50명 초과 | | `message_too_large` | 본문+첨부가 base64 인코딩 후 5MB 초과(원본 약 3.6MB) | | `attachment_url_not_allowed` | 첨부 URL 이 사설 대역·메타데이터·비 https | | `attachment_fetch_failed` | 첨부를 내려받지 못함(404·타임아웃 등) | | `invalid_input` | `to` 가 비었거나, 첨부가 10개를 넘거나, `idempotencyKey` 가 빈 문자열, 또는 `inReplyTo`·`references` 가 Message-ID 형식이 아님 |

- `error` (string, optional) — 사람이 읽을 수 있는 에러 메시지
- `code` (string, optional) — 기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서 바뀔 수 있지만 코드는 계약입니다. 같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는 - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서 해제해야 발신됩니다. - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다. 전화번호 정규화 관련: - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님) - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)
- `ip` (string, optional) — `overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다. 해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔 「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.

### 401 Unauthorized Error

인증 실패

- `error` (string, optional) — 사람이 읽을 수 있는 에러 메시지
- `code` (string, optional) — 기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서 바뀔 수 있지만 코드는 계약입니다. 같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는 - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서 해제해야 발신됩니다. - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다. 전화번호 정규화 관련: - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님) - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)
- `ip` (string, optional) — `overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다. 해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔 「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.

### 403 Forbidden Error

| code | 의미 | | --- | --- | | `email_domain_not_verified` | `from` 도메인이 이 계정에서 검증된 발신 도메인이 아님 | | `no_active_subscription` | 활성 구독이 없음 | | `email_blocked` | 이메일 발송이 제한된 계정(고객센터 문의) | | `email_sending_paused` | 반송·스팸신고 비율이 높아 발송이 정지됨(고객센터 문의) | 접근 권한이 없을 때도 403 입니다.

- `error` (string, optional) — 사람이 읽을 수 있는 에러 메시지
- `code` (string, optional) — 기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서 바뀔 수 있지만 코드는 계약입니다. 같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는 - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서 해제해야 발신됩니다. - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다. 전화번호 정규화 관련: - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님) - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)
- `ip` (string, optional) — `overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다. 해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔 「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.

### 422 Unprocessable Entity Error

| code | 의미 | | --- | --- | | `plan_not_supported` | 이메일을 사용할 수 없는 플랜(유료 플랜으로 변경 필요) | | `daily_quota_exceeded` | **오늘**(KST 자정 기준) 발송 한도 초과 | | `quota_exceeded` | **이번 청구월** 발송 한도 초과 | | `override_quota_exceeded` | 계정에 별도 한도가 걸려 있고 그 한도를 초과 | | `recipient_blocked` | 수신자가 **전원** 수신거부 명단에 있어 보낼 대상이 없음 | 한도는 **수신자 수**로 셉니다 — 발송 요청 수가 아닙니다. 한 번의 요청에 수신자를 50명까지 넣을 수 있어, 요청 수로 세면 한도가 50배로 헐거워지기 때문입니다. 한도는 **월과 일 두 축**이고 플랜마다 다릅니다. 두 축인 이유는 월 한도만으로는 하루에 몰아 보내는 것을 막을 수 없기 때문입니다 — 메일 서비스의 평판은 총량보다 그 순간의 발송량에 훨씬 민감합니다. 오류 메시지에 `사용량+이번 요청/한도` 가 함께 담기므로 어느 축에 얼마나 걸렸는지 바로 알 수 있습니다. 한도를 올려야 하면 고객센터로 문의해 주세요. 계정별로 조정할 수 있고, 조정된 계정은 플랜 한도 대신 그 값이 적용됩니다(그때는 `override_quota_exceeded` 가 납니다). ⚠️ `recipient_blocked` 는 **전원이 걸렸을 때만** 납니다. 일부만 명단에 있으면 그 사람만 빼고 나머지에게 발송하며(`201`), 빠진 주소는 응답의 `suppressed` 에 담깁니다 — **`2xx` 를 받아도 그 칸을 확인해야 누가 안 갔는지 알 수 있습니다.**

- `error` (string, optional) — 사람이 읽을 수 있는 에러 메시지
- `code` (string, optional) — 기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서 바뀔 수 있지만 코드는 계약입니다. 같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는 - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서 해제해야 발신됩니다. - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다. 전화번호 정규화 관련: - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님) - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)
- `ip` (string, optional) — `overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다. 해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔 「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.

### 502 Bad Gateway Error

공급자 요청 실패(영구). 같은 요청을 다시 보내도 같은 결과입니다.

- `error` (string, optional) — 사람이 읽을 수 있는 에러 메시지
- `code` (string, optional) — 기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서 바뀔 수 있지만 코드는 계약입니다. 같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는 - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서 해제해야 발신됩니다. - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다. 전화번호 정규화 관련: - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님) - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)
- `ip` (string, optional) — `overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다. 해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔 「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.

### 503 Service Unavailable Error

공급자에 일시적으로 연결할 수 없음, 또는 이메일 발송이 아직 설정되지 않음. 잠시 후 다시 시도하십시오.

- `error` (string, optional) — 사람이 읽을 수 있는 에러 메시지
- `code` (string, optional) — 기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서 바뀔 수 있지만 코드는 계약입니다. 같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는 - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서 해제해야 발신됩니다. - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다. 전화번호 정규화 관련: - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님) - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)
- `ip` (string, optional) — `overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다. 해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔 「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.

## Types

### EmailResponseVerdicts

공급자의 스팸·바이러스·인증 판정. 받은 메일에만 있습니다. ⚠️ **값은 공급자 원문 그대로**입니다(`PASS`·`FAIL`·`GRAY`·`PROCESSING_FAILED` 등). 우리가 해석하지 않으므로 목록이 늘어날 수 있습니다 — 모르는 값을 만나면 안전한 쪽으로 처리하십시오. ⛔ **`from` 만 믿고 자동 처리하지 마십시오.** 발신자는 위조될 수 있고, 그걸 판별하라고 `spf`·`dkim`·`dmarc` 를 함께 드립니다.

- `spam` (string, optional, nullable)
- `virus` (string, optional, nullable)
- `spf` (string, optional, nullable)
- `dkim` (string, optional, nullable)
- `dmarc` (string, optional, nullable)

## Examples

**Request**

```json
{
  "from": "no-reply@example.com",
  "to": [
    "customer@gmail.com"
  ],
  "subject": "주문이 접수되었습니다",
  "body": "주문번호 1024 접수되었습니다.",
  "attachmentUrl": [
    "https://files.example.com/receipt-1024.pdf"
  ],
  "idempotencyKey": "order-1024-receipt"
}
```

**Response**

```json
{
  "emailId": "clx9eml00001",
  "direction": "inbound",
  "status": "sent",
  "from": "no-reply@example.com",
  "to": [
    "customer@gmail.com"
  ],
  "recipientCount": 1,
  "createdAt": "2026-09-03T07:20:00.000Z",
  "cc": [],
  "bcc": [],
  "suppressed": [],
  "subject": "주문이 접수되었습니다",
  "attachmentCount": 1,
  "failureCode": null,
  "failureMessage": null,
  "replyTo": [
    "support@example.com"
  ],
  "messageId": "CAF7n8xW1p_abc@mail.gmail.com",
  "inReplyTo": null,
  "references": [
    "CAF7n8xW1p_xyz@mail.gmail.com",
    "CAF7n8xW1p_abc@mail.gmail.com"
  ],
  "recipients": [
    "support@example.com"
  ],
  "receivedAt": "2026-09-04T05:48:00.000Z",
  "sizeBytes": 4030,
  "verdicts": {
    "spam": "PASS",
    "virus": "PASS",
    "spf": "PASS",
    "dkim": "PASS",
    "dmarc": "GRAY"
  },
  "parseStatus": "parsed"
}
```

**SDK Code**

```python Emails_sendEmail_example
import requests

url = "https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails"

payload = {
    "from": "no-reply@example.com",
    "to": ["customer@gmail.com"],
    "subject": "주문이 접수되었습니다",
    "body": "주문번호 1024 접수되었습니다.",
    "attachmentUrl": ["https://files.example.com/receipt-1024.pdf"],
    "idempotencyKey": "order-1024-receipt"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Emails_sendEmail_example
const url = 'https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"from":"no-reply@example.com","to":["customer@gmail.com"],"subject":"주문이 접수되었습니다","body":"주문번호 1024 접수되었습니다.","attachmentUrl":["https://files.example.com/receipt-1024.pdf"],"idempotencyKey":"order-1024-receipt"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Emails_sendEmail_example
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails"

	payload := strings.NewReader("{\n  \"from\": \"no-reply@example.com\",\n  \"to\": [\n    \"customer@gmail.com\"\n  ],\n  \"subject\": \"주문이 접수되었습니다\",\n  \"body\": \"주문번호 1024 접수되었습니다.\",\n  \"attachmentUrl\": [\n    \"https://files.example.com/receipt-1024.pdf\"\n  ],\n  \"idempotencyKey\": \"order-1024-receipt\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Emails_sendEmail_example
require 'uri'
require 'net/http'

url = URI("https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"from\": \"no-reply@example.com\",\n  \"to\": [\n    \"customer@gmail.com\"\n  ],\n  \"subject\": \"주문이 접수되었습니다\",\n  \"body\": \"주문번호 1024 접수되었습니다.\",\n  \"attachmentUrl\": [\n    \"https://files.example.com/receipt-1024.pdf\"\n  ],\n  \"idempotencyKey\": \"order-1024-receipt\"\n}"

response = http.request(request)
puts response.read_body
```

```java Emails_sendEmail_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"from\": \"no-reply@example.com\",\n  \"to\": [\n    \"customer@gmail.com\"\n  ],\n  \"subject\": \"주문이 접수되었습니다\",\n  \"body\": \"주문번호 1024 접수되었습니다.\",\n  \"attachmentUrl\": [\n    \"https://files.example.com/receipt-1024.pdf\"\n  ],\n  \"idempotencyKey\": \"order-1024-receipt\"\n}")
  .asString();
```

```php Emails_sendEmail_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails', [
  'body' => '{
  "from": "no-reply@example.com",
  "to": [
    "customer@gmail.com"
  ],
  "subject": "주문이 접수되었습니다",
  "body": "주문번호 1024 접수되었습니다.",
  "attachmentUrl": [
    "https://files.example.com/receipt-1024.pdf"
  ],
  "idempotencyKey": "order-1024-receipt"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Emails_sendEmail_example
using RestSharp;

var client = new RestClient("https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"from\": \"no-reply@example.com\",\n  \"to\": [\n    \"customer@gmail.com\"\n  ],\n  \"subject\": \"주문이 접수되었습니다\",\n  \"body\": \"주문번호 1024 접수되었습니다.\",\n  \"attachmentUrl\": [\n    \"https://files.example.com/receipt-1024.pdf\"\n  ],\n  \"idempotencyKey\": \"order-1024-receipt\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Emails_sendEmail_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "from": "no-reply@example.com",
  "to": ["customer@gmail.com"],
  "subject": "주문이 접수되었습니다",
  "body": "주문번호 1024 접수되었습니다.",
  "attachmentUrl": ["https://files.example.com/receipt-1024.pdf"],
  "idempotencyKey": "order-1024-receipt"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/emails")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```