Code Library

M · UDF

fnEmailAnonimo

Mascara um e-mail trocando por * uma parte dos caracteres da parte local e do nome do domínio, em posições pseudoaleatórias e estáveis entre refreshes.

Categoria: Máscara de dadosTipo: UDFVersão: v1.2Fonte: Paulo André Jesus — OfficeTuningAtualizado em: 27/09/2026
Documentação completa na Function Library →

Sintaxe

fnEmailAnonimo(email as nullable text, optional proporcao as nullable number) as nullable text

Argumentos

NomeTipoObrigatórioDescrição
emailnullable textSimE-mail a mascarar.
proporcaonullable numberNãoFração dos caracteres elegíveis que vira *. De 0.1 a 1; padrão 0.5.

Retorno

nullable text

Código completo

let
    fnEmailAnonimo = (email as nullable text, optional proporcao as nullable number) as nullable text =>
        let
            vEmail = Text.Lower(Text.Trim(email ?? "")),
            vPartes = Text.Split(vEmail, "@"),
            // Exatamente um "@", parte local preenchida e domínio com ponto (não no início)
            // "and" para no primeiro false: vPartes{1} só é lido quando há 2 partes
            vValido = List.Count(vPartes) = 2 and vPartes{0} <> "" and Text.PositionOf(vPartes{1}, ".") > 0,
            // Proporção por parte: padrão 50%, limitada entre 10% e 100%
            vProporcao = List.Max({0.1, List.Min({1, proporcao ?? 0.5})}),

            // Mascara um trecho mantendo o 1º caractere e os separadores (. - _ +).
            // A semente vem do PRÓPRIO trecho: o mesmo domínio recebe sempre a mesma máscara,
            // e comparar linhas não revela as letras escondidas.
            fnMascarar = (trecho as text) as text =>
                let
                    vChars = List.Buffer(Text.ToList(trecho)),
                    vElegiveis = List.Select(
                        List.Numbers(1, List.Count(vChars) - 1),
                        each not List.Contains({".", "-", "_", "+"}, vChars{_})
                    ),
                    vTotal = List.Count(vElegiveis),
                    vQtd = List.Min({vTotal, List.Max({1, Number.RoundUp(vTotal * vProporcao)})}),
                    vSemente = Number.Mod(
                        List.Sum(List.Transform(List.Positions(vChars), each Character.ToNumber(vChars{_}) * (_ + 1))),
                        2147483647
                    ),
                    // Embaralha as posições elegíveis e pega as vQtd primeiras
                    vSorteados = List.Buffer(
                        List.Transform(
                            List.FirstN(List.Sort(List.Zip({vElegiveis, List.Random(vTotal, vSemente)}), each _{1}), vQtd),
                            each _{0}
                        )
                    )
                in
                    if vTotal = 0 then trecho   // nada a mascarar (ex.: "a")
                    else Text.Combine(
                        List.Transform(List.Positions(vChars), each if List.Contains(vSorteados, _) then "*" else vChars{_})
                    ),

            vLocal = vPartes{0},
            vDominio = vPartes{1},
            vPosPonto = Text.PositionOf(vDominio, "."),
            vNomeDominio = Text.Start(vDominio, vPosPonto),   // "empresa" em "empresa.com.br"
            vSufixo = Text.Middle(vDominio, vPosPonto)         // ".com.br" fica visível
        in
            if not vValido then null
            else fnMascarar(vLocal) & "@" & fnMascarar(vNomeDominio) & vSufixo,

    fnEmailAnonimoTipo = type function (
        email as (type nullable text meta [
            Documentation.FieldCaption = "E-mail",
            Documentation.FieldDescription = "Endereço a mascarar. Espaços nas pontas são removidos; e-mail inválido retorna null."
        ]),
        optional proporcao as (type nullable number meta [
            Documentation.FieldCaption = "Proporção",
            Documentation.FieldDescription = "Fração dos caracteres elegíveis de cada parte que vira * (0.1 a 1; padrão 0.5)."
        ])
    ) as nullable text meta [
        Documentation.Name = "fnEmailAnonimo",
        Documentation.Description = "Mascara um e-mail substituindo caracteres por * em posições pseudoaleatórias.",
        Documentation.LongDescription = "Mascara separadamente a parte local e o nome do domínio, cada um com semente derivada do próprio trecho: o mesmo domínio recebe sempre a mesma máscara, e o resultado é estável entre refreshes. Mantém o 1º caractere de cada parte, o @, os separadores (. - _ +) e o sufixo após o primeiro ponto do domínio. A saída é em minúsculas. E-mail inválido retorna null.",
        Documentation.Category = "Máscara de dados",
        Documentation.Author = "Paulo André Jesus",
        Documentation.Version = "1.2",
        Documentation.Examples = {
            [Description = "Padrão (50%)", Code = "fnEmailAnonimo(""paulo.jesus@officetuning.com.br"")", Result = "Metade dos caracteres elegíveis de cada parte vira *; o domínio sai igual em todas as linhas"],
            [Description = "Máscara mais forte", Code = "fnEmailAnonimo(""paulo.jesus@officetuning.com.br"", 0.8)", Result = "80% dos caracteres elegíveis de cada parte viram *"],
            [Description = "Nada elegível", Code = "fnEmailAnonimo(""a@b.com"")", Result = """a@b.com"""],
            [Description = "Inválido", Code = "fnEmailAnonimo(""paulo.officetuning.com"")", Result = "null"]
        }
    ]
in
    Value.ReplaceType(fnEmailAnonimo, fnEmailAnonimoTipo)

Exemplos

fnEmailAnonimo("paulo.jesus@officetuning.com.br")
// metade dos caracteres elegíveis de cada parte vira *; o domínio sai igual em todas as linhas

fnEmailAnonimo("a@b.com")
// "a@b.com"  (nada a mascarar)

fnEmailAnonimo("paulo.officetuning.com")
// null  (sem @)

Dicas e observações

⚠️ Até a versão 1.1, a semente vinha do e-mail inteiro: o mesmo domínio saía com máscaras diferentes em cada linha, e cruzando poucas linhas dava para reconstruí-lo. Num modelo de RH real, um único domínio aparecia com 1.911 máscaras distintas. A 1.2 mascara parte local e domínio com sementes próprias. 💡 Ficam visíveis o 1º caractere de cada parte, o @, os separadores (. - _ +) e tudo após o primeiro ponto do domínio. 💡 A proporção vale para cada parte, com no mínimo um * em cada uma. ⚠️ Só o trecho antes do primeiro ponto do domínio é mascarado: em joao@mail.empresa.com.br o nome da empresa fica à vista. ⚠️ Mascarar não é anonimizar: com a coluna de nome aberta ao lado, a parte local continua identificável. Oculte o nome (OLS) ou suba a proporção. 💡 A saída vem em minúsculas. Trocar de versão muda todas as máscaras já geradas.