Saltar para a documentação
Python

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.send_batch, templates.send e broadcasts.send), gerada uma vez por **chamada** 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 idempotency_key para estender essa garantia entre processos, para que um trabalho que tenha ido abaixo e voltado a correr reproduza os seus envios em vez de os repetir.

idempotency.py
invoice_id = 'inv_4192' client.emails.send(    {'from': sender, 'to': recipient, 'subject': subject, 'text': text},    idempotency_key=f'invoice:{invoice_id}',)

Derive-a do que tornou o envio necessário. Nunca de um relógio. Reutilizar uma chave com um corpo diferente é recusado com idempotency_key_reuse em vez de reproduzido silenciosamente.

Tudo o resto

Todas as leituras são repetidas. 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ê
Todas as leiturasSimNada muda.
emails.send, emails.send_batch, templates.send e broadcasts.sendSimUma chave de idempotência torna uma repetição numa reprodução.
emails.cancel, emails.reschedule, broadcasts.cancel, forms.publish, forms.pause, forms.resume, forms.approve_submission, account.accept_invitation e account.decline_invitationSimUma definição pura de um estado nomeado.
threads.update, threads.trash e threads.restoreSimUma 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.
labels.update, webhooks.update, settings.update, roles.update, members.update, domains.update, contacts.update, audiences.update, keys.update, emails.update, forms.update, branding.update, chats.rename, threads.update_note, domains.update_address, domains.update_address_forward, account.set_email_notification, account.set_push_muted, app_host.set e workspaces.set_activeSimUma definição pura de campos nomeados.
members.grant_address, members.grant_domain, rules.reorder e threads.reorder_notesSimA concessão é um upsert, e a ordem é indicada por inteiro.
templates.publish, imports.startSimPublicar uma head que já está publicada, ou iniciar uma importação que já começou, devolve-a inalterada.
templates.preview, templates.render, broadcasts.preview e rules.testSimRenderizam, contam ou avaliam, e não escrevem nada.
domains.verify, app_host.verifySimUma verificação repetida não altera nada além da hora da verificação.
contacts.save, contacts.set_audiences, contacts.remove_photo, contacts.block, contacts.unblock, contacts.delete_many, keys.revoke, account.remove_photo, branding.remove_image, domains.remove_logo, domains.remove_logo_certificate, domains.remove_address_photo, app_host.delete, files.revoke_link, files.revoke_all_links e subscriptions.moveSimCada uma indica o resultado final, por isso uma segunda chamada deixa o que a primeira deixou.
audiences.add_contact, audiences.add_contacts, audiences.remove_contacts, audiences.import_contacts, suppressions.add, domains.create_address e senders.researchSimUma repetição encontra o trabalho da primeira chamada já feito e reporta-o em vez de o fazer duas vezes.
contacts.set_photo, imports.upload_chunk, account.set_photo, branding.upload_image, domains.set_logo, domains.set_logo_certificate e domains.set_address_photoSimOs bytes enviados de novo substituem o que a primeira tentativa guardou.
drafts.create, labels.create, webhooks.create, templates.create, rules.create, roles.create, temp_mail.create, files.upload, templates.design e forms.designNã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.revoke_address, temp_mail.delete e temp_mail.delete_messageNãoUma repetição depois de uma resposta perdida reporta falha para trabalho que teve sucesso.
webhooks.rotate_secretNãoUma segunda rotação invalida o segredo que a primeira tentativa devolveu.
webhooks.testNãoEnviaria uma segunda entrega sintética.
webhooks.replay_deliveryNãoEnviaria o evento ao seu recetor uma segunda vez.
emails.translateNãoGasta chamadas ao modelo, por isso uma repetição após um pedido sem resposta compra a mesma resposta duas vezes.
emails.compose, emails.rewrite e emails.suggest_subjectNãoCada tentativa gasta mais uma ação de IA e volta com uma resposta diferente.
Todas as outras chamadasNãoEnviada uma vez, e uma falha é reportada em vez de repetida.

forms.update é repetido mesmo com expectedUpdatedAt, por isso uma repetição depois de uma resposta perdida pode voltar com 409 version_conflict, porque a primeira tentativa foi concluída. Leia o formulário antes de tentar de novo.

client.raw.request repete um GET e envia tudo o resto uma única vez, a não ser que passe repeatable=True.

O backoff

  • Limitado por max_retries no cliente, com duas tentativas extra por omissão.
  • 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, 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 dormir, por isso o erro é levantado com retry_after_seconds nele. Voltar mais cedo do que o pedido não é respeitá-lo.
  • timeout limita cada tentativa, por isso uma chamada que use as suas duas repetições pode demorar três timeouts mais as esperas entre eles.
  • Uma chamada de AsyncOpenEmail cancelada nunca é repetida. O cancelamento propaga-se de imediato, a partir do pedido ou da espera antes da tentativa seguinte.