Repetições e idempotência
O que é repetido, o que deliberadamente não é, e porque é que um envio repetido não pode duplicar.
Envios
O cliente anexa uma Idempotency-Key a todos os envios (emails->send, emails->sendBatch, templates->send e broadcasts->send), gerada uma vez por chamada como oe- seguido de um UUID aleatório, e reutilizada pelas repetições dessa chamada. A API reivindica essa chave antes de despachar seja o que for, por isso uma repetição reproduz a mensagem original em vez de enviar uma segunda, enquanto duas chamadas deliberadas a send continuam a enviar duas vezes. São intenções diferentes e assim se mantêm.
Passe a sua própria idempotencyKey: para estender essa garantia entre processos, para que uma tarefa que tenha ido abaixo e voltado a correr reproduza os seus envios em vez de os repetir. Uma reprodução responde com replayed a true e com a mensagem guardada tal como está agora.
$invoiceId = 'inv_2026_09_4192'; $sent = $client->emails->send([ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',], idempotencyKey: 'invoice:' . $invoiceId); echo $sent['id'], ' ', ($sent['replayed'] ?? false) ? 'replayed' : 'sent', PHP_EOL;Derive-a do que tornou o envio necessário. Nunca de um relógio. Reutilizar uma chave com um corpo diferente é recusado com um 422 idempotency_key_reuse em vez de reproduzido silenciosamente. Uma chave tem de 1 a 255 caracteres entre letras, algarismos, _, ., : ou -, e qualquer outra coisa é um 400 invalid_idempotency_key.
Tudo o resto
Todos os GET são repetidos. Uma escrita só é repetida quando um segundo pedido idêntico não pode significar nada de diferente do primeiro, e um envio qualifica-se porque a sua chave de idempotência transforma uma repetição numa reprodução.
| Chamada | Repetida | Porquê |
|---|---|---|
| Todos os GET | Sim | Nada muda. |
| emails->send, emails->sendBatch, templates->send, broadcasts->send | Sim | Uma chave de idempotência torna uma repetição numa reprodução. |
| emails->cancel, emails->reschedule, broadcasts->cancel, forms->pause, forms->resume, threads->restore | Sim | Uma definição pura de um estado nomeado. |
| threads->update, threads->trash | Sim | Uma definição de etiqueta. Aplicá-la duas vezes é aplicá-la uma vez. |
| threads->snooze, threads->unsnooze | Sim | O instante de despertar está no corpo, não é derivado da hora de chegada. |
| emails->update, labels->update, webhooks->update, settings->update, roles->update, members->update, domains->update, domains->updateAddress, domains->updateAddressForward, contacts->update, audiences->update, keys->update, forms->update, branding->update, threads->updateNote, chats->rename, account->setEmailNotification, account->setPushMuted, appHost->set, workspaces->setActive | Sim | Uma definição pura de campos nomeados. |
| members->grantAddress, members->grantDomain, rules->reorder, threads->reorderNotes | Sim | Uma concessão é um upsert, e uma ordem é indicada por inteiro. |
| templates->publish, forms->publish, imports->start | Sim | Publicar o que já está publicado, ou iniciar uma importação que já começou, devolve-o inalterado. |
| templates->preview, templates->render, broadcasts->preview, rules->test | Sim | Renderizam, contam ou avaliam, e não escrevem nada. |
| domains->verify, appHost->verify, senders->research | Sim | Uma verificação repetida não altera nada além da hora em que foi feita. |
| contacts->save, contacts->setAudiences, contacts->removePhoto, contacts->block, contacts->unblock, contacts->deleteMany, keys->revoke, files->revokeLink, files->revokeAllLinks, appHost->delete, account->removePhoto, branding->removeImage, domains->removeLogo, domains->removeLogoCertificate, domains->removeAddressPhoto, account->acceptInvitation, account->declineInvitation, forms->approveSubmission, subscriptions->move | Sim | Cada uma indica o resultado final, por isso uma segunda chamada deixa o que a primeira deixou. |
| audiences->addContact, audiences->addContacts, audiences->removeContacts, audiences->importContacts, suppressions->add, domains->createAddress | Sim | Uma repetição encontra o trabalho da primeira chamada já feito e reporta-o em vez de o fazer duas vezes. |
| contacts->setPhoto, account->setPhoto, branding->uploadImage, domains->setLogo, domains->setLogoCertificate, domains->setAddressPhoto, imports->uploadChunk | Sim | Os bytes enviados de novo substituem o que a primeira tentativa guardou. |
| drafts->create, labels->create, webhooks->create, templates->create, rules->create, roles->create, tempMail->create, files->upload | Não | Uma repetição deixa dois objetos. |
| drafts->update | Não | Leia o id do resultado de cada escrita em vez de reutilizar o que enviou. |
| drafts->delete, labels->delete, webhooks->delete, templates->delete, rules->delete, roles->delete, members->remove, members->revokeAddress, tempMail->delete, tempMail->deleteMessage | Não | Uma repetição depois de uma resposta perdida reporta falha para trabalho que teve sucesso. |
| webhooks->rotateSecret | Não | Uma segunda rotação invalida o segredo que a primeira tentativa devolveu. |
| webhooks->test | Não | Enviaria uma segunda entrega sintética. |
| webhooks->replayDelivery | Não | Enviaria o evento ao seu recetor uma segunda vez. |
| emails->translate, emails->compose, emails->rewrite, emails->suggestSubject | Não | Cada uma gasta chamadas ao modelo, por isso uma repetição após um pedido sem resposta compra a mesma resposta duas vezes. |
| security->beginStepUp, security->verifyStepUp | Não | Uma repetição poderia enviar um segundo email ou gastar uma segunda tentativa do código. |
| Qualquer outra chamada que não seja um GET | Não | Enviada uma vez, e uma falha é reportada em vez de repetida. |
O backoff
- Limitado por
maxRetries:no cliente, com duas tentativas extra por omissão.maxRetries: 0desativa as repetições. - Apenas depois de uma falha de rede ou de um
408,500,502,503ou504. Um429só é repetido quando traz umRetry-After, e esta API não envia nenhum, por isso um limite de taxa lança exceção de imediato. Qualquer outro estado lança logo. - Exponencial, de meio segundo até oito, com jitter: cada espera é um ponto aleatório entre metade desse limite e o limite inteiro, para que uma frota não se ressincronize na recuperação.
- Ritmado pelo
Retry-Afterem qualquer uma das suas formas, delay-seconds e HTTP-date. Quando o servidor indica uma espera, o cliente espera exatamente esse tempo em vez de recuar progressivamente. - Um servidor a pedir mais de um minuto é tratado como estando a dizer ao cliente para parar e não para esperar, por isso a exceção é lançada com
retryAfterSecondsnele. Voltar mais cedo do que o pedido não é respeitá-lo. - Um timeout é uma falha de rede como qualquer outra, por isso uma chamada que é seguro repetir é tentada de novo depois de um, e
timeout:aplica-se de novo a cada tentativa. - As esperas são chamadas a
sleepeusleepdentro da chamada, por isso o script também espera, e a chamada só devolve ou lança uma exceção depois de terminada a sua última tentativa. Num pedido web pelo qual uma pessoa está à espera, mantenhatimeout:emaxRetries:baixos.