Skip to main content
Você já tem usuários. Trazê-los para cá é um upload, e esta página fala principalmente das duas coisas que a importação se recusa a fazer no seu lugar, porque as duas são coisas que você não desfaz depois.

O formato

A resposta é 202, não 201: o arquivo foi aceito e está durável, e nada dele foi aplicado ainda. Um worker aplica as linhas em lotes; consulte GET /v1/organization/contact-imports/{id} e os números vão se preenchendo. environment é obrigatório aqui. Todas as outras leituras desta superfície assumem live por padrão, e esta se recusa a assumir — ler o ambiente errado é um clique perdido, e dez mil contatos escritos no ambiente errado não se desescrevem. O painel faz a mesma coisa em Contatos → Importar, e nomeia o ambiente na confirmação pelo mesmo motivo.

Colunas

email e external_id são os identificadores. Seu arquivo precisa de pelo menos uma das duas colunas; sem nenhuma, toda linha falharia pelo mesmo motivo, então o arquivo é recusado em vez de virar um relatório de falhas idênticas. O casamento ignora maiúsculas e lê espaços, hífens e pontos como underscores, de modo que First Name e first_name são a mesma coluna. O que a tabela não nomeia é ignorado — um export de verdade traz uma dúzia de colunas que aqui não têm onde morar. id vira external_id de propósito: é como a maioria dos provedores chama a própria chave primária, e daqui é exatamente isso que ela é.

O que uma linha faz

Ninguém com essa identidade ainda — o contato é criado com tudo que a linha trouxe, atribuição de primeiro toque incluída. Vale notar: este é o único momento em que um contato nasce muito depois da visita, então o arquivo pode dizer de onde a pessoa veio em vez de inventarmos “csv” para ela. Alguém que já está aqui — a linha casa com essa pessoa e não mexe no perfil dela. O export do seu provedor anterior não é evidência sobre um contato que este ambiente já tem: o nome e o endereço dele são para onde vão os avisos de segurança dele, e uma linha desatualizada não é motivo para redirecioná-los. Se a linha trouxer um identificador livre — um external_id para quem só conhecíamos por e-mail —, essa aresta é anexada. Uma linha que nomeia duas pessoas diferentes — reportada como conflict, e nada é escrito por ela. Essa última é a decisão em torno da qual o recurso inteiro foi construído, então merece um título.

Uma colisão é reportada, nunca resolvida

Tipos identificadores nunca reapontam. Um email ou um external_id que já pertence a outro contato é uma duplicata para fundir à mão, e não existe desfazer fusão — o registro da fusão existe justamente para que uma fusão errada seja consertada à mão, e não por restore. Uma importação de dez mil linhas é a maneira mais rápida já inventada de criar fusões erradas em lote. Então ela não cria nenhuma. Quando o email de uma linha pertence a um contato e o external_id pertence a outro, a linha cai no relatório como conflict nomeando os dois ids, e nada é escrito:
Os dois ids estão ali porque o conserto exige dois: passe-os para POST /v1/organization/contacts/{id}/merge{id} é a linha que desaparece, target_id no corpo é a sobrevivente — e importe aquela linha de novo. Um relatório nomeando só um do par seria um relatório sobre o qual ninguém consegue agir.

Uma importação não prova um endereço

email_verified num contato quer dizer que alguém provou que lê e-mail naquele endereço: mandamos algo para lá e a pessoa voltou. Um arquivo não produz isso. Então todo contato importado chega com email_verified: false, e não existe parâmetro, header ou coluna que mude isso. Uma coluna email_verified no CSV é recusada pelo nome, com um 422 explicando o porquê — recusada em vez de ignorada, porque você a colocou ali por um motivo e o silêncio deixaria você acreditar que conseguiu. Isso importa mais do que parece, porque email_verified é o que a claim de um JWT de sessão carrega e o que o guard <Verified> do SDK lê. No momento em que uma importação pudesse definir esse campo, os dois passariam a ser afirmações sobre uma planilha. O que deixa a pergunta honesta: o que acontece com as pessoas que você acabou de importar?
  • Elas estão identificadas. O boot acha, o login acha, o <SignedIn> vê, e elas aparecem na sua lista de contatos. Não estar verificado não é ser desconhecido.
  • O primeiro link mágico ou código por e-mail enviado àquele endereço o prova e liga o marco — de forma permanente, no primeiro uso. Esses dois fluxos existem exatamente para isso, que é também o motivo de funcionarem nos dois modos. Um único “entrar com um link” é todo o seu backfill de verificação, uma pessoa por vez, pago pelas pessoas que de fato voltam.
  • No modo federado, email_verified não está no caminho: o HMAC prova o external_id, e o endereço é um atributo. Se o seu produto mantém o próprio login, importar não muda nada sobre quem entra.
  • Se hoje o <Verified> protege algo que os seus importados precisam no primeiro dia, proteja com <SignedIn> e coloque a prova de endereço onde ela pertence — na frente das telas onde o dado de outra pessoa poderia vazar. É essa a fronteira para a qual o <Verified> foi desenhado.
Senhas seguem a mesma postura: password e password_hash são recusadas pelo nome. Migrar credencial é um projeto próprio, e derrubar a coluna em silêncio é como uma migração descobre no dia do lançamento que ninguém consegue entrar.

Nada é enviado por e-mail

Nenhuma mensagem, de nenhum tipo, para ninguém do arquivo. Uma importação é a maneira mais rápida de mandar dez mil e-mails para endereços que nunca ouviram falar de você, e um desses acaba com a reputação de um domínio de envio — o seu e o de todos os outros na plataforma. O relatório é onde o resultado mora. Se você quer que as pessoas importadas ouçam de você, isso é um ato deliberado que você faz depois, no ritmo que o seu domínio de envio aguenta.

Linhas ruins são reportadas, não fatais

Uma linha ilegível não interrompe a importação. Parar na linha 4.312 é inútil para quem está migrando; ignorar em silêncio é pior, porque você terminaria acreditando que todo mundo chegou.
Tudo, exceto identity_conflict e apply_failed, é decidido enquanto o arquivo é lido — por isso as falhas já estão no relatório quando o upload responde.

Times

Uma coluna customer coloca pessoas em times. O nome é casado dentro do ambiente — é a única alça que uma planilha tem sobre um time — e um time que não existe é criado. O mais antigo ganha quando dois times têm o mesmo nome, para que importar o mesmo arquivo duas vezes convirja num time em vez de bifurcar outro. A primeira pessoa a entrar num time recém-criado vira owner, a menos que o arquivo diga outra coisa: um time precisa ter um, e o arquivo oferece exatamente um candidato. Quem já está num time mantém o papel que já tem — um arquivo pode colocar uma pessoa num time, não pode mudar o papel dela.

Rodar duas vezes é seguro

Nada deduplica pelos bytes do arquivo, então subir o mesmo CSV duas vezes produz duas importações. E não produz uma segunda audiência: cada linha casa com o contato que a primeira execução criou, então o segundo relatório lê matched de cima a baixo. Essa mesma propriedade é o que faz uma importação interrompida ser retomável em vez de reiniciada. O progresso vive nas linhas, não numa mensagem — um worker que morre no meio do arquivo deixa as linhas aplicadas terminais e o resto pendente, e a passada seguinte continua de onde parou.

Tamanho

Um upload carrega até 50.000 linhas ou 8 MiB, o que vier primeiro. Passando de qualquer um dos dois o arquivo é recusado inteiro com 413, e este é o único lugar em que recusar o arquivo em vez da linha está certo: não há nada a relatar sobre uma linha que nunca foi lida. Divida e importe as partes.

Eventos

Cada contato que a importação cria publica contact.identified, o mesmo fato que todas as outras portas publicam — então suas automações não precisam saber que houve uma importação. Quando a última linha fica terminal, a importação publica contact_import.completed uma vez, com os números. Se você tem um endpoint de webhook e quer o tiro de partida sem dez mil fatos individuais no dia da migração, inscreva-o nesse tipo e não em contact.identified.