Envio agendado
Defina scheduled_at para reter uma mensagem até um horário específico. Quando esse horário chega, a mensagem entra no ciclo de vida normal de entrega e produz os mesmos eventos de um envio imediato. Sua aplicação não precisa rodar seu próprio agendador.
Agendando um envio
Adicione um timestamp scheduled_at a um envio POST /v1/email/messages normal. Nada mais no payload muda.
const msg = await bird.email.send({
from: "news@yourdomain.com",
to: ["delivered@messagebird.dev"],
subject: "Your weekly digest",
html: "<p>Here is what happened this week...</p>",
category: "marketing",
scheduled_at: "2027-01-15T09:00:00Z",
});
console.log(msg.id, msg.status); // "em_…", "accepted"msg = client.email.send(
from_="news@yourdomain.com",
to=["delivered@messagebird.dev"],
subject="Your weekly digest",
html="<p>Here is what happened this week...</p>",
category="marketing",
scheduled_at="2027-01-15T09:00:00Z",
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
"time"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Email.Send(context.Background(), bird.EmailSendParams{
From: "news@yourdomain.com",
To: []string{"delivered@messagebird.dev"},
Subject: "Your weekly digest",
HTML: "<p>Here is what happened this week...</p>",
Category: bird.CategoryMarketing,
ScheduledAt: time.Date(2026, 7, 30, 9, 0, 0, 0, time.UTC),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$message = $bird->email->send(
from: 'news@yourdomain.com',
to: ['delivered@messagebird.dev'],
subject: 'Your weekly digest',
html: '<p>Here is what happened this week...</p>',
category: 'marketing',
scheduledAt: new \DateTimeImmutable('2027-01-15T09:00:00Z'),
);
echo $message->getId(), ' ', $message->getStatus();bird email send \
--from news@yourdomain.com \
--to delivered@messagebird.dev \
--subject 'Your weekly digest' \
--html '<p>Here is what happened this week...</p>' \
--category marketing \
--scheduled-at 2027-01-15T09:00:00Zcurl -X POST https://us1.platform.bird.com/v1/email/messages \
-H "Authorization: Bearer bk_us1_..." \
-H "Content-Type: application/json" \
-d '{
"from": "news@yourdomain.com",
"to": ["delivered@messagebird.dev"],
"subject": "Your weekly digest",
"html": "<p>Here is what happened this week...</p>",
"category": "marketing",
"scheduled_at": "2027-01-15T09:00:00Z"
}'A chamada retorna 202 Accepted com o ID da mensagem prefixado por em_ e status: accepted imediatamente. É o mesmo objeto de mensagem que um envio imediato retorna, mais scheduled_at ecoado em UTC, para que você confirme o horário de envio sem uma leitura adicional. A aceitação é síncrona; a entrega é adiada. Omita scheduled_at da solicitação e a mensagem sai na hora, e a resposta não traz a chave scheduled_at.
Nos endpoints de leitura a mensagem mostra status: scheduled com seu scheduled_at até o horário de envio chegar:
Exemplo de código
{
"id": "em_01ky7q24hafjgvzfg02v3m177p",
"status": "scheduled",
"scheduled_at": "2027-01-15T09:00:00Z",
"category": "marketing"
}Quando o horário chega, liberamos a mensagem e seu status avança pelos estados habituais (accepted, depois processed, depois delivered, e assim por diante). scheduled_at permanece definido depois disso, para que você sempre veja para quando a mensagem foi agendada.
O agendamento consome uma unidade da cota de e-mails agendados da sua organização no período de faturamento. Exceder essa cota é rejeitado com um 422 (E10003).
Um envio agendado usa conteúdo inline
scheduled_at e template são mutuamente exclusivos, e um envio que define ambos é rejeitado com um 422. Esse é o contrato: um envio agendado tem seu próprio subject e corpo, e um envio de template sai imediatamente. Para agendar conteúdo de template, renderize o assunto e o corpo primeiro. O dashboard e o bird CLI mostram a prévia exata do assunto, HTML e texto que um envio de template entregaria. Agende esses valores renderizados como conteúdo inline.
Um item de batch aceita scheduled_at nos mesmos termos, então um batch pode misturar mensagens agendadas e imediatas. Cada item agendado consome sua própria unidade da cota, e o batch inteiro é rejeitado se o horário de qualquer item estiver fora do intervalo permitido. Cada item agendado traz seu próprio scheduled_at na resposta do batch, e um item de envio imediato não tem a chave scheduled_at; a referência do batch mostra ambos em uma mesma resposta.
Um payload de envio imediato ainda pode ser grande demais para agendar. Se o corpo, a lista de destinatários ou os metadados excederem o limite de agendamento, API retorna 422. Reduza esses campos ou envie a mensagem imediatamente.
Escolhendo o horário de envio
scheduled_at é um timestamp absoluto RFC 3339. Duas regras o governam:
- Precisa estar entre 30 segundos e 30 dias no futuro. Menos de 30 segundos ou mais de 30 dias é rejeitado com um 422. O limite mínimo impede que um agendamento dispute com um envio imediato. Trinta dias é o horizonte máximo pelo qual retemos uma mensagem.
- Forneça um instante exato. Inclua um Z UTC (2027-01-15T09:00:00Z) ou um offset explícito (2026-07-30T09:00:00-04:00, o mesmo instante que 13:00:00Z). Comparamos o instante com o horário atual e nunca interpretamos um horário local sem offset nem aplicamos o fuso horário do destinatário. Para enviar às 9h no horário local de cada destinatário, calcule esses instantes você mesmo e agende um envio por fuso horário.
Expressões relativas como "in 2 hours" não são aceitas. Envie um timestamp resolvido.
Listando mensagens agendadas
Filtre a lista de mensagens por status para ver as mensagens que ainda não foram disparadas:
for await (const message of bird.email.list({ status: "scheduled" })) {
console.log(message.id, message.scheduled_at);
}for message in client.email.list(status="scheduled"):
print(message.id, message.scheduled_at)for msg, err := range client.Email.List(context.Background(), bird.EmailListParams{Status: bird.EmailStatusScheduled}) {
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id)
}foreach ($bird->email->list(['status' => 'scheduled']) as $message) {
echo $message->getId(), "\n";
}bird email list --status scheduledcurl "https://us1.platform.bird.com/v1/email/messages?status=scheduled" \
-H "Authorization: Bearer bk_us1_..."status=canceled lista as que você cancelou antes do envio. Quando uma mensagem agendada dispara, ela entra no pipeline e aparece com os status de entrega, igual a qualquer outro envio. O log de e-mails do dashboard oferece os mesmos filtros Scheduled e Canceled.
Cancelando um envio agendado
Cancele uma mensagem a qualquer momento antes de ela começar a ser enviada com POST /v1/email/messages/{message_id}/cancel:
await bird.email.cancel("em_abc123");client.email.cancel("em_abc123")if err := client.Email.Cancel(context.Background(), "em_abc123"); err != nil {
log.Fatal(err)
}$bird->email->cancel('em_01krdgeqcxet5s7t44vh8rt9mg');bird email cancel <message-id> --yes{
"name": "email_cancel",
"arguments": {
"message_id": "<message-id>"
}
}curl -X POST "https://{region}.platform.bird.com/v1/email/messages/{message_id}/cancel" \
-H "Authorization: Bearer $TOKEN"Um cancelamento bem-sucedido retorna 204 No Content. O status da mensagem passa a ser canceled, ela nunca é enviada, e um webhook email.canceled dispara. Quatro coisas importantes:
-
Só uma mensagem ainda agendada pode ser cancelada. Uma mensagem que já começou a ser enviada, já foi enviada ou já foi cancelada retorna 409:Exemplo de código
{ "error": { "type": "conflict_error", "code": "E10005", "name": "EmailNotCancelable", "message": "This message cannot be canceled.", "remediation": "Only a scheduled message that has not started sending can be canceled. Once sending begins there is no way to pull it back." } }Quando o horário de envio chega, um cancelamento também pode perder a corrida para o próprio envio e retornar 409 pelo mesmo motivo. -
Um envio grande pode levar alguns segundos para se tornar cancelável. Um envio agendado com anexos ou corpo grande ainda está armazenando conteúdo após o 202, então:
- Um cancelamento nessa janela retorna 409 e a mensagem permanece agendada.
- Leia a mensagem novamente.
- Se ela ainda mostrar status: scheduled, tente o cancelamento novamente.
-
Cancelar não devolve a cota de e-mails agendados. A unidade consumida no momento do agendamento permanece consumida, e é isso que impede que um loop de agendar e cancelar contorne a cota. Sua cota de envio regular não é afetada, porque ela só é cobrada quando a mensagem é realmente enviada.
-
O cancelamento pode ser repetido com segurança usando um Idempotency-Key, como qualquer outra escrita.
Para mover um envio agendado para outro horário, cancele-o e envie uma nova solicitação com o novo scheduled_at. Você recebe um novo ID em_.
O que acontece no horário de envio
O agendamento muda apenas quando a mensagem é liberada. Sua construção e governança permanecem as mesmas. Anexos, categoria, tags e metadados se comportam exatamente como em um envio imediato e são ecoados nos eventos de webhook da mesma forma. Quatro verificações se dividem entre os dois momentos:
- Validação de payload e domínio acontece na hora. Um envio agendado malformado falha na chamada API com um 422, então você descobre agora e não às 9h.
- O domínio do remetente é verificado novamente no horário de envio. Se o domínio from não estiver mais verificado quando o horário agendado chegar, a mensagem não é enviada. Seus destinatários retornam como rejected com um motivo, em vez de receberem e-mail de um domínio não verificado. Mantenha o domínio verificado durante toda a janela.
- Sua cota de envio é cobrada no horário de envio. A cota de envio regular é consumida quando a mensagem dispara. O agendamento não altera a cota. Se a cota estiver esgotada no horário de envio, os destinatários são rejeitados.
- A supressão é avaliada no horário de envio, com base na sua lista de supressão como ela estiver naquele momento, então alguém que cancelar a inscrição entre o agendamento e o envio ainda será respeitado.
Erros
| Status | Código | Quando |
|---|---|---|
| 422 | E10003 | A cota de e-mails agendados da sua organização para o período de faturamento foi esgotada |
| 422 | scheduled_at está a menos de 30 segundos ou a mais de 30 dias | |
| 422 | scheduled_at foi combinado com template | |
| 422 | O payload é grande demais para estacionar; reduza o corpo, os destinatários ou os metadados, ou envie agora | |
| 409 | E10005 | A mensagem não pode mais ser cancelada: já começou a ser enviada, já foi enviada ou já foi cancelada |
| 404 | Nenhuma mensagem com esse ID neste espaço de trabalho |
Webhooks
Dois eventos são específicos do agendamento, além dos eventos de entrega habituais:
- email.scheduled dispara quando uma mensagem é aceita com um scheduled_at futuro e informa esse horário.
- email.canceled dispara quando uma mensagem agendada é cancelada antes do envio.
Quando a mensagem dispara, a cadeia normal de email.accepted segue sem alterações.
Próximos passos
- Enviando e-mail: o payload completo de envio e o modelo assíncrono 202
- Supressões: para quem não entregamos e por quê, avaliado no horário de envio
- Eventos e webhooks: os eventos que uma mensagem agendada produz após disparar
- Idempotência: retentativas seguras para as chamadas de agendamento e cancelamento
- Referência do API: o contrato completo do endpoint de cancelamento
- Agendar um email para envio posterior: um vídeo que mostra um envio agendado e como cancelá-lo
Recursos relacionados
Continue com a documentação, guias e exemplos sobre este tópico. Os recursos estão em inglês.