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. Um único “entrar com um link” é todo o seu backfill de verificação, uma pessoa por vez, pago pelas pessoas que de fato voltam.
email_verifiednão está no caminho do login: o HMAC prova oexternal_id, e o endereço é um atributo. O seu produto mantém o próprio login, então 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.
Senha não é coluna
O UserKit nunca autentica os seus usuários, então um arquivo não tem onde pôr uma senha. Uma coluna de hash é recusada pelo nome, não ignorada —password_hash, passwordhash, password_digest, encrypted_password,
hashed_password, e as colunas de algoritmo ao lado, password_hasher,
password_algorithm, hash_algorithm — com unimportable_column, porque uma
coluna descartada em silêncio é uma migração que alguém acredita ter levado a
credencial, descoberta no primeiro login que não funciona. Uma coluna
password em texto puro é recusada dê no que der, e é a única recusa aqui que é
sobre o arquivo: uma coluna com a senha de todo mundo em claro não tem versão
de si mesma que esteja tudo bem. O seu login fica onde está; a importação é sobre
quem essas pessoas são, não sobre como elas entram.
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 a maioria das falhas já está no relatório quando o
upload responde. Essas duas precisam do banco: elas são sobre quem já está aqui,
não sobre o que a linha diz.
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.