문서로 건너뛰기
Ruby

배치 발송

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

emails.send_batch

send_batch.rb
invoices = [  {number: "INV-1042", email: "[email protected]"},  {number: "INV-1043", email: "[email protected]"}] messages = invoices.map do |invoice|  {from: "[email protected]", to: invoice[:email], subject: "Invoice #{invoice[:number]}", text: "Your invoice is attached."}end result = client.emails.send_batch(messages, idempotency_key: "invoices:2026-09") puts "#{result.sent} sent, #{result.failed} failed" result.items.each do |item|  if item[:status] == "error"    warn "#{item[:index]} #{item.dig(:error, :code)} #{item.dig(:error, :message)}"  else    puts "#{item[:index]} #{item.dig(:email, :id)}"  endend

send_batch는 각각 emails.send의 본문과 똑같은 형태인 메시지 Hash의 Array를 받아 OpenEmail::BatchResult를 반환합니다. 그 items에는 메시지마다 하나씩 Hash가 순서대로 들어 있으며, 각각 메시지를 담은 ok이거나 그 메시지가 거부된 이유를 담은 봉투를 가진 error입니다. 아무것도 롤백되지 않으므로, failed 건수가 0보다 크면 배치를 다시 보낼 이유가 아니라 처리해야 할 목록입니다.

멱등성 키 하나가 배치 전체를 담당하고 서버가 이를 항목마다 확장하므로, 재시도된 배치는 메시지를 첫 번째 것으로 합쳐 버리지 않고 모든 메시지를 재생합니다. 재시도할 때는 같은 Array를 같은 순서로 보내세요: 위치가 바뀐 항목은 다른 위치의 키에 묶여 idempotency_key_reuse 오류로 돌아옵니다.

거부된 메시지는 예외를 발생시키지 않습니다. 예외는 배치 전체의 문제에서만 발생합니다: 빈 Array, 100개를 넘는 메시지, translate를 담은 메시지가 10개를 넘는 경우, 키나 스코프 실패, 서버 장애입니다. 도중에 생긴 서버 장애는 앞의 항목이 이미 나간 뒤에 발생하며, 클라이언트는 같은 키로 재시도하므로 그 항목들은 두 번 보내지지 않고 재생됩니다.

항목은 하나의 요청 안에서 차례로 발송되므로, 즉시 발송의 큰 배치는 한 번의 send보다 눈에 띄게 오래 걸립니다. 클라이언트의 timeout:은 넉넉하게 두세요.

매개변수: emails.send_batch

emailsArray<Hash>필수
1개에서 100개까지의 메시지이며, `{"emails": [...]}`로 전송되어 주어진 순서대로 하나씩 수락됩니다. 각 메시지는 `emails.send`와 같은 처리를 거치므로, 수신자가 하나면 감싸지고, Time은 시각으로 바뀌며, 첨부 파일 바이트는 인코딩됩니다. 빈 Array, 100개 초과, 또는 `translate`를 담은 메시지가 10개를 넘으면 `emails`에 대한 `validation_error`로 호출 전체가 거부됩니다. `emails:send` 스코프 누락과 형식이 잘못된 `idempotency_key:`도 단 한 건의 메시지도 보내지기 전에 호출 전체를 거부합니다.
idempotency_keyString
프로세스를 가로질러 배치를 중복 제거합니다. 어떤 경우든 클라이언트가 호출마다 새로 생성한 키를 붙이므로 클라이언트 자체의 재시도는 결코 이중 발송을 하지 않으며, 서버는 받은 키가 무엇이든 항목마다 `key/0`, `key/1` 식으로 확장합니다. 구분자인 슬래시는 사용자 키에 들어갈 수 없는 문자이므로, 하나의 키가 100개의 메시지에 걸쳐 있어도 그것들이 첫 번째 메시지로 뭉뚱그려질 수 없습니다.
api_keyString
클라이언트의 키 대신 이 키로 배치를 발송합니다.

emails의 각 메시지

fromString or Hash필수
발신자입니다. 주소만 쓰거나 `Name <addr@host>`, 또는 `email`과 `name`을 가진 Hash로 지정합니다. 기본 발신자는 없으며 키가 이 주소를 사용할 수 있어야 합니다. 거부되면 해당 항목 하나가 `from_address_forbidden` 코드의 `permission_error`로 실패합니다.
toString, Hash or Array필수
수신자는 최소 한 명이어야 하며, 하나만 주면 클라이언트가 Array로 감쌉니다. `to`, `cc`, `bcc`를 합쳐 최대 50개 주소이고, 배치 전체가 아니라 메시지 단위로 셉니다.
ccString, Hash or Array
기본값은 없음이며, `to`, `bcc`와 같은 50개 주소 합계에 포함됩니다.
bccString, Hash or Array
기본값은 없음이며, 같은 50개 주소 합계에 포함됩니다. `Bcc`는 `headers`로 설정할 수 없는 이름 중 하나이므로, 숨은 참조를 넣는 방법은 이것뿐입니다. 헤더 형태로 넣으면 주소를 가려 주는 수신자별 봉투가 무의미해집니다.
replyToString or Hash
답장이 가는 곳입니다. `headers` 이후에 적용되므로, 거기에 설정한 `Reply-To`가 있으면 두 번째로 추가되지 않고 덮어씁니다.
subjectString
최대 998자로 RFC 5322의 줄 길이 제한이며, 기본값은 빈 String입니다. `template`이 제목을 제공하는 경우, 빈 제목은 템플릿 자체의 제목으로 넘어갑니다.
htmlString
HTML 파트이며 최대 100만 자이고, 두 본문이 모두 주어졌을 때 수신자가 보는 파트입니다. `html`, `text`, `template`, `draftId` 중 하나는 필수이며, 어느 것도 없는 항목은 `html`에 대한 `validation_error`로 실패합니다.
textString
일반 텍스트 파트이며 최대 100만 자입니다. 둘 다 보낼 수 있지만 이 경로의 모든 전송 계층은 하나의 String으로 하나의 본문을 만들므로, `html`이 있으면 그쪽이 우선합니다.
headersHash
`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을 담을 수 없는데, 두 번째 줄은 곧 두 번째 헤더이기 때문입니다.
attachmentsArray<Hash>
메시지당 파일 최대 20개이며, 인라인 파일은 디코딩 후 합계 5 MB까지이고 배치가 아니라 메시지 단위로 셉니다. `content`는 통신상에서 base64입니다. 바이트를 바이너리 String, IO, Pathname으로 전달하면 클라이언트가 인코딩합니다. `fileId`만 가진 Hash는 워크스페이스에 이미 있는 파일을 가리키며 인라인 상한에 포함되지 않습니다.
threadIdString
기존 스레드에 답장합니다. 최대 256자입니다. 전송 계층이 이 값으로 In-Reply-To와 References를 쓰며, 답장이 대화 옆이 아니라 대화 안에 들어가게 하는 것이 바로 그것입니다.
draftIdString
저장된 초안의 내용을 이 봉투로 보냅니다. 최대 256자입니다. 여기서 만들어진 수신자, 제목, 헤더가 실제로 전송되는 값입니다.
templateHash
저장된 템플릿을 서버에서 렌더링합니다. id(`tpl_…`)나 slug로 지정하며, `version`으로 리비전을 고정하고 `props`와 `slots`로 값을 채웁니다. 항목이 수락될 때 한 번 확정되며, `html`이나 `text`와 함께 쓰거나 `draftId`와 함께 쓰면 거부됩니다. 각각이 메시지 내용에 대한 또 하나의 답이기 때문입니다.
scheduledAtTime, DateTime or String
Time 또는 DateTime, ISO 8601 시각, 또는 `PT1H` 같은 기간입니다. 최소 1초 뒤여야 하고 최대 365일 뒤까지 가능합니다. Ruby의 Date는 그날의 UTC 자정을 뜻합니다. 항목마다 독립적으로 예약되므로 하나의 배치가 서로 다른 발송 시각 100개를 담을 수 있습니다.
cancellableForSecondsInteger
즉시 발송에 대한 실행 취소 시간(초)으로, 0에서 900까지이고 기본값은 0입니다. 0보다 큰 값은 같은 항목의 `scheduledAt`과 함께 쓰면 거부되는데, 예약된 메시지는 나가기 전까지 이미 취소할 수 있기 때문입니다.
trackingHash
`opens`와 `clicks`이며 각각 선택 사항이고, 각각 이 메시지 하나에 한해 설정을 덮어씁니다. 생략한 키는 메시지를 보내는 주소(또는 그 주소를 받아 낸 캐치올)의 설정을 따르며, 그 주소가 켜지 않았다면 꺼져 있습니다.
tagsHash
라벨은 최대 10개이며, 키는 `A-Za-z0-9_-`에서 뽑은 1자에서 64자, 값은 최대 256자입니다. 메시지에 그대로 되돌려 주고 해석하지 않습니다: `emails.list`는 `status:`, `from:`, `broadcast_id:`와 예약 기간으로만 필터링하므로, 태그는 메시지를 찾는 수단이 아니라 이미 가진 메시지에서 읽어 내는 값입니다.
translateHash
이 항목을 다른 언어로 보냅니다. 수락 시점에 확정되므로 승인된 문구가 그대로 나갑니다. 하나의 배치에서 이 값을 담을 수 있는 항목은 최대 10개입니다: 각각이 여러 번의 모델 호출을 소모하고 항목은 순서대로 처리되므로, 더 큰 배치는 발송 도중에 중단될 것입니다. 그 수를 넘으면 무엇이든 보내지기 전에 `emails`에 대한 `too_many_items`로 호출 전체가 거부됩니다.

응답: OpenEmail::BatchResult

itemsArray<Hash>
보낸 순서대로 메시지마다 하나씩 들어가는 Hash입니다. 롤백은 없으므로 이것은 트랜잭션에 대한 보고가 아니라 각 메시지에 무슨 일이 있었는지에 대한 기록입니다. API는 전부 수락되었든 일부든 하나도 수락되지 않았든 207을 응답하므로 호출은 어느 쪽이든 반환되며, 분기해야 할 대상은 항목별 `status`입니다.
sentInteger
수락된 항목의 수이며, 실제로 나간 개수와는 다릅니다. 어떤 항목은 `ok`이면서도 `status`가 `failed`나 `partial`인 `email`을 담을 수 있는데, 행이 만들어진 뒤에 전송 계층이 메시지를 거부한 것은 거부된 요청이 아니라 배달 결과이기 때문입니다.
failedInteger
`error`를 담은 항목의 수입니다. 0보다 크면 배치를 다시 보낼 이유가 아니라 처리할 목록입니다. 수락된 메시지는 이미 나갔습니다.

각 항목

indexInteger
이 항목의 메시지가 보낸 Array에서 차지하던 위치입니다. 순서뿐 아니라 키로도 실려 있으므로, `items`를 필터링하거나 정렬하는 코드도 어떤 메시지가 실패했는지 말할 수 있습니다.
statusString
`ok` 또는 `error`입니다. `ok`는 `email`을, `error`는 `error`를 담으며, 둘 다 담는 항목은 없습니다.
emailHash
수락된 메시지이며 `ok` 항목에만 있고, 단건 발송이 반환하는 것과 같은 형태입니다. 파생된 `Idempotency-Key`가 이미 존재하는 발송과 일치하면 `replayed`가 true이며, 이때는 새로 보내진 것이 없고 이것이 원래 메시지입니다. `tracking` 키는 담지 않는데, 참여 지표는 나중에 보고되고 수락 시점에는 보고할 것이 없기 때문입니다.
errorHash
이 메시지 하나가 거부된 이유이며 `error` 항목에만 있습니다. API의 오류 봉투에서 `docUrl`과 `requestId`를 뺀 것으로, 그 둘은 요청을 설명하는데 요청 전체는 성공했기 때문입니다.

항목의 오류

typeString
분기에 사용할 분류입니다: `validation_error`, `permission_error`, `not_found_error`, `conflict_error` 등이 있습니다. 이 집합은 동결되어 있으며 `code`와 달리 늘어나지 않습니다.
codeString
구체적인 실패입니다. `from_address_forbidden`, `invalid_email_address`, `too_many_recipients`, `reserved_header`, `message_too_large`, `unknown_parameter` 등이며, 열려 있고 추가되므로 모르는 코드는 그 `type`으로 취급하세요.
messageString
사람이 읽도록 쓰인 한 문장으로, 문제가 된 값이 있으면 그것을 짚어 줍니다. 안정적인 식별자가 아닙니다. 분기는 `code`로 하세요.
paramString
거부된 필드를 해당 메시지 내부의 점 표기 경로로 나타냅니다. `to.0`, `from`, `attachments` 같은 식입니다. 실패가 특정 필드를 가리키지 않으면 없으며, 배치에서의 위치가 앞에 붙는 일은 없습니다. 그것은 `index`가 할 일입니다.