Saltar para a documentação
PHP

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.

idempotency.php
$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.

ChamadaRepetidaPorquê
Todos os GETSimNada muda.
emails->send, emails->sendBatch, templates->send, broadcasts->sendSimUma chave de idempotência torna uma repetição numa reprodução.
emails->cancel, emails->reschedule, broadcasts->cancel, forms->pause, forms->resume, threads->restoreSimUma definição pura de um estado nomeado.
threads->update, threads->trashSimUma definição de etiqueta. Aplicá-la duas vezes é aplicá-la uma vez.
threads->snooze, threads->unsnoozeSimO 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->setActiveSimUma definição pura de campos nomeados.
members->grantAddress, members->grantDomain, rules->reorder, threads->reorderNotesSimUma concessão é um upsert, e uma ordem é indicada por inteiro.
templates->publish, forms->publish, imports->startSimPublicar o que já está publicado, ou iniciar uma importação que já começou, devolve-o inalterado.
templates->preview, templates->render, broadcasts->preview, rules->testSimRenderizam, contam ou avaliam, e não escrevem nada.
domains->verify, appHost->verify, senders->researchSimUma 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->moveSimCada 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->createAddressSimUma 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->uploadChunkSimOs 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->uploadNãoUma repetição deixa dois objetos.
drafts->updateNãoLeia 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->deleteMessageNãoUma repetição depois de uma resposta perdida reporta falha para trabalho que teve sucesso.
webhooks->rotateSecretNãoUma segunda rotação invalida o segredo que a primeira tentativa devolveu.
webhooks->testNãoEnviaria uma segunda entrega sintética.
webhooks->replayDeliveryNãoEnviaria o evento ao seu recetor uma segunda vez.
emails->translate, emails->compose, emails->rewrite, emails->suggestSubjectNãoCada 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->verifyStepUpNãoUma repetição poderia enviar um segundo email ou gastar uma segunda tentativa do código.
Qualquer outra chamada que não seja um GETNãoEnviada 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: 0 desativa as repetições.
  • Apenas depois de uma falha de rede ou de um 408, 500, 502, 503 ou 504. Um 429 só é repetido quando traz um Retry-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-After em 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 retryAfterSeconds nele. 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 sleep e usleep dentro 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, mantenha timeout: e maxRetries: baixos.