문서로 건너뛰기
PHP

배치 발송

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

emails->sendBatch

send_batch.php
$invoices = [    ['number' => 'INV-1042', 'email' => '[email protected]'],    ['number' => 'INV-1043', 'email' => '[email protected]'],]; $messages = []; foreach ($invoices as $invoice) {    $messages[] = [        'from' => '[email protected]',        'to' => $invoice['email'],        'subject' => 'Invoice ' . $invoice['number'],        'text' => 'Your invoice is attached.',    ];} $result = $client->emails->sendBatch($messages, idempotencyKey: 'invoices:2026-09'); echo $result->sent, ' sent, ', $result->failed, ' failed', PHP_EOL; foreach ($result as $item) {    if ($item['status'] === 'error') {        error_log($item['index'] . ' ' . $item['error']['code'] . ' ' . $item['error']['message']);    } else {        echo $item['index'], ' ', $item['email']['id'], PHP_EOL;    }}

sendBatch는 각각 emails->send가 받는 배열과 똑같은 형태인 메시지 배열의 리스트를 받아 OpenEmail\Result\BatchResult를 반환합니다. 그 items에는 메시지마다 하나씩 배열이 순서대로 들어 있으며, 각각 메시지를 담은 ok이거나 그 메시지가 거부된 이유를 담은 봉투를 가진 error이고, 결과를 순회하면 이 항목들을 차례로 돕니다. 아무것도 롤백되지 않으므로, failed 건수가 0보다 크면 배치를 다시 보낼 이유가 아니라 처리해야 할 목록입니다.

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

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

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

매개변수: emails->sendBatch

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

emails의 각 메시지

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

응답: OpenEmail\Result\BatchResult

결과는 읽기 전용이며, items에 대한 IteratorAggregate이자 Countable이므로, foreach ($result as $item)로 항목을 순회하고 count($result)로 개수를 셉니다.

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

각 항목

indexint
이 항목의 메시지가 보낸 리스트에서 차지하던 위치입니다. 순서뿐 아니라 키로도 실려 있으므로, `items`를 필터링하거나 정렬하는 코드도 어떤 메시지가 실패했는지 말할 수 있습니다.
statusstring
`ok` 또는 `error`입니다. `ok`는 `email`을, `error`는 `error`를 담으며, 둘 다 담는 항목은 없습니다.
emailarray
수락된 메시지이며 `ok` 항목에만 있고, 단건 발송이 반환하는 것과 같은 형태입니다. 파생된 `Idempotency-Key`가 이미 존재하는 발송과 일치하면 `replayed`가 true이며, 이때는 새로 보내진 것이 없고 이것이 원래 메시지입니다. `tracking` 키는 담지 않는데, 참여 지표는 나중에 보고되고 수락 시점에는 보고할 것이 없기 때문입니다.
errorarray
이 메시지 하나가 거부된 이유이며 `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`으로 취급하세요. 여기서 `code`인 이유는 이것이 디코딩된 봉투이기 때문이며, 예외에서는 같은 값을 `errorCode`가 담습니다.
messagestring
사람이 읽도록 쓰인 한 문장으로, 문제가 된 값이 있으면 그것을 짚어 줍니다. 안정적인 식별자가 아닙니다. 분기는 `code`로 하세요.
paramstring
거부된 필드를 해당 메시지 내부의 점 표기 경로로 나타냅니다. `to.0`, `from`, `attachments` 같은 식입니다. 실패가 특정 필드를 가리키지 않으면 없으며, 배치에서의 위치가 앞에 붙는 일은 없습니다. 그것은 `index`가 할 일입니다.