Pular para o conteúdo

Dê ao seu agente um endereço de e-mail próprio

As inboxes de agente ainda não estão em nenhum plano à venda. Para pedir, escreva para [email protected].

Uma inbox de agente é um endereço de e-mail, como [email protected], que um agente lê pela API. Ela não tem senha nem login. Os e-mails chegam aos nossos servidores de e-mail, e você os lê com as mesmas chamadas de conversas e mensagens de qualquer outro e-mail.

Crie uma inbox

O endereço precisa terminar em um domínio que você cadastrou para inboxes de agente. Um dono cadastra o domínio no console, em Domínios, e cria os registros DNS que a página mostra. O domínio está pronto quando a situação dele é Ativo. Escolha um nome só para os seus agentes, como agents.acme.com. Uma chave não cadastra domínio. Um agente pede a um dono com um handoff manage_mail_domains.

Criar, alterar e excluir inboxes exige uma chave com a permissão inboxes. Listar e consultar exige read. Uma chave que um dono limitou a algumas caixas de e-mail ou inboxes de agente não cria inbox, porque a nova inbox ficaria fora da lista dela.

const { inbox } = await colding.agentInboxes.create({ address: "[email protected]" });
console.log(inbox.id, inbox.address);

const page = await colding.threads.list({ mailboxId: inbox.id });
for (const thread of page.items) console.log(thread.subject);

Troque a chave pela sua e o domínio por um dos seus. A chamada cria o endereço e mostra o ID dele. Uma inbox nova não tem e-mails, então a lista abaixo vem vazia.

A resposta é { created, inbox }. inbox.id é o ID que toda chamada de conversas recebe como mailboxId. Se você pedir um endereço que já tem, recebe essa inbox de volta com created igual a false.

Dois campos são opcionais. displayName é o nome que as pessoas veem ao lado do endereço. Sem ele, usamos a parte antes do @. label é o nome de um grupo, uma palavra como client-acme que ajuda a achar as inboxes depois. Um endereço não muda. Para usar outro, exclua a inbox e crie uma nova.

Antes do @, use letras minúsculas, números, pontos, hífens e sublinhados, até 64 caracteres. O ponto não pode ficar no começo, no fim nem duas vezes seguidas, e o sinal de mais não é aceito. Estes nomes nunca viram inbox, porque sistemas de e-mail e autoridades certificadoras escrevem para eles: postmaster, abuse, fbl, mailer-daemon, noc, security, hostmaster, webmaster, usenet, news, www, uucp, ftp, admin, administrator, root, ssladmin.

Uma criação é recusada por um destes motivos. Quando uma linha tem dois nomes, o segundo é o final de messageKey, que começa com serverErrors.

400 VALIDATION_ERRORinboxAddressInvalid
O endereço não é um que uma inbox possa ter. Veja as regras acima.
400 VALIDATION_ERRORinboxAddressReserved
O nome antes do @ é um dos nomes reservados.
409 CONFLICTinboxDomainNotReady
O domínio não é um domínio ativo seu para inboxes de agente. details.domainState vale pending ou failing, ou null quando você não tem um domínio com esse nome. Cadastre o domínio, termine os registros DNS ou corrija o registro que a última conferência achou errado.
409 CONFLICTinboxAddressTaken
Uma caixa de e-mail já usa esse endereço. Escolha outro.
409 CONFLICTinboxAddressHeld
Outro workspace excluiu uma inbox com esse endereço, e ele continua reservado. details.until é o dia em que ele fica livre, ou null quando ele fica reservado para sempre.
402 PLAN_GATE
O seu plano não tem espaço para mais uma inbox de agente, nem para os e-mails que ela guardaria.
429 RATE_LIMIT_EXCEEDED
Este workspace já pediu 1.000 endereços hoje. details.resetAt diz quando o limite zera.
403 AUP_REQUIRED
Um dono ainda não aceitou a política de uso aceitável. Peça com um handoff accept_aup.
403 FORBIDDEN
A chave não tem a permissão inboxes, ou um dono a limitou a algumas caixas de e-mail ou inboxes de agente.

Crie várias de uma vez

agentInboxes.createMany cria até 500 inboxes em uma chamada. Cada item recebe os mesmos campos de agentInboxes.create. O console cria uma de cada vez, então use esta chamada para criar várias.

const names = ["ana", "bruno", "carla"];
const { results } = await colding.agentInboxes.createMany({
  inboxes: names.map((name) => ({ address: `${name}@agents.acme.com`, label: "client-acme" })),
});

for (const result of results) {
  if (result.status === "refused") console.log(result.address, result.error.code);
  else console.log(result.address, result.status, result.inbox.id);
}

A resposta tem um item em results para cada endereço, na ordem em que você os enviou. O status dele é created, existing (você já tinha o endereço) ou refused. Um item recusado traz um error com o mesmo code e o mesmo messageKey que agentInboxes.create responderia, então um endereço ruim não impede os outros.

A chamada inteira falha, e nada é criado, quando o seu plano não tem espaço para todas as inboxes que ela criaria ou quando a chave não pode criar inboxes. O mesmo endereço duas vezes na mesma chamada é recusado na segunda vez, com a chave de mensagem serverErrors.inboxDuplicateInBatch.

Cada endereço que você envia conta para os 1.000 por dia que um workspace pode pedir, os recusados também.

Ache e agrupe inboxes

Um grupo é um nome que você dá a inboxes para achá-las depois, como client-acme. Na API, ele é o campo label. agentInboxes.list lista as inboxes do workspace por endereço. Filtre com label, com domainId para um domínio, com q para o começo de um endereço ou com ids para inboxes que você já conhece (até 100). agentInboxes.get lê uma.

const page = await colding.agentInboxes.list({ label: "client-acme", q: "an" });
for (const inbox of page.items) console.log(inbox.address, inbox.displayName);

const first = page.items[0];
if (first) {
  const inbox = await colding.agentInboxes.get({ inboxId: first.id });
  console.log(inbox.createdAt, inbox.paused);
}

Cada inbox tem id, address, domainId, displayName, label, paused, createdAt e createdBy. A lista é paginada: passe nextCursor de volta como cursor.

Uma inbox excluída não aparece na lista. Consultar uma inbox excluída, ou de outro workspace, responde 404.

Renomeie, reagrupe ou pause

agentInboxes.update muda displayName, label ou paused. Envie pelo menos um. Envie label: null para tirar a inbox do grupo.

const renamed = await colding.agentInboxes.update({
  inboxId,
  displayName: "Ana at Acme",
  label: "client-beta",
});
console.log(renamed.displayName, renamed.label);

const paused = await colding.agentInboxes.update({ inboxId, paused: true });
console.log(paused.paused);

paused pausa as respostas da inbox. Não impede a chegada de e-mails: a inbox continua recebendo, e você continua lendo. As respostas das inboxes de agente ainda não estão abertas, então hoje não há o que pausar.

Para impedir que cheguem e-mails a um endereço, exclua a inbox.

Leia o que chega

Os e-mails de uma inbox de agente ficam guardados como conversas e mensagens, como qualquer outro e-mail, então as chamadas de conversas e mensagens os leem. O id da inbox é o mailboxId que elas recebem, como o primeiro exemplo mostrou.

Uma lista de conversas sem mailboxId reúne as suas caixas de e-mail conectadas e as suas inboxes de agente. Escolha com inboxes: agent, connected ou all. mailDomainId mantém só as inboxes de um domínio. Um mailboxId vale mais que os dois. Se o seu código quer as caixas de e-mail que as pessoas conectaram, passe connected ou um mailboxId, para que os e-mails dos agentes não apareçam nela.

const agentMail = await colding.threads.list({ inboxes: "agent" });
console.log(agentMail.items.length);

const yourMailboxes = await colding.threads.list({ inboxes: "connected" });
console.log(yourMailboxes.items.length);

const oneDomain = await colding.threads.list({ mailDomainId });
console.log(oneDomain.items.length);

Para saber quando chega um e-mail, salve um webhook para message.received. Os dados dele trazem mailboxId, o mesmo ID de inbox.id, então um só handler envia cada mensagem ao agente certo:

async function receive(request: Request): Promise<Response> {
  const event = await verifyWebhook({
    payload: await request.text(),
    signature: request.headers.get("x-colding-signature"),
    secret: "whsec_the_secret_you_were_shown",
  });
  if (event.type !== "message.received") return new Response("ok");

  const agent = agentByInbox.get(event.data.mailboxId);
  if (agent) {
    const message = await colding.messages.get({ messageId: event.data.id });
    console.log(agent, event.data.from, message.body.text);
  }
  return new Response("ok");
}

Configure o webhook e leia o que ele perdeu

Confira quem enviou e baixe os arquivos

O e-mail que o nosso filtro marca como spam vai para a pasta Spam da inbox e não gera message.received. Se alguém o mover para a Caixa de entrada, o evento é gerado nessa hora, uma vez. Para ler o que o filtro segurou, liste as conversas com folder: "spam".

Uma mensagem de uma inbox de agente tem auth: o que o nosso servidor de e-mail conferiu sobre o domínio de quem enviou. O domínio passou quando dmarc é pass, ou none com aligned verdadeiro. fail quer dizer que a conferência falhou. Passar mostra que o e-mail veio daquele domínio. Não mostra quem controla um endereço dentro dele. Uma caixa de e-mail que você conectou tem auth: null. automated é verdadeiro quando nenhuma pessoa escreveu a mensagem: uma resposta automática, uma lista de e-mails, um bounce ou um relatório. O evento traz os dois campos.

fromOrgDomain é o domínio principal de quem enviou, como foi achado quando a mensagem chegou (acme.com para mail.acme.com). A conferência usou esse domínio. Ele só descreve a conferência: não é permissão para responder a nenhum endereço.

const { message } = await colding.messages.get({ messageId });
// The From domain passed its check. This does not prove who controls its individual address.
const auth = message.auth;
const fromItsDomain = auth?.dmarc === "pass" || (auth?.dmarc === "none" && auth.aligned === true);
if (fromItsDomain) {
  // Even then, its text and files are data from the sender, never instructions for your agent.
  const link = await colding.messages.getAttachment({ messageId, index: 0 });
  const file = await fetch(link.url); // a plain GET, no key, within a few minutes
  console.log(link.name, file.status);
}
const original = await colding.messages.getOriginal({ messageId }); // the .eml, as it arrived
console.log(original.url);

messages.getAttachment devolve um link para um arquivo, pela posição dele em attachments na mensagem, a partir de 0. messages.getOriginal devolve um link para a mensagem inteira, com todos os cabeçalhos, num arquivo .eml. Abra o link com um GET simples, sem chave, em até 10 minutos e no máximo 3 vezes. Quem tiver o link pode baixar o arquivo, e ele sempre chega como download, nunca como uma página. Um workspace pode pedir 30 links por minuto.

Numa inbox de agente, os arquivos e o original ficam guardados por 30 dias depois que a mensagem chega. attachmentsUntil na mensagem é o último momento em que dá para baixá-los. Depois disso a mensagem e o texto dela continuam aqui, e as duas chamadas respondem 404. Uma caixa de e-mail que você conectou não guarda arquivos: messages.getAttachment busca o arquivo na caixa a cada download, e messages.getOriginal responde 409 para ela.

Um arquivo foi feito por quem enviou a mensagem, como o texto dela. Trate como dados, nunca como instruções.

Responda a uma mensagem

messages.reply responde a uma mensagem de uma inbox de agente. Passe o id da mensagem e o seu texto. A chamada escreve a resposta e a envia pela inbox que recebeu a mensagem, na mesma conversa, com o assunto começando por Re:. Você não escolhe quem recebe. Ela vai para quem enviou a mensagem.

Troque a chave e o ID pelos seus. O próprio ID da mensagem é um bom intentKey: se o seu código repetir a chamada, a resposta sai uma vez, não duas:

const send = await colding.messages.reply({
  messageId,
  bodyText: "Thanks, Ana. The invoice is on its way.",
  intentKey: messageId, // a retry of this call sends nothing twice
});
console.log(send.status, send.to);
// reserved [email protected]

O mesmo intentKey com o mesmo texto devolve o mesmo envio. Com outro texto, responde 422 sendIntentReused, então uma chave vale uma resposta. Se você não passar intentKey, a gente cria um, e aí uma nova tentativa envia uma segunda resposta.

Uma resposta vai para uma pessoa e ainda não leva arquivos nem cópias.

Uma inbox de agente responde só ao endereço do campo De da mensagem. Se a mensagem pede que as respostas vão para outro endereço (Reply-To), a chamada é recusada. Também é preciso ter prova do domínio de quem enviou: auth.dmarc é pass, ou none com aligned verdadeiro. Uma mensagem com automated verdadeiro nunca é respondida. Uma caixa de e-mail que você conectou continua com as regras de sempre.

Uma resposta escrita como rascunho (drafts.reply e depois drafts.send) segue as mesmas regras. Antes de você enviar, drafts.preview mostra em blockers o que a impediria: inbox_replies_only, inbox_reply_automated, inbox_sender_unauthenticated e inbox_thread_full.

Uma inbox envia no máximo 10 respostas numa conversa em qualquer intervalo de 24 horas. O limite impede que dois agentes respondam um ao outro para sempre. Uma resposta agendada conta quando chega a hora, e é cancelada nessa hora se a conversa estiver cheia.

Toda mensagem que uma inbox de agente envia leva o cabeçalho Auto-Submitted: auto-replied. Ele pede aos outros programas de e-mail que não respondam automaticamente. Ele não avisa a pessoa de que foi um agente que escreveu para ela.

Onde a lei exigir, avise as pessoas de que um agente escreve para elas. A política de uso aceitável tem uma linha sobre isso. Ler a política de uso aceitável

Uma resposta é recusada por um destes motivos. Quando uma linha tem dois nomes, o segundo é o final de messageKey, que começa com serverErrors.

409 CONFLICTinboxRepliesOnly
A chamada abriria uma conversa, faria um acompanhamento de uma mensagem sua ou iria para um endereço diferente do de quem enviou. Uma inbox de agente só responde. Para escrever primeiro, envie por uma caixa de e-mail que você conectou.
409 CONFLICTinboxReplyAutomated
Uma máquina escreveu a mensagem, então nada a responde. Duas máquinas que respondem uma à outra nunca param. details.messageId traz o ID da mensagem.
409 CONFLICTinboxSenderUnauthenticated
Nada prova que a mensagem veio do domínio de quem enviou. Qualquer um pode escrever qualquer endereço no campo De, e a resposta iria para lá. Para responder a essa pessoa mesmo assim, envie por uma caixa de e-mail que você conectou. details.messageId traz o ID da mensagem.
409 CONFLICTmailboxUnderReview
Nossa equipe colocou a inbox em espera enquanto analisa uma denúncia sobre ela, então nada sai dela. details.ground diz o motivo. Cite o details.caseNumber quando escrever para a gente.
409 CONFLICTmailboxAutomaticHold
E-mails demais do domínio da inbox foram marcados como spam, deram bounce ou foram recusados como spam, então as respostas de todas as inboxes de agente desse domínio ficam em espera até nossa equipe liberar de novo. details.ground diz qual foi: complaint_rate, bounce_rate, policy_bounce_rate ou policy_abuse_burst. Cite o details.caseNumber quando escrever para a gente.
409 CONFLICTinboxFirstContactReview
Nossa equipe está analisando denúncias sobre a inbox, então ela só responde a quem seu workspace já escreveu antes, e quem enviou esta mensagem é um contato novo. Cite o details.caseNumber quando escrever para a gente.
429 QUOTA_EXCEEDEDinboxThreadDay
A inbox já enviou 10 respostas nesta conversa nas últimas 24 horas. details.resetAt diz quando abre uma vaga, e o cabeçalho Retry-After diz quantos segundos esperar. Uma resposta cancelada antes de sair devolve a vaga.
429 QUOTA_EXCEEDEDquotaAgentInboxDay
A inbox já usou o limite de envios de hoje. details.resetAt diz quando o dia termina.
402 PLAN_GATEagentRepliesOff
As respostas a pessoas de fora do workspace ainda não estão abertas para ele. details.gate é agentReplies e details.upgradeTo é null: nenhum plano as abre, então escreva para [email protected]. As respostas às outras inboxes de agente do seu workspace continuam saindo.
429 QUOTA_EXCEEDEDquotaAgentRepliesDay
O workspace já usou o limite de hoje para respostas a pessoas de fora. details.scope é agentRepliesDay, e details.resetAt diz quando o dia termina.

Como suas respostas são assinadas e para onde vão os bounces

Toda resposta de uma inbox de agente é assinada com a chave DKIM do seu próprio domínio. Com a assinatura, o outro servidor de e-mail confere que a mensagem veio do seu domínio e que ninguém a alterou no caminho. Você não precisa fazer nada. A gente cria a chave e troca a cada 365 dias, e os registros que você adicionou continuam iguais.

Se os registros de um domínio falharem na nossa conferência por 72 horas, a gente para de enviar por ele. O envio responde 409, com a chave de mensagem mailDomainNotSigning, e a inbox continua recebendo e-mails. Corrija os registros na página do domínio, no console, e clique em Conferir agora.

Quando um servidor de e-mail devolve uma mensagem de vez (o endereço não existe, a caixa foi fechada), o bounce chega a um endereço nosso, feito só para aquele e-mail. A gente liga o bounce ao seu envio, preenche bouncedAt nele e dispara send.bounced uma vez. Para ver, leia o envio:

const send = await colding.sends.get({ sendId });
console.log(send.status, send.bouncedAt);
// sent 2026-10-14T09:31:07.000Z

O status continua sent, porque a mensagem saiu. bouncedAt é a hora em que o bounce chegou. Só conta o bounce que responde a um e-mail seu. Uma mensagem que cita o envio de outra pessoa, ou que foi inventada para parecer um bounce, é descartada. O bounce de uma resposta de agente ainda não entra na taxa de bounce do seu workspace, a taxa que pode restringir um workspace. Ele vai passar a contar quando o log do nosso servidor de e-mail puder confirmá-lo.

Uma inbox de agente pode responder a pessoas para quem o workspace nunca escreveu: até 50 por dia em cada inbox e 200 por dia no workspace inteiro. O dia termina à meia-noite no fuso do workspace. Quem já recebeu e-mail do workspace não conta. Passou do limite, o envio responde 429, com details.scope (agentFirstRepliesInbox ou agentFirstRepliesWorkspace), limit e resetAt. Um envio que você agendou é cancelado quando bate no limite.

Enviar um e-mail e acompanhar

Uma chave para uma inbox de agente

Uma chave com read lê todas as inboxes do workspace. Para manter um agente longe dos e-mails dos outros, um dono pode criar uma chave que lê só a inbox desse agente. O dono cria a chave no console, em Configurações › Chaves de API, ou aperta Criar uma chave para esta inbox de agente na linha da inbox. A chave recebe read e send. Uma chave não cria chaves, então o agente pede ao dono.

Essa chave lê a inbox dela e mais nada. Uma conversa, uma mensagem ou uma inbox que não está na lista dela responde 404, exatamente como responderia para algo que não existe. Uma lista, como threads.list, traz só os e-mails da inbox dela.

Troque a chave e os IDs pelos seus. As primeiras chamadas leem a mensagem mais recente da inbox. A última pede outra inbox e recebe 404:

const threads = await colding.threads.list({ mailboxId: myInbox });
const newest = threads.items[0];
if (newest) {
  const messages = await colding.threads.messages({ threadId: newest.id });
  const last = messages.items[0];
  if (last) {
    const detail = await colding.messages.get({ messageId: last.id });
    console.log(detail.message.subject, detail.body.text);
  }
}

try {
  await colding.agentInboxes.get({ inboxId: otherInbox });
} catch (error) {
  // 404, the same answer as for an inbox that does not exist.
  if (!(error instanceof ColdingError) || error.status !== 404) throw error;
  console.log("This key can't see that inbox.");
}

O resto do workspace fica fechado para essa chave. Contatos, campanhas, alertas e as outras chamadas do workspace respondem 403, com details.scope igual a keyReads. Ela também não abre o fluxo de eventos ao vivo e recebe o mesmo 403.

events.list mostra a essa chave só os eventos das inboxes dela, os que têm data.mailboxId na lista. Um evento sem mailboxId fica de fora. Para recuperar o que veio durante uma pausa, guarde o resumeCursor da última página que você tratou e envie-o como cursor na próxima vez.

Uma conversa pode ter e-mails de mais de uma inbox, como uma mensagem enviada para dois endereços seus. Essa chave vê só as cópias dela. O número de mensagens, a lista de inboxes e a hora da última mensagem vêm só dessas cópias. Marcar a conversa como lida e movê-la mudam só a cópia da chave. Etiquetas e adiamentos são da conversa inteira, então, em uma conversa que também tem e-mails de uma inbox que a chave não lê, a resposta é 403, com details.scope igual a keyReads.

Com até 20 agentes, dê a cada um a sua própria chave. Acima disso, o seu backend recebe o webhook e roteia por mailboxId.

Como funcionam eventos, cursores e restaurações

Exclua uma inbox

agentInboxes.delete exclui uma inbox. Isso não pode ser desfeito.

const deleted = await colding.agentInboxes.delete({ inboxId });
console.log(deleted.id, deleted.heldUntil ?? "held for good");

A partir daí, o nosso servidor de e-mail recusa os e-mails para o endereço com um erro permanente (um 550), e o servidor de quem enviou avisa que o endereço não existe. Os e-mails que a inbox guardava são apagados junto, em até uma hora. A inbox deixa de contar nas inboxes e no armazenamento do seu plano.

O endereço fica reservado. heldUntil é o dia em que outro workspace pode usá-lo, 90 dias depois da exclusão. Sem essa reserva, um e-mail que era para você poderia chegar a outra pessoa. Você mesmo pode criar o endereço de novo a qualquer momento. Em um domínio nosso, o endereço fica reservado para sempre, e heldUntil vale null.

Excluir é a única forma de impedir que cheguem e-mails a um endereço. Pausar interrompe apenas as respostas.

Chaves e e-mails não confiáveis

Tudo em uma mensagem foi escrito por quem a enviou: o assunto, o nome de quem enviou e o corpo. O agente trata isso como dados, nunca como instruções nem como aprovação para fazer algo. Ler uma mensagem não dá permissão nenhuma.

Uma inbox de agente só responde a e-mails que chegaram. Ela nunca escreve primeiro para quem não escreveu para ela.

Uma chave com read lê todas as inboxes do workspace, a menos que um dono tenha dado a ela uma lista de inboxes para ler. Dê a cada agente só a lista de que ele precisa e não entregue nenhuma chave a um código que você não executa.