콘텐츠로 이동

이벤트 유형

웹훅을 만들 때 이 이벤트 유형을 원하는 대로 조합해 구독할 수 있습니다.

이벤트 발생 시점 last_event 설정값
email.scheduled scheduled_at이 있는 이메일이 접수되었습니다. scheduled
email.sent 이메일이 Amazon SES에 전달되었습니다. sent
email.delivered 수신자의 메일 서버가 이메일을 수락했습니다. delivered
email.delivery_delayed 전달이 지연되거나 일시적으로 실패하고 있으며, SES가 계속 재시도합니다. delivery_delayed
email.bounced 영구적인 실패입니다(주소가 존재하지 않거나 도메인이 거부함). bounced
email.complained 수신자가 이메일을 스팸으로 신고했습니다. complained
email.opened 수신자가 이메일을 열었습니다(열람 추적 필요). opened
email.clicked 수신자가 링크를 클릭했습니다(클릭 추적 필요). clicked
email.failed SpacePost 또는 SES가 이메일을 보내지 못했습니다(거부, 렌더링 실패, 일시 중지된 팀 또는 테넌트). failed
email.suppressed 수신자가 발송 제외 목록에 있어 건너뛰었습니다. suppressed

웹훅 본문은 Resend의 형식과 정확히 같으므로 Resend용으로 작성한 핸들러가 그대로 동작합니다.

{
"type": "email.delivered",
"created_at": "2026-10-23T14:02:10.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z",
"broadcast_id": "0192e1c4-0000-7000-8000-0000000000bb",
"tenant_id": "0192e1c4-5555-7000-8000-0000000000cc",
"tags": { "category": "welcome" }
}
}
  • type은 이벤트이고, created_at은 이벤트가 발생한 시각입니다.
  • data.email_id, from, to(영향받은 수신자만이 아니라 이메일의 모든 수신자), subject, created_at(이메일이 생성된 시각)은 항상 포함됩니다.
  • data.broadcast_id는 브로드캐스트 이메일일 때만 포함됩니다.
  • data.tenant_id는 이메일이 테넌트를 위해 발송된 경우(플랫폼 모드)에만 포함됩니다. 사용자의 external_id가 아니라 SpacePost의 테넌트 ID입니다.
  • data.tags는 이메일에 태그가 있을 때만 객체({ "name": "value" })로 포함됩니다.
  • 일부 이벤트는 data에 이벤트 이름을 딴 객체를 하나 더 추가합니다: bounce, click, open, failed, suppressed. 아래에 정리되어 있습니다. 그 외에는 추가되는 것이 없습니다. 최상위 bounceType, delayType, reason 키는 없습니다.
{
"type": "email.scheduled",
"created_at": "2026-10-23T14:02:09.700Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Your trial ends tomorrow",
"created_at": "2026-10-23T14:02:09.512Z"
}
}
{
"type": "email.sent",
"created_at": "2026-10-23T14:02:10.100Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z"
}
}
{
"type": "email.delivered",
"created_at": "2026-10-23T14:02:11.350Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z"
}
}

SES 전달 지연과 일시적 반송에서 발생합니다. 단순 지연에는 추가 필드가 없습니다. 일시적 반송에는 bounce 객체도 포함됩니다(아래 참고, type: "Transient").

{
"type": "email.delivery_delayed",
"created_at": "2026-10-23T14:07:00.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z"
}
}

영구 반송만 이 이벤트로 전달되며, 해당 주소는 발송 제외 목록에 추가됩니다. data.bounce에는 type(Permanent), subType, 수신 서버의 message가 들어 있습니다.

{
"type": "email.bounced",
"created_at": "2026-10-23T14:02:12.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["nobody@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z",
"bounce": {
"type": "Permanent",
"subType": "General",
"message": "smtp; 550 5.1.1 The email account that you tried to reach does not exist."
}
}
}

반송 하위 유형에는 General, NoEmail, Suppressed, OnAccountSuppressionList, MailboxFull, MessageTooLarge, ContentRejected, AttachmentRejected가 있습니다.

추가 필드는 없습니다. 해당 주소는 발송 제외 목록에 추가됩니다.

{
"type": "email.complained",
"created_at": "2026-10-23T15:30:00.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Our monthly newsletter",
"created_at": "2026-10-23T14:02:09.512Z"
}
}
{
"type": "email.opened",
"created_at": "2026-10-23T14:05:44.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z",
"open": { "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (Macintosh)" }
}
}
{
"type": "email.clicked",
"created_at": "2026-10-23T14:06:10.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z",
"click": {
"link": "https://acme.com/get-started",
"ipAddress": "203.0.113.7",
"userAgent": "Mozilla/5.0 (Macintosh)",
"timestamp": "2026-10-23T14:06:10.000Z"
}
}
}
{
"type": "email.failed",
"created_at": "2026-10-23T14:02:10.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z",
"failed": { "reason": "Attachment https://files.acme.com/report.pdf returned HTTP 404" }
}
}

suppressed.reason은 suppression_list(수신자가 발송 제외 목록에 있음) 또는 recipient_domain_does_not_accept_mail(수신자 도메인에 메일 서버가 없음)입니다.

{
"type": "email.suppressed",
"created_at": "2026-10-23T14:02:10.000Z",
"data": {
"email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10",
"from": "Acme <hello@acme.com>",
"to": ["user@example.com", "other@example.com"],
"subject": "Welcome to Acme",
"created_at": "2026-10-23T14:02:09.512Z",
"suppressed": { "recipients": ["other@example.com"], "reason": "suppression_list" }
}
}

TypeScript에서 이벤트 타입 지정하기

섹션 제목: “TypeScript에서 이벤트 타입 지정하기”

Node SDK는 type으로 구분되는 WebhookPayload 유니언을 내보내고 spacepost.webhooks.verify()가 이를 반환하므로, event.type으로 좁히면 올바른 data 필드를 얻을 수 있습니다.

import type { WebhookPayload } from "@spacepost/node";
function handle(event: WebhookPayload) {
switch (event.type) {
case "email.bounced":
return event.data.bounce?.subType;
case "email.clicked":
return event.data.click?.link;
case "email.suppressed":
return event.data.suppressed.recipients;
case "email.failed":
return event.data.failed.reason;
default:
return event.data.tenant_id; // present when the email was sent for a tenant
}
}