SMTP 및 HTTP API
API 키를 생성하고 SMTP 또는 HTTP API로 메일을 보냅니다. 요청 필드, 응답, 오류 코드, 예제를 포함합니다.
스트림을 통해 발송하는 방법은 두 가지입니다. 모든 애플리케이션이나 메일 라이브러리에서 SMTP로 보내거나 HTTP API를 사용합니다. 두 방법 모두 같은 API 키, 할당량, 억제 목록, 이벤트를 사용합니다. 시작하기 전에 발신 도메인을 인증하고 API 키를 생성하십시오.
API 키
- 스트림을 열고 API 키 탭으로 이동합니다.
- 키 생성을 클릭하고
production과 같은 이름을 입력한 뒤 확인합니다. - 표시된 키를 복사하고 저장했습니다를 클릭합니다. 키는 이때 한 번만 표시됩니다. 이후 목록에는
ytm_aB3dE6gH…와 같은 접두사만 표시됩니다.
모든 키는 ytm_로 시작합니다. 스트림당 최대 20개의 활성 키를 가질 수 있습니다. 목록에는 각 키를 마지막으로 사용한 시각이 표시됩니다. 키를 더 이상 사용하지 않으려면 폐기를 클릭합니다. 해당 키를 사용하는 프로그램은 즉시 작동을 멈춥니다. 키는 해당 키가 속한 스트림에서만 작동합니다.
API 키는 서버에만 보관하십시오. 브라우저 코드, 모바일 앱, 공개 저장소에 넣지 마십시오. 키를 가진 사람은 누구나 고객의 도메인으로 메일을 보낼 수 있습니다.
SMTP
스트림의 개요 탭에 SMTP 설정이 표시됩니다.
| 설정 | 값 |
|---|---|
| 호스트 | 스트림에 표시된 SMTP 호스트, 보통 smtp.tm.yeapcloud.net |
| 포트 | STARTTLS 사용 시 587, TLS 사용 시 465 |
| 사용자 이름 | apikey(이 단어 그대로) |
| 비밀번호 | 이 스트림의 API 키 |
| 카테고리 헤더 | X-Yeap-Category |
콘솔에 표시된 호스트를 사용하십시오. 일부 스트림은 자체 호스트 이름을 가진 다른 서버 그룹을 통해 발송합니다.
SMTP 발송 방식:
- From 헤더에는 스트림에서 인증된 도메인의 주소를 사용해야 합니다.
- 각 수신자(To, Cc, Bcc)는 별도의 사본과 별도의 메시지 ID를 받으며, 각각 이메일 1통으로 계산됩니다.
- 카테고리를 지정하려면
X-Yeap-Category: password-reset과 같은 헤더를 추가합니다. 이 헤더는 전달 전에 제거됩니다. 허용되는 문자는 영문자, 숫자,.,_,-이며 최대 64자입니다. - 메시지 크기는 최대 25 MB이며, SMTP 트랜잭션당 최대 1,000명의 수신자를 지정할 수 있습니다.
- 모든 메시지에는
X-TM-ID헤더(발송 기록과 이벤트에 나타나는 메시지 ID)와 메일함 제공업체의 피드백 루프용Feedback-ID헤더가 추가됩니다. - 마케팅 스트림에서는 직접 설정한
List-Unsubscribe헤더가 Yeap Cloud의 원클릭 수신 거부 헤더로 대체됩니다. SMTP는 메시지 본문을 변경하지 않으므로 우편 주소와 수신 거부 링크를 직접 콘텐츠에 추가하십시오. 억제 목록 및 수신 거부를 참조하십시오. - 열람 및 클릭 추적은 HTTP API를 통해서만 사용할 수 있습니다.
Python 표준 라이브러리 예제:
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "Example <noreply@example.com>"
msg["To"] = "user@example.org"
msg["Subject"] = "Reset your password"
msg["X-Yeap-Category"] = "password-reset"
msg.set_content("Click the link to reset your password: https://example.com/reset/abc")
with smtplib.SMTP("smtp.tm.yeapcloud.net", 587) as s:
s.starttls()
s.login("apikey", "ytm_your_api_key")
s.send_message(msg)SMTP 응답 코드
서버가 메시지를 거부하면 응답 코드로 이유를 알 수 있습니다. 4xx 코드는 일시적이므로 나중에 다시 시도하십시오. 5xx 코드는 영구적이므로 원인을 해결한 후 다시 보내십시오.
| 코드 | 메시지 | 원인 및 조치 |
|---|---|---|
535 5.7.8 | Authentication failed / API key revoked | 사용자 이름이나 키가 잘못되었거나 키가 폐기되었습니다. 사용자 이름은 apikey여야 합니다. |
454 4.7.0 | Temporary authentication failure / Too many authentication failures | 서버가 아직 준비되지 않았거나 IP의 로그인 실패 횟수가 너무 많습니다. 잠시 후 다시 시도하십시오. |
451 4.7.1 | Stream is paused | 스트림이 자동으로 일시 중지되었습니다. 스트림에 표시된 사유를 확인하고 고객 지원에 문의하십시오. |
550 5.7.1 | Stream is banned / suspended / terminated | 스트림이 차단되었거나, 구독이 정지되었거나, 해지되었습니다. |
550 5.7.1 | From domain … is not verified for this stream | 도메인을 인증하거나, 도메인이 이 스트림과 공유되거나 연결되어 있는지 확인하십시오. |
550 5.7.1 | Sandbox mode: recipient is not a verified address | 스트림이 샌드박스에 있습니다. 수신자를 인증하거나 본인 도메인으로 보내십시오. |
550 5.7.1 | Recipient is on a suppression list / All recipients are suppressed | 주소가 억제되었습니다(반송, 스팸 신고, 수신 거부 또는 수동). |
452 4.7.1 | Hourly sending limit reached, try later | 시간당 한도(또는 샌드박스 일일 한도)를 모두 사용했습니다. 다음 시간에 다시 시도하십시오. |
550 5.7.1 | Monthly sending quota exhausted | 월간 발송량을 모두 사용했으며 초과 사용이 허용되지 않습니다. 무료 요금제이거나 초과 사용 지출 한도에 도달한 경우입니다. |
452 4.5.3 | Too many recipients | 한 트랜잭션의 수신자가 1,000명을 넘었습니다. |
553 5.1.3 | Invalid recipient address | 수신자 주소 형식이 잘못되었습니다. |
554 5.7.1 | Message rejected: virus detected | 메시지에 바이러스가 포함되어 있었습니다. 스트림이 즉시 차단되고 모든 API 키가 폐기됩니다. |
554 5.7.1 | Message rejected as spam | 콘텐츠가 스팸으로 판정되었습니다. 거부가 반복되면 스트림이 일시 중지됩니다. |
451 4.3.0 | Content scanner unavailable, try again later | 마케팅 메일은 검사 후에만 수락됩니다. 나중에 다시 시도하십시오. 그 밖의 451 4.3.0 응답은 일시적인 서버 오류이므로 다시 시도하십시오. |
554 5.6.0 | Malformed message headers | 메시지 헤더를 수정하십시오. |
HTTP API
| 항목 | 값 |
|---|---|
| 기본 URL | https://api.tm.yeapcloud.net/v1(https://api.yeap.cloud/api/tm/v1로도 사용 가능) |
| 인증 | Authorization: Bearer ytm_your_api_key |
| 형식 | JSON 요청 및 응답 본문, Content-Type: application/json |
| 요청 속도 제한 | API 키당 분당 600회 요청 |
오류가 발생하면 HTTP 상태 코드와 다음과 같은 본문이 반환됩니다.
{"error": {"code": "invalid_request", "message": "subject is required (single line)"}}| HTTP 상태 | error.code | 의미 |
|---|---|---|
| 400 | invalid_json, invalid_request | 본문이 JSON 객체가 아니거나, 배치가 비어 있거나 너무 큽니다 |
| 401 | unauthorized | API 키가 없거나, 형식이 잘못되었거나, 폐기되었거나, 알 수 없는 키입니다 |
| 403 | stream_paused, stream_banned, stream_suspended | 스트림에서 발송할 수 없습니다 |
| 403 | from_domain_unverified, invalid_from | From 도메인이 이 스트림에서 인증되지 않았거나 From 주소가 잘못되었습니다 |
| 403 | postal_address_required | 우편 주소가 없는 마케팅 스트림입니다. 마케팅 설정 탭에서 입력하십시오. |
| 403 | virus | 첨부 파일에 바이러스가 포함되어 있었습니다. 스트림이 차단됩니다. |
| 404 | not_found | 이 스트림에 해당 메시지가 없습니다 |
| 422 | invalid_request | 필드가 누락되었거나 잘못되었습니다. message에 해당 필드가 표시됩니다. |
| 429 | rate_limited | 1분에 600회를 초과하여 요청했습니다. 속도를 줄이고 다시 시도하십시오. |
| 503 | no_node | 현재 사용할 수 있는 발송 서버가 없습니다. 나중에 다시 시도하십시오. |
메시지 발송: POST /v1/send
| 필드 | 유형 | 설명 |
|---|---|---|
from | string | 필수. noreply@example.com 또는 Example <noreply@example.com>과 같은 주소 하나. 도메인은 스트림에서 인증되어 있어야 합니다. |
to | string 또는 array | 필수. 주소 하나, 쉼표로 구분된 문자열 또는 배열. 배열 항목은 {"email": "...", "name": "..."} 객체일 수도 있습니다. |
cc, bcc | string 또는 array | 선택. to와 같은 형식. Bcc 주소는 헤더에 나타나지 않습니다. |
reply_to | string 또는 array | 선택. Reply-To 주소. |
subject | string | 필수. 한 줄, 최대 998자. |
html, text | string | 둘 중 하나 이상 필수. 최상의 결과를 위해 둘 다 보내십시오. |
headers | object | 선택. {"X-Campaign": "spring"}과 같은 추가 헤더. 표준 헤더(From, To, Subject, Reply-To, Message-ID, List-Unsubscribe 등)는 여기서 설정할 수 없습니다. |
attachments | array | 선택. 항목 {"filename": "invoice.pdf", "content_base64": "...", "type": "application/pdf"}. 최대 10개 파일, 총 10 MB. |
category | string | 선택. 영문자, 숫자, ., _, -, 최대 64자. |
tags | array of strings | 선택. 자체 필터링용 태그 최대 10개, 각 최대 64자. |
track_opens, track_clicks | boolean | 선택. 이 메시지에 대해 열람 추적(추적 픽셀)과 클릭 추적(t.tm.yeapcloud.net을 거치도록 링크 재작성)을 켜거나 끕니다. HTML 전용. 생략하면 스트림 기본값이 적용됩니다. 둘 다 기본적으로 꺼져 있습니다. |
variables | object | 선택. subject, html, text의 {{name}} 자리 표시자를 대체합니다. html에서는 값이 HTML 이스케이프됩니다. 알 수 없는 자리 표시자는 그대로 남습니다. |
한 번의 호출에는 to, cc, bcc를 합쳐 최대 50명의 수신자를 지정할 수 있습니다. 각 수신자는 자체 메시지 ID를 가진 별도의 사본을 받습니다. 여러 사람에게 서로 다른 내용을 보내려면 배치 엔드포인트를 사용하십시오.
예제:
curl -X POST https://api.tm.yeapcloud.net/v1/send \
-H "Authorization: Bearer ytm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"from": "Example <noreply@example.com>",
"to": ["alice@example.org"],
"subject": "Your order {{order_id}} has shipped",
"html": "<p>Hi {{name}}, your order is on its way.</p>",
"text": "Hi {{name}}, your order is on its way.",
"category": "shipping",
"tags": ["order"],
"variables": {"name": "Alice", "order_id": "A-1024"}
}'응답:
{
"message_ids": ["01JAQ3Z9V8K4M2N6P7R5S1T0XW"],
"rejected": []
}message_ids에는 수락된 사본이 나열됩니다. rejected에는 발송되지 않은 수신자가 각각 rcpt와 reason과 함께 나열됩니다. 일부 또는 모든 수신자가 거부되어도 호출은 성공할 수 있으므로 항상 rejected를 확인하십시오.
reason | 의미 |
|---|---|
invalid_recipient | 주소가 잘못되었습니다 |
suppressed:<scope>:<reason> | 주소가 억제 목록에 있습니다. 예: suppressed:global:hard_bounce |
sandbox_recipient_not_verified | 스트림이 샌드박스에 있으며 해당 수신자는 허용되지 않습니다 |
rate_limited_hourly, rate_limited_sandbox_or_hourly | 시간당 한도(또는 샌드박스 일일 한도)를 모두 사용했습니다 |
monthly_quota_exceeded | 월간 발송량을 모두 사용했으며 초과 사용이 허용되지 않습니다 |
enqueue_failed | 메시지를 대기열에 넣지 못했습니다. 할당량에서 차감되지 않습니다. 다시 시도하십시오. |
requests를 사용한 Python 예제:
import requests
resp = requests.post(
"https://api.tm.yeapcloud.net/v1/send",
headers={"Authorization": "Bearer ytm_your_api_key"},
json={
"from": "Example <noreply@example.com>",
"to": "alice@example.org",
"subject": "Welcome",
"html": "<p>Welcome aboard!</p>",
"text": "Welcome aboard!",
"category": "welcome",
},
timeout=30,
)
resp.raise_for_status()
result = resp.json()
print(result["message_ids"], result["rejected"])대량 발송: POST /v1/batch
한 번의 요청으로 최대 500개의 메시지를 보냅니다. 각 항목에는 /v1/send와 같은 필드가 있습니다.
{
"messages": [
{"from": "noreply@example.com", "to": "a@example.org", "subject": "Hi A", "text": "Hello A"},
{"from": "noreply@example.com", "to": "b@example.org", "subject": "Hi B", "text": "Hello B"}
]
}응답에는 메시지마다 결과가 하나씩 같은 순서로 포함됩니다.
{
"results": [
{"index": 0, "message_ids": ["01JAQ..."], "rejected": []},
{"index": 1, "error": {"code": "invalid_request", "message": "subject is required (single line)"}, "rejected": []}
]
}메시지 조회: GET /v1/messages
| 쿼리 매개변수 | 설명 |
|---|---|
status | 상태로 필터링: queued, delivered, deferred, bounced, complained, rejected 또는 failed |
to | 수신자 주소로 필터링 |
since | 이 시각 이후(포함)에 생성된 메시지만, 예: 2026-10-01T00:00:00Z |
limit | 페이지당 결과 수, 1–100(기본값 50) |
before | 다음 페이지를 조회하려면 이전 응답의 next_before 값을 전달합니다 |
응답은 {"data": [...], "next_before": "..."}입니다. 마지막 페이지에서는 next_before가 null입니다. 메시지는 30일 동안 보관됩니다.
GET /v1/messages/{id}는 메시지 하나와 해당 이벤트를 반환합니다.
{
"id": "01JAQ3Z9V8K4M2N6P7R5S1T0XW",
"stream_id": 12,
"source": "api",
"to": "alice@example.org",
"from": "Example <noreply@example.com>",
"subject": "Welcome",
"category": "welcome",
"tags": [],
"status": "delivered",
"smtp_code": 250,
"smtp_response": "250 2.0.0 OK",
"opened_at": null,
"clicked_at": null,
"unsubscribed_at": null,
"created_at": "2026-10-20 10:15:02",
"updated_at": "2026-10-20 10:15:04",
"events": [
{"type": "delivered", "at": "2026-10-20 10:15:04", "rcpt": "alice@example.org", "smtp_code": 250, "mx": "mx.example.org"}
]
}억제 목록: /v1/suppressions
이 엔드포인트는 API 키가 속한 스트림의 억제 목록을 관리합니다.
| 요청 | 본문 또는 쿼리 | 응답 |
|---|---|---|
GET /v1/suppressions | 쿼리 email(선택), limit(1–500, 기본값 100), offset | {"data": [{"id", "scope", "email", "reason", "category", "note", "created_at"}]} |
POST /v1/suppressions | {"email": "..."} 또는 {"emails": [...]}(최대 1,000개), 선택적 category | {"added": 2, "invalid": []} |
DELETE /v1/suppressions | {"email": "..."}, 선택적 category(본문 또는 쿼리) | {"removed": 1} |
API로 추가한 주소의 사유는 manual입니다. 계정 전체 및 전역 억제 항목은 API로 변경할 수 없습니다. 억제 목록 및 수신 거부를 참조하십시오.