문서로 건너뛰기
SDK

배치 발송

`emails.sendBatch`: 최대 100개의 메시지와 항목별 결과.

emails.sendBatch

send-batch.ts
const result = await openemail.emails.sendBatch(invoices.map(toMessage)) console.log(result.sent, 'sent,', result.failed, 'failed') for (const item of result.items) {  if (item.status === 'error') console.error(item.index, item.error.code, item.error.message)  else console.log(item.index, item.email.id)}

items에는 입력마다 하나씩 순서대로 항목이 들어가며, 각 항목은 메시지를 담은 ok이거나 그 메시지가 거부되었을 봉투를 담은 error입니다. 롤백은 없으므로 failed > 0은 배치를 다시 보내야 할 이유가 아니라 처리할 목록입니다.

하나의 멱등성 키가 배치 전체를 덮고 서버가 항목마다 그것을 확장하므로, 재시도된 배치는 모든 메시지를 재생할 뿐 첫 번째 메시지로 뭉뚱그려지지 않습니다.

매개변수: emails.sendBatch

emailsEmailSend[]필수
1개에서 100개까지의 메시지이며, `{ "emails": [...] }`로 직렬화되어 주어진 순서대로 하나씩 처리됩니다. 빈 배열, 100개 초과, 또는 `translate`를 담은 항목이 10개를 넘으면 `emails`에 대한 `validation_error`로 호출 전체가 거부됩니다. `emails:send` 스코프 누락, 배열도 `{ emails: [...] }`도 아닌 본문, 형식이 잘못된 `Idempotency-Key`도 마찬가지이며, 모두 단 한 건의 메시지도 보내지기 전에 판정됩니다.
options.idempotencyKeystring
프로세스를 가로질러 배치를 중복 제거합니다. 어떤 경우든 클라이언트가 호출마다 새로 생성한 키를 붙이므로 클라이언트 자체의 재시도는 결코 이중 발송을 하지 않으며, 서버는 받은 키가 무엇이든 항목마다 `key/0`, `key/1` 식으로 확장합니다. 구분자인 슬래시는 사용자 키에 들어갈 수 없는 문자이므로, 하나의 키가 100개의 메시지에 걸쳐 있어도 그것들이 첫 번째 메시지로 뭉뚱그려질 수 없습니다.
emails[].fromRecipientInput필수
발신자입니다. 주소만 쓰거나 `Name <addr@host>` 또는 객체로 지정합니다. 기본 발신자는 없으며 키가 이 주소를 사용할 수 있어야 합니다. 거부되면 해당 항목 하나가 `from_address_forbidden` 코드의 `permission_error`로 실패합니다.
emails[].toRecipientInput | RecipientInput[]필수
수신자는 최소 한 명이어야 하며, 하나만 주면 클라이언트가 배열로 감쌉니다. `to`, `cc`, `bcc`를 합쳐 최대 50개 주소이고, 배치 전체가 아니라 메시지 단위로 셉니다.
emails[].ccRecipientInput | RecipientInput[]
기본값은 없음이며, `to`, `bcc`와 같은 50개 주소 합계에 포함됩니다.
emails[].bccRecipientInput | RecipientInput[]
기본값은 없음이며, 같은 50개 주소 합계에 포함됩니다. `Bcc`는 `headers`로 설정할 수 없는 이름 중 하나이므로, 숨은 참조를 넣는 방법은 이것뿐입니다. 헤더 형태로 넣으면 주소를 가려 주는 수신자별 봉투가 무의미해집니다.
emails[].replyToRecipientInput
답장이 가는 곳입니다. `headers` 이후에 적용되므로, 거기에 설정한 `Reply-To`가 있으면 두 번째로 추가되지 않고 덮어씁니다.
emails[].subjectstring
최대 998자로 RFC 5322의 줄 길이 제한이며, 기본값은 빈 문자열입니다. `template`이 제목을 제공하는 경우, 빈 제목은 템플릿 자체의 제목으로 넘어갑니다.
emails[].htmlstring
HTML 파트이며 최대 100만 자이고, 두 본문이 모두 주어졌을 때 수신자가 보는 파트입니다. `html`, `text`, `template`, `draftId` 중 하나는 필수이며, 어느 것도 없는 항목은 `html`에 대한 `validation_error`로 실패합니다.
emails[].textstring
일반 텍스트 파트이며 최대 100만 자입니다. 둘 다 보낼 수 있지만 이 경로의 모든 전송 계층은 하나의 문자열로 하나의 본문을 만들므로, `html`이 있으면 그쪽이 이깁니다.
emails[].headersRecord<string, string>
`X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority, Feedback-ID만 허용됩니다. 전송 계층이 스스로 설정하는 것(From, To, Bcc, Subject, Message-ID, DKIM 및 ARC 헤더)은 조용히 버려지지 않고 `reserved_header`로 거부됩니다. 값은 최대 998자이고 CR, LF, NUL을 담을 수 없는데, 두 번째 줄은 곧 두 번째 헤더이기 때문입니다.
emails[].attachmentsAttachmentInput[]
메시지당 파일 최대 20개이며, 인라인 파일은 디코딩 후 합계 5 MB까지이고 배치가 아니라 메시지 단위로 셉니다. `content`는 전송 시 base64이며, 바이트를 전달하면 클라이언트가 인코딩합니다. 손으로 만든 base64가 호출 스택을 터뜨리는 대표적인 지점이 바로 여기입니다. `{ fileId }` 항목은 워크스페이스에 이미 있는 파일을 가리키며 인라인 상한에 포함되지 않습니다.
emails[].threadIdstring
기존 스레드에 답장합니다. 최대 256자입니다. 전송 계층이 이 값으로 In-Reply-To와 References를 쓰며, 답장이 대화 옆이 아니라 대화 안에 들어가게 하는 것이 바로 그것입니다.
emails[].draftIdstring
저장된 초안의 내용을 이 봉투로 보냅니다. 최대 256자입니다. 여기서 만들어진 수신자, 제목, 헤더가 실제로 전송되는 값입니다.
emails[].template{ id, version?, props?, slots? }
저장된 템플릿을 서버에서 렌더링합니다. id(`tpl_…`)나 slug로 지정하며, `version`으로 리비전을 고정하고 `props`/`slots`로 값을 채웁니다. 항목이 수락될 때 한 번 해석되며, `html`/`text`와 함께 쓰거나 `draftId`와 함께 쓰면 거부됩니다. 각각이 메시지 내용에 대한 또 하나의 답이기 때문입니다.
emails[].scheduledAtDate | string
`Date`, ISO-8601 시각, 또는 `PT1H` 같은 기간입니다. 최소 1초 뒤여야 하고 최대 365일 뒤까지 가능합니다. 항목마다 독립적으로 예약되므로 하나의 배치가 서로 다른 발송 시각 100개를 담을 수 있습니다.
emails[].cancellableForSecondsnumber
즉시 발송에 대한 실행 취소 시간(초)으로, 0에서 900까지의 integer이고 기본값은 0입니다. 0보다 큰 값은 같은 항목의 `scheduledAt`과 함께 쓰면 거부되는데, 예약된 메시지는 나가기 전까지 이미 취소할 수 있기 때문입니다.
emails[].trackingTrackingRequest
`opens`와 `clicks`이며 각각 독립적으로 선택 사항이고, 각각 이 메시지 하나에 한해 설정을 덮어씁니다. 생략한 스위치는 메시지를 보내는 주소의 설정으로, 없으면 All addresses 설정으로 떨어지며, 그 둘 중 하나가 끄지 않았다면 켜져 있습니다.
emails[].tagsRecord<string, string>
레이블은 최대 10개이며, 키는 `A-Za-z0-9_-`에서 뽑은 1자에서 64자, 값은 최대 256자입니다. 메시지에 그대로 되돌려 주고 해석하지 않습니다. `emails.list`는 `status`, `from`, `limit`, `cursor`만 받으므로, 태그는 메시지를 찾는 수단이 아니라 이미 가진 메시지에서 읽어 내는 값입니다.
emails[].translateSendTranslateOptions
이 항목을 다른 언어로 보냅니다. 수락 시점에 해석되므로 승인된 문구가 그대로 나갑니다. 하나의 배치에서 이 값을 담을 수 있는 항목은 최대 10개입니다. 각각이 여러 번의 모델 호출을 소모하고 항목은 순서대로 처리되므로, 더 큰 배치는 발송 도중에 중단될 것입니다. 그 수를 넘으면 무엇이 보내지기 전에 `emails`에 대한 `too_many_items`로 호출 전체가 거부됩니다.

응답: BatchResultResource

itemsBatchItemResource[]
보낸 순서대로 입력마다 하나씩 들어가는 항목입니다. 롤백은 없으므로 이것은 트랜잭션에 대한 보고가 아니라 각 메시지에 무슨 일이 있었는지에 대한 기록입니다. API는 전부 수락되었든 일부든 하나도 수락되지 않았든 207을 응답하므로 프로미스는 어느 쪽이든 resolve되며, 분기해야 할 대상은 항목별 `status`입니다.
sentnumber
수락된 항목의 수이며, 실제로 나간 개수와는 다릅니다. 어떤 항목은 `ok`이면서도 `email.status`가 `failed`나 `partial`일 수 있는데, 행이 만들어진 뒤에 전송 계층이 메시지를 거부한 것은 거부된 요청이 아니라 배달 결과이기 때문입니다.
failednumber
`error`를 담은 항목의 수입니다. `failed > 0`은 배치를 다시 보낼 이유가 아니라 처리할 목록입니다. 수락된 메시지는 이미 나갔습니다.
items[].indexnumber
이 항목의 메시지가 보낸 배열에서 차지하던 위치입니다. 순서뿐 아니라 필드로도 실려 있으므로, `items`를 필터링하거나 정렬하는 코드도 어떤 입력이 실패했는지 말할 수 있습니다.
items[].status'ok' | 'error'
유니온의 판별자입니다. `ok`는 `email`을, `error`는 `error`를 담으며, 둘 다 담는 항목은 없습니다.
items[].emailSentEmailResource
수락된 메시지이며 `ok` 항목에만 있고, 단건 발송이 반환하는 것과 같은 형태입니다. `tracking` 키는 담지 않는데, 참여 지표는 나중에 보고되고 수락 시점에는 보고할 것이 없기 때문입니다.
items[].email.replayedboolean
파생된 `Idempotency-Key`가 이미 존재하는 발송과 일치해 새로 보내진 것이 없고 이것이 원래의 메시지일 때 true입니다.
items[].error{ type: string; code: string; message: string; param?: string }
이 메시지 하나가 거부된 이유이며 `error` 항목에만 있습니다. API의 오류 봉투에서 `docUrl`과 `requestId`를 뺀 것으로, 그 둘은 요청을 설명하는데 요청 자체는 성공했기 때문입니다.
items[].error.typestring
클라이언트가 분기해도 되는 분류입니다. `validation_error`, `permission_error`, `not_found_error`, `conflict_error` 등이 있습니다. 이 집합은 고정되어 있으며 `code`와 달리 늘어나지 않습니다.
items[].error.codestring
구체적인 실패입니다. `from_address_forbidden`, `invalid_email_address`, `too_many_recipients`, `reserved_header`, `message_too_large`, `unknown_parameter` 등이며, 열려 있고 추가되므로 모르는 코드는 그 `type`으로 취급하세요.
items[].error.messagestring
사람이 읽도록 쓰인 한 문장으로, 문제가 된 값이 있으면 그것을 짚어 줍니다. 안정적인 식별자가 아닙니다. 분기는 `code`로 하세요.
items[].error.paramstring
거부된 필드를 해당 메시지 내부의 점 표기 경로로 나타냅니다. `to.0`, `from`, `attachments` 같은 식입니다. 실패가 특정 필드를 가리키지 않으면 없으며, 배치에서의 위치가 앞에 붙는 일은 없습니다. 그것은 `index`가 할 일입니다.