SDK
재시도와 멱등성
무엇이 재시도되고, 무엇이 의도적으로 재시도되지 않으며, 재시도된 발송이 왜 중복될 수 없는지.
발송
클라이언트는 모든 발송(emails.send, emails.sendBatch, templates.send)에 Idempotency-Key를 붙이며, 이 키는 **호출**마다 한 번 생성되어 그 호출의 재시도에서 재사용됩니다. API는 무엇이든 발송하기 전에 그 키를 선점하므로, 재시도는 두 번째 메시지를 보내는 대신 원래 메시지를 그대로 재생합니다. 반면 의도적으로 send()를 두 번 호출하면 두 번 발송됩니다. 둘은 서로 다른 의도이며, 앞으로도 다르게 남습니다.
직접 만든 idempotencyKey를 전달하면 그 보장을 프로세스 경계 너머까지 확장할 수 있습니다. 그러면 중간에 죽었다가 다시 실행된 작업이 발송을 반복하지 않고 재생합니다.
await openemail.emails.send(message, { idempotencyKey: `invoice:${invoice.id}` })발송이 필요해진 원인에서 키를 만드십시오. 절대 시계에서 만들지 마십시오. 같은 키를 다른 본문과 함께 재사용하면 조용히 재생되지 않고 idempotency_key_reuse로 거부됩니다.
그 밖의 모든 것
모든 읽기는 재시도됩니다. 쓰기는 두 번째 동일 요청이 첫 번째와 다른 의미를 가질 수 없는 경우에만 재시도되며, 발송은 멱등성 키가 반복을 재생으로 바꾸기 때문에 여기에 해당합니다.
| 호출 | 재시도 여부 | 이유 |
|---|---|---|
| 모든 읽기 | 예 | 바뀌는 것이 없습니다. |
| `emails.send`, `emails.sendBatch`, `templates.send` | 예 | 멱등성 키가 반복을 재생으로 바꿉니다. |
| `emails.cancel`, `emails.reschedule` | 예 | 명시된 상태를 그대로 설정하는 순수한 작업입니다. |
| `threads.update`, `threads.trash` | 예 | 레이블 설정입니다. 두 번 적용해도 한 번 적용한 것과 같습니다. |
| `threads.snooze`, `threads.unsnooze` | 예 | 깨어날 시각이 본문에 담겨 있으며, 도착 시각에서 계산되지 않습니다. |
| `labels.update`, `webhooks.update`, `settings.update`, `roles.update`, `members.update` | 예 | 명시된 필드를 그대로 설정하는 순수한 작업입니다. |
| `members.grantAddress`, `rules.reorder` | 예 | 권한 부여는 upsert이며, 순서는 전체가 명시됩니다. |
| `templates.publish` | 예 | 이미 게시된 head를 다시 게시하면 그대로 반환됩니다. |
| `templates.preview`, `rules.test` | 예 | 렌더링하거나 평가할 뿐, 아무것도 기록하지 않습니다. |
| `drafts.create`, `labels.create`, `webhooks.create`, `templates.create`, `rules.create`, `roles.create`, `tempMail.create` | 아니요 | 재시도하면 객체가 두 개 남습니다. |
| `drafts.update` | 아니요 | 보낸 id를 재사용하지 말고, 매 쓰기의 결과에서 id를 읽으십시오. |
| `drafts.delete`, `labels.delete`, `webhooks.delete`, `templates.delete`, `rules.delete`, `roles.delete`, `members.remove`, `members.revokeAddress`, `tempMail.delete`, `tempMail.deleteMessage` | 아니요 | 응답을 잃어버린 뒤 재시도하면 이미 성공한 작업을 실패로 보고하게 됩니다. |
| `webhooks.rotateSecret` | 아니요 | 두 번째 회전은 첫 번째 시도가 돌려준 시크릿을 무효화합니다. |
| `webhooks.test` | 아니요 | 두 번째 테스트 전달이 발송됩니다. |
| `emails.translate` | 아니요 | 모델 호출 비용이 들므로, 응답 없는 요청을 재시도하면 같은 답을 두 번 사는 셈이 됩니다. |
| 그 밖의 모든 쓰기 | 아니요 | 한 번만 전송되며, 실패는 반복되지 않고 보고됩니다. |
백오프
- 클라이언트의
maxRetries로 제한되며, 기본값은 두 번의 추가 시도입니다. - 네트워크 실패나
408,500,502,503,504에서만 재시도합니다.429는Retry-After가 함께 올 때만 재시도하는데 이 API는 그 헤더를 보내지 않으므로, 요청 제한은 곧바로 예외를 던집니다. 그 밖의 상태 코드도 즉시 예외를 던집니다. - 0.5초에서 8초까지 지수적으로 늘어나며 지터가 적용되므로, 여러 대가 복구 시점에 다시 동기화되지 않습니다.
Retry-After가 delay-seconds든 HTTP-date든 그 값에 맞춰 대기합니다. 서버가 대기 시간을 지정하면 클라이언트는 백오프 대신 정확히 그만큼 기다립니다.- 서버가 1분보다 긴 대기를 요구하면 잠시 쉬라는 뜻이 아니라 멈추라는 뜻으로 해석하여,
retryAfterSeconds를 담은 오류를 던집니다. 요청받은 것보다 일찍 돌아오는 것은 그 지시를 따르는 것이 아니기 때문입니다. - 호출자의
AbortSignal은 절대 재시도하지 않습니다. 중단되면 요청 중이든 다음 시도 전 대기 중이든 즉시OpenEmailNetworkError를 던집니다.