O formato
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 — umexternal_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. Umemail 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:
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
bootacha, 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_verifiednão está no caminho: o HMAC prova oexternal_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.
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 colunacustomer 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 com413, 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 publicacontact.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.