Visão geral das Jornadas
As Jornadas são o recurso para criar serviços de impacto na Turn.io. As Jornadas foram fortemente inspiradas pelas ideias originais do Hypercard e do Hypertalk.
As Jornadas eram previamente chamadas de stacks. As palavras "stacks" e "jornadas" podem ser usadas de forma intercambiável em nossa documentação, e você ainda pode ver o uso de stacks em alguns nomes de função.
Na Turn.io, as Jornadas são contêineres que contêm um ou mais cards e os relacionamentos entre eles. Os cards executam as diversas etapas que compõem seu serviço de impacto usando funções e expressões.
As funções e expressões disponíveis são descritas abaixo, com exemplos e erros comuns.
Linguagem de codificação das Jornadas
Servi ços de impacto construídos usando jornadas são descritos usando uma linguagem de codificação personalizada. Às vezes, nos referimos a ela como a DSL das Jornadas (Domain Specific Language).
Vamos analisar a estrutura e a sintaxe da linguagem de código.
Bloco de código da Jornada
Pense em uma jornada como um contêiner que contém vários cards.
Exemplo
O exemplo a seguir cria uma jornada vazia para fins ilustrativos.
stack JourneyName do
...
end
Pontos importantes
- Sua jornada deve ter um nome.
- O comando
do
inicia a jornada. - O comando
end
conclui a jornada. - Toda a lógica que a jornada deve executar será adicionada entre os comandos do e end.
Você pode omitir o stack do ... end
, fazendo com que o sistema assuma implicitamente que uma única jornada descreve todos os cards definidos.
Bloco de código do Card
Pense em um card como executando uma ou mais etapas em seu serviço de impacto. Os cards fazem uso de várias funções e expressões para descrever essas etapas.
Exemplo
O exemplo a seguir cria um card vazio para fins ilustrativos.
card CardName do
...
end
Pontos importantes
- A estrutura de código para criar um card é semelhante à estrutura de código para criar uma jornada.
- Seu bloco de código de card sempre será definido dentro de um bloco de código de jornada.
- Seu card deve ter um nome.
- O comando
do
inicia o card. - O comando
end
conclui o card. - Toda a lógica do card será adicionada entre os comandos do e end.
- A lógica consistirá em funções, expressões etc. Veja os exemplos abaixo.
Usando comentários
Qualquer linha de texto em sua jornada prefixada com um sinal # se tornará um comentário. Isso significa que não será uma linha de código executável, sendo ignorada quando a jornada for executada.
card MyCard do
# Este é um comentário
end
Documentando suas Jornadas
Comentários de código são ótimos para documentar linhas individuais dentro de um Card, mas às vezes podem ser limitados. Quando se deseja descrever preocupações mais amplas, como fornecer mais detalhes sobre decisões específicas de design, fazer links para documentação ou pesquisa externas, ou descrever detalhes-chave de implementação, os blocos de documentação em Markdown são o ideal.
Markdown é um formato de texto simples usado para fornecer marcações adicionais ao texto puro. Há um ótimo tutorial de Markdown disponível em https://www.markdowntutorial.com que cobre os detalhes; também existe o Markdown Cheat Sheet se você estiver procurando um guia de referência rápida.
A ideia por trás de combinar cards, para descrever a lógica, e Markdown, para descrever o processo de pensamento e as informações de fundo, é que essas jornadas se tornarão autodescritivas ao longo do tempo. Na verdade, todo o seu serviço de impacto em si é descrito como um grande arquivo de texto em Markdown.
Entre seus cards, você pode adicionar quantos blocos de Markdown forem necessários para descrever a lógica do seu serviço de impacto.
Sua primeira jornada
Agora que sabemos que uma jornada contém um ou mais cards, e cada card contém lógica que descreve seu serviço de impacto, vamos juntar tudo. O exemplo mostra como blocos de jornada e blocos de card se encaixam para criar uma jornada muito simples.
Exemplo
O exemplo a seguir cria uma jornada simples que envia uma única mensagem de texto para um usuário, dizendo "Hello World!". Note como o bloco do card fica dentro do bloco da jornada.
stack MyJourney do
card MyCard do
text("Hello world!")
end
end
Relacionando cards
Os cards descrevem as etapas do seu serviço de impacto. Cada card descreve um ponto específico em uma série de interações.
O primeiro card é sempre o que a jornada inicia. Depois disso, a ordem em que os cards aparecem no código da jornada não é importante. O que é importante é descrever os relacionamentos entre os cards, pois isso determina como os usuários navegarão entre eles.
Existem duas formas principais de descrever o que acontece após cada card, ou seja, a then
ou a condição when
.
A condição then
O relacionamento then
é usado para conectar um card a outro.
Sintaxe
card FirstCard, then: NextCard do
...
end
Exemplo
Este exemplo mostra como usar a palavra-chave then:
para indicar quando um card deve ser imediatamente seguido por outro card. Neste caso, duas mensagens de texto serão enviadas uma após a outra, primeiro "this is the first card"
e depois "this is the second card"
.
card One, then: Two do
text("this is the first card")
end
card Two do
text("this is the second card")
end
Pontos importantes
- Sem
then:
, apenas a primeira mensagem de texto será enviada.
A condição when
A cláusula when é usada para adicionar condições que controlam a ordem de execução de um card.
Sintaxe
card CardName when some_condition do
...
end
Observe que com a sintaxe then
usamos uma vírgula e dois pontos (CardName, then:
).
No entanto, when
deve ocorrer imediatamente após o cardName
sem vírgula ou dois pontos.
Operadores Booleanos
Esta é uma lista das funções de comparação que podem ser usadas nas Jornadas:
<
- menor que>
- maior que>=
- maior ou igual a<=
- menor ou igual a=
- igual a==
- igual a!=
- diferente de<>
- diferente de
Eles podem ser combinados com os operadores padrão and
e or
, bem como a palavra-chave not
.
Você pode usar parênteses para determinar a ordem de avaliação:
(true or false) and false
será avaliado comofalse
true or (false and false)
será avaliado comotrue
Por fim, no que diz respeito a valores truthy e falsey:
- variáveis que são nil ou null serão avaliadas como
false
- simplesmente verificar se uma variável não é o valor booleano
true
resultará emfalse
. Então qualquer uso dewhen some_variable do
será avaliado como false. Caso contrário, você deve incluir uma comparação. Por exemplo, algumas linguagens podem declarar que uma string não vazia ou uma lista não vazia é avaliada como verdadeiro, mas não é o caso nas Jornadas.
Se você não tiver certeza sobre o valor de uma expressão, pode usar a função log
para visualizar a saída no simulador.
Exemplo
O exemplo a seguir elabora um pouco o exemplo anterior, introduzindo condições entre cards.
Você notará que o card One
segue para o card Two
usando a palavra-chave then:
. Uma diferença importante aqui é que estamos usando a função ask()
, que aguarda a entrada do usuário antes de prosseguir.
O card Two
é definido várias vezes, dois deles têm condições que protegem o card. Dependendo da resposta do usuário, o sistema selecionará automaticamente o card Three
para o qual a condição seja avaliada como true
. O último card Two
não tem condição, o que significa que sempre será avaliado como true
, funcionando como um fallback padrão.
card One, then: Two do
text("this is the second card")
age = ask("What is your age?")
end
card Two when age > 18 do
text("Hello boomer")
end
card Two when age > 16 and age <= 18 do
text("Hey there!")
end
card Two do
text("This service is too cool for you")
end
Gatilhos (Triggers)
Os Gatilhos (Triggers) são definidos no início da sua jornada e especificam quando sua jornada deve ser executada. Por exemplo, você pode usar o Gatilho (Trigger) a seguir para iniciar sua jornada quando um usuário enviar a mensagem "hi" para o seu serviço:
trigger(on: "MESSAGE RECEIVED") when has_phrase(event.message.text.body, "hi")
card FirstCard do
text("Welcome!")
end
Uma vez acionada (triggered), a jornada começará a ser executada a partir de seu primeiro card.
Um Gatilho (Trigger) é composto por um evento, como MESSAGE RECEIVED
, e uma expressão opcional (especificada após a palavra-chave when
) que pode ser usada para corresponder às propriedades da mensagem recebida e/ou do contato.
Existem dois tipos de Gatilhos (Triggers):
- Mensagens Gatilho de Entrada: estes Gatilhos (Triggers) são executados quando um usuário envia uma mensagem para o seu serviço.
- Gatilhos Temporais: estes Gatilhos (Triggers) são executados em uma data/hora específica ou algum tempo antes/depois de um evento específico (como "2 horas após o cadastro"). Eles também podem ser configurados para serem executados periodicamente em uma programação (como "toda segunda-feira às 15:00").
Dividimos os aspectos de código dos gatilhos nas seções a seguir, mas você também pode aprender fazendo: crie um gatilho (trigger) no canvas sem código e depois mude para a visualização de código para ver o código gerado. Observe que o código é somente leitura e você não pode editá-lo diretamente:
Mensagens Gatilho de Entrada
Esses Gatilhos (Triggers) são executados quando um contato envia uma mensagem para o seu serviço. Os seguintes eventos são suportados:
MESSAGE RECEIVED
: aciona quando um contato envia uma mensagem para o seu serviço.FIRST TIME
: aciona quando um contato envia uma mensagem para o seu serviço pela primeira vez.CATCH ALL
: aciona se nenhum Gatilho (Trigger) (ou Automação) tratou a mensagem recebida.
Você pode refinar ainda mais o Gatilho (Trigger) fornecendo uma expressão que corresponda às propriedades da mensagem recebida e/ou do contato que enviou a mensagem.
O exemplo a seguir corresponde a mensagens que contêm a palavra "hi", mas somente para contatos que optaram por participar (opted in):
trigger(on: "MESSAGE RECEIVED")
when has_phrase(event.message.text.body, "hi") and contact.opted_in == true
card FirstCard do
text("Welcome opted-in user!")
end
Na expressão, você pode se referir à mensagem recebida com event.message
(e seu conteúdo com event.message.text.body
) e ao contato que enviou a mensagem com contact
. Para se referir a campos de perfil de contato, você pode usar a sintaxe contact.field_name_here
.
As seguintes Expressões são suportadas em Mensagens Gatilho de Entrada:
- has_all_members
- has_any_member
- has_any_beginning
- has_any_end
- has_any_exact_phrase
- has_any_phrase
- has_all_words
- has_any_word
- has_beginning
- has_date
- has_date_eq
- has_date_gt
- has_date_lt
- has_email
- has_end
- has_group
- has_member
- has_number
- has_number_eq
- has_number_gt
- has_number_gte
- has_number_lt
- has_number_lte
- has_only_phrase
- has_only_text
- has_pattern
- has_phone
- has_phrase
- has_text
- has_time
- isbool
- is_nil_or_empty
- isnumber
- isstring
Todos os operadores booleanos e sintaxe discutidos para declarações when
são suportados em um gatilho de mensagem.
Gatilhos Temporais
Os Gatilhos Temporais são executados em uma data/hora específica ou algum tempo antes/depois de um evento específico (como "2 horas após o cadastro"). Eles também podem ser configurados para serem executados periodicamente em um cronograma (como "toda segunda-feira às 15:00").
Os seguintes Gatilhos Temporais são suportados:
- Data específica: aciona em um datetime específico, por exemplo:
trigger(at: "2024-09-13T15:45:00Z")
. - Recorrente: aciona em uma programação recorrente de estilo cron, por exemplo:
trigger(every: "30 15 * * MON")
. - Relativo a um campo de perfil: aciona algum tempo antes/depois do valor datetime contido em um campo de perfil de contato, por exemplo:
trigger(interval: "+3d", relative_to: "contact.due_date")
.
Assim como nos Mensagens Gatilho de Entrada, é possível refinar ainda mais o Gatilho (Trigger) fornecendo uma expressão após a palavra-chave when
. A expressão atua como um filtro para seus contatos: o gatilho (trigger) será executado para todos os contatos para os quais a expressão retornar true
.
Como exemplo, o seguinte Gatilho (Trigger) será executado todas as terças-feiras às 10:30 para todos os contatos que tiverem o campo de perfil is_pregnant
definido como true
:
trigger(every: "30 10 * * TUE") when contact.is_pregnant == true
As seguintes Expressões são suportadas em Gatilhos Temporais:
Todos os operadores booleanos e sintaxe discutidos para declarações when
são suportados em um gatilho temporal.
Uma observação importante sobre modelos (templates) de mensagem: ao usar Gatilhos Temporais, muitas vezes é difícil dizer se o gatilho (trigger) será executado dentro da janela de serviço de 24 horas do WhatsApp para um contato. Como o WhatsApp só permite o envio de modelos (templates) de mensagem fora da janela de serviço de 24 horas, recomendamos fortemente o uso de modelos (templates) de mensagem (em vez de mensagens normais) em jornadas que usem Gatilhos Temporais.
Gatilhos de data específica
Gatilhos de data específica serão executados na datetime especificada para todos os contatos para os quais a expressão fornecida for avaliada como true
.
O datetime é fornecido com o parâmetro at:
e deve ser uma string datetime válida em formato ISO8601.
Por exemplo, a jornada a seguir será acionada às 15:45 em 13 de setembro de 2024 para todos os contatos que tiverem o campo opted_in
definido como true
ou o campo registration_status
definido como "completed"
.
trigger(at: "2024-09-13T15:45:00Z") when
contact.opted_in == true or contact.registration_status == "completed"
Gatilhos Recorrentes
Gatilhos recorrentes serão executados em uma programação recorrente especificada usando uma cron schedule expression.
Recomendamos usar o site Crontab Guru para construir e validar sua expressão cron. Observe que as expressões cron serão avaliadas no fuso horário UTC.
Também é possível especificar uma data de término para o gatilho recorrente informando o parâmetro opcional until
. O parâmetro until
precisa ser um valor datetime válido no formato ISO8601.
Por exemplo, o seguinte gatilho será executado toda segunda-feira às 15:30 (até 13 de setembro de 2025) para todos os contatos que tiverem o campo language
definido como qualquer valor diferente de "en"
.
trigger(every: "30 15 * * MON", until: "2025-09-13T15:45:00Z") when
not has_phrase(contact.language, "en")
Gatilhos relativos a um campo de perfil
Esses gatilhos são executados em um intervalo de tempo especificado em relação ao valor datetime contido em um campo de perfil.
Por exemplo, podem ser usados para acionar uma jornada 2 horas após um contato se inscrever em um programa:
trigger(interval: "+2h", relative_to: "contact.enrolled_at") when
has_phrase(contact.project_enrolled, "project_a")
O intervalo de tempo relativo é fornecido como o argumento interval
e deve ser uma string composta pelos seguintes elementos:
- Sinal
+
ou-
, onde+
significa depois e-
significa antes. - Um inteiro
- Uma unidade de tempo das seguintes suportadas:
m
para minutos.h
para horas.d
para dias.w
para semanas.M
para meses.
O campo de contato deve ser do tipo data e deve ser especificado como o argumento relative_to
usando a sintaxe "contact.field_name"
.
O exemplo a seguir envia uma mensagem ao usuário 3 dias antes do aniversário dele:
trigger(interval: "-3d", relative_to: "contact.birthday")
card UpcomingBirthday do
send_message_template("birthday_3d_before", "en", [])
end
Também é possível substituir a hora em que o gatilho (trigger) deve ser executado usando a opção target_time
(especificada como uma string de hora UTC ISO8601 válida). Isso é útil quando se usam unidades de intervalo como dias/semanas/meses para evitar enviar mensagens em horários inconvenientes.
Por exemplo, o seguinte Gatilho (Trigger) será executado 2 semanas antes do valor datetime do campo due_date
do contato, exatamente às 15:30:
trigger(interval: "-2w", relative_to: "contact.due_date", target_time: "15:30:00") when
contact.is_expecting == true
Variáveis
Como em muitas linguagens de programação, variáveis podem ser usadas para armazenar valores. Todas as variáveis declaradas em uma jornada estão disponíveis para todos os cards da jornada, e não apenas no card em que a variável foi criada.
Os valores atribuídos às variáveis estão disponíveis apenas durante a execução da jornada e serão perdidos quando o usuário terminar de interagir com a jornada. Se você precisar salvar dados para recuperação posterior (por exemplo, para recuperá-los de outras jornadas no futuro), consulte sobre contato profiles.
card CardOne, then CardTwo do
first = 2
end
card CardTwo, then CardThree do
second = 3
result = first + second
end
card CardThree do
# Isso envia a mensagem "The result is: 5"
text("The result is: @result")
end
Tabelas de conteúdo (Content Tables)
Um caso de uso comum ao escrever jornadas é armazenar algum conteúdo de forma que seja fácil interagir com ele a partir de um card.
Um exemplo é um questionário de 10 perguntas. A lógica descrita nos cards não precisa mudar se o conteúdo for atualizado para ter menos ou mais perguntas. Simplesmente queremos percorrer todas as perguntas disponíveis, uma a uma, até que todas sejam respondidas.
Content Tables são ótimas para esses tipos de casos. Elas têm um nome exclusivo e funcionam como uma planilha, permitindo que se especifiquem colunas com nomes e linhas para cada.
Dentro de um card, as linhas da content table podem ser lidas uma a uma usando Expressions.
As tabelas têm um atributo de lista chamado rows
que dá acesso a todas as linhas da tabela. Cada valor de linha individual pode ser acessado usando o nome da coluna em minúsculas.
card TextCard do
row = table_0.rows[0]
text("""
The value of the first rows is: @row.column_1, @row.column_2, and @row.column_3
""")
end
Parâmetros
Frequentemente, ao escrever jornadas, haverá partes comuns de texto e valores que são reutilizadas entre os cards. Pense em coisas como o texto de boas-vindas em um botão que leva de volta ao menu principal, por exemplo.
Muitas vezes é desejável apresentar esses elementos comuns em uma interface facilmente editável fora de objetos de código em uma jornada.
Parameters são ótimos para isso, pois permitem atribuir um nome a um elemento comumente reutilizado e fornecer um valor padrão para ele. Ao escrever cards, você pode então se referir a esses valores pelo nome usando Expressions
Os parâmetros são semelhantes às Content Tables, exceto que são otimizados para a leitura de valores individuais em vez de linhas combinadas.
Os valores em parameters são armazenados em um atributo chamado items
, de onde as entradas individuais podem ser lidas pelo nome em minúsculas.
card TextCard do
text("""
@parameters_0.items.welcome_text
@parameters_0.items.intro
""")
end
Uso de texto multilinha
O texto multilinha é delimitado por aspas triplas ("""
) e permite representar facilmente texto que se estende por várias linhas.
card QuestionCard do
ask("""
Welcome!
Is "Jane" your name?
""")
end
Ele também permite que você use aspas ("
) dentro sem precisar escapá-las:
card Card do
data = parse_json("""
{
"my": "json",
"structure": [1, 2, 3]
}
""")
end
Aspas simples não são mais suportadas para valores de string. Isso era suportado anteriormente, mas, de agora em diante, gerará um erro ao salvar uma jornada.
Se você estiver usando aspas simples como no exemplo a seguir:
map(0..10, &concatenate(&1, ', ')
Use aspas duplas em vez disso:
map(0..10, &concatenate(&1, ", ")
Se você precisar usar aspas em uma string, pode escapá-las com uma barra invertida: \"
.
map(0..10, &concatenate(&1, "\","))
O bloco de código avisará quando você estiver usando aspas simples na próxima vez que você salvar seu código:
Erros comuns
- Todas as linhas, incluindo as aspas triplas, devem estar indentadas no mesmo nível, conforme o exemplo abaixo.
- Não coloque nenhum texto na mesma linha que as aspas triplas. Por exemplo, não faça
ask("""Welcome
, apenasask("""
e o texto na linha seguinte.
Formatando texto
O WhatsApp permite que você formate seu texto em negrito, itálico, tachado e monospace.
Formato | Sintaxe | Exemplo |
---|---|---|
Itálico | coloque um sublinhado (_) em ambos os lados do texto | _seu texto_ |
Negrito | coloque um asterisco (*) em ambos os lados do texto | *seu texto* |
Tachado | coloque uma til (~) em ambos os lados do texto | ~seu texto~ |
Monospace | coloque três crases (```) em ambos os lados do texto | seu texto |
Desempenho da Jornada
Ao criar jornadas, incentivamos manter em mente o desempenho; criando serviços rápidos e responsivos para seus usuários finais no WhatsApp. Uma das coisas que fazemos para incentivar um serviço responsivo é definir um tempo limite de 30 segundos para que as jornadas concluam uma ação em resposta à mensagem de um usuário. Por exemplo, se você depende de várias chamadas de webhook externas para complementar seu serviço, certifique-se de que elas respondam dentro de um tempo razoável para permitir que as conversas fluam de forma natural.