snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
---
title: Gestire gli array nelle azioni Code
description: Scopri come gestire correttamente gli input di tipo array nelle azioni Code dei workflow.
---
Quando lavori con gli array nelle azioni Code, potresti incontrare due problemi comuni:
1. **Array passati come stringhe** — i dati provenienti da sistemi esterni o da passaggi precedenti arrivano come stringa invece che come un vero array
2. **Impossibile selezionare singoli elementi** — puoi selezionare solo l'intero array, non i campi specifici al suo interno
Entrambi i casi si possono risolvere con un nodo Code.
## Analizzare gli array dalle stringhe
Gli array vengono spesso passati tra i passaggi del workflow come stringhe o JSON invece che come array nativi. Ciò accade quando:
* Ricezione di dati da API esterne tramite HTTP Request
* Elaborazione dei payload dei webhook
* Passaggio di dati tra i passaggi del workflow
**Soluzione**: aggiungi questo pattern all'inizio della tua azione Code:
```javascript
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
// Handle input that may come as a string or an array
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Now you can safely work with usersFormatted as an array
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
})),
};
};
```
La riga chiave `typeof users === "string" ? JSON.parse(users) : users` verifica se l'input è una stringa, lo analizza se necessario o lo usa direttamente se è già un array.
## Estrazione di campi specifici dagli array
Un webhook potrebbe restituire un array come `answers: [...]`, ma nei passaggi successivi del workflow puoi selezionare solo l'**intero array** — non i singoli elementi al suo interno.
**Soluzione**: aggiungi un nodo Code per estrarre campi specifici e restituirli come oggetto strutturato:
```javascript
export const main = async (params: {
answers: any;
}): Promise<object> => {
const { answers } = params;
// Handle input that may come as a string or an array
const answersFormatted = typeof answers === "string"
? JSON.parse(answers)
: answers;
// Extract specific fields from the array
const firstname = answersFormatted[0]?.text || "";
const name = answersFormatted[1]?.text || "";
return {
answer: {
firstname,
name
}
};
};
```
Il nodo Code restituisce un oggetto strutturato invece di un array. Nei passaggi successivi, ora puoi selezionare campi individuali come `answer.firstname` e `answer.name` dal selettore delle variabili.
<Tip>
**Vuoi iterare sull'array invece di estrarre i campi?** Quando uno step di Code o Logic Function restituisce un array di primo livello, puoi passarlo direttamente a un [Iterator](/l/it/user-guide/workflows/capabilities/use-iterator): seleziona l'opzione **Whole list** dello step come input dell'Iterator, quindi fai riferimento a ciascun elemento con `{{iterator.currentItem}}` all'interno del ciclo. In quel caso non è necessario ristrutturare l'array in un oggetto.
</Tip>
<Tip>
Fai clic sull'icona quadrata in alto a destra dell'editor di codice per visualizzarlo a tutto schermo — utile poiché la larghezza predefinita dell'editor è limitata.
</Tip>

View File

@@ -0,0 +1,182 @@
---
title: Porta i dati di prodotto in Twenty
description: Sincronizza i dati del catalogo prodotti da un data warehouse nel tuo CRM in base a una pianificazione.
---
Usa questo modello per mantenere Twenty sincronizzato con i dati di prodotto dal tuo data warehouse (ad esempio, Snowflake, BigQuery, PostgreSQL).
## Struttura del flusso di lavoro
1. **Trigger**: In base a una pianificazione
2. **Code**: Interroga il tuo data warehouse
3. **Code** (opzionale): Formatta i dati come array
4. **Iterator**: Itera su ciascun prodotto
5. **Upsert Record**: Crea o aggiorna in Twenty
<img src="/images/user-guide/workflows/create_users_workflow.png" style={{width:'100%'}} />
## Passaggio 1: Pianifica il trigger
Imposta il flusso di lavoro per l'esecuzione con una frequenza che corrisponda alle tue esigenze di aggiornamento dei dati:
* Ogni 5 minuti per una sincronizzazione quasi in tempo reale
* Ogni ora per i dati meno critici
* Ogni giorno per aggiornamenti in batch
## Passaggio 2: Interroga il tuo data warehouse
Aggiungi un'azione **Code** per recuperare i dati recenti:
```javascript
export const main = async () => {
const intervalMinutes = 10; // Match your schedule frequency
const cutoffTime = new Date(Date.now() - intervalMinutes * 60 * 1000).toISOString();
// Replace with your actual data warehouse connection
const response = await fetch("https://your-warehouse-api.com/query", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
query: `
SELECT id, name, sku, price, stock_quantity, updated_at
FROM products
WHERE updated_at >= '${cutoffTime}'
`
})
});
const data = await response.json();
return { products: data.results };
};
```
<Tip>
Filtra per `updated_at >= last X minutes` per recuperare solo i record modificati di recente. Questo mantiene efficiente la sincronizzazione.
</Tip>
## Passaggio 3: Formatta i dati (opzionale)
Se il tuo data warehouse restituisce dati in un formato che richiede una trasformazione, aggiungi un'altra azione **Code**. Le trasformazioni comuni includono conversioni di tipo, ridenominazione dei campi e pulizia dei dati.
### Esempio: Dati utente con campi booleani e di stato
```javascript
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Convert string "true"/"false" to actual booleans
const toBool = (v: any) => v === true || v === "true";
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
isActiveLast30d: toBool(user.isActiveLast30d),
isActiveLast7d: toBool(user.isActiveLast7d),
isActiveLast24h: toBool(user.isActiveLast24h),
isTwenty: toBool(user.isTwenty),
})),
};
};
```
### Esempio: Dati di prodotto con conversioni di tipo
```javascript
export const main = async (params: { products: any }) => {
const products = typeof params.products === "string"
? JSON.parse(params.products)
: params.products;
return {
products: products.map(product => ({
externalId: product.id,
name: product.name,
sku: product.sku,
price: parseFloat(product.price), // String → Number
stockQuantity: parseInt(product.stock_quantity),
isActive: product.status === "active" // String → Boolean
}))
};
};
```
### Esempio: Formattazione di date e valute
```javascript
export const main = async (params: { deals: any }) => {
const deals = typeof params.deals === "string"
? JSON.parse(params.deals)
: params.deals;
return {
deals: deals.map(deal => ({
...deal,
// Convert Unix timestamp to ISO date
closedAt: deal.closed_timestamp
? new Date(deal.closed_timestamp * 1000).toISOString()
: null,
// Ensure amount is a number (remove currency symbols)
amount: parseFloat(String(deal.amount).replace(/[^0-9.-]/g, "")),
// Normalize stage names
stage: deal.stage?.toLowerCase().replace(/_/g, " ")
}))
};
};
```
### Trasformazioni comuni
| Formato di origine | Formato di destinazione | Codice |
| -------------------- | ----------------------- | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"` |
| `"123.45"` | `123.45` | `parseFloat(value)` |
| `"active"` | `"ACTIVE"` | `value.toUpperCase()` |
| `1704067200` (Unix) | Data ISO | `new Date(v * 1000).toISOString()` |
| `"$1,234.56"` | `1234.56` | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""` | `value \|\| ""` |
## Passaggio 4: Itera sui prodotti
Aggiungi un'azione **Iterator**:
* Input: `{{code.products}}`
Questo esegue un ciclo su ogni prodotto nell'array.
## Passaggio 5: Esegui l'upsert di ogni record
All'interno dell'Iterator, aggiungi un'azione **Upsert Record**:
| Impostazione | Valore |
| ---------------------- | ----------------------------------------- |
| **Oggetto** | Il tuo oggetto Prodotto personalizzato |
| **Corrispondenza per** | ID esterno o SKU (identificatore univoco) |
| **Nome** | `{{iterator.item.name}}` |
| **SKU** | `{{iterator.item.sku}}` |
| **Prezzo** | `{{iterator.item.price}}` |
<Tip>
Usa **Upsert** (aggiornare o creare) invece di creare rami separati per creazione e aggiornamento. È più veloce da costruire e più facile da eseguire il debug.
</Tip>
## Esempi di casi d'uso
| Fonte | Dati |
| -------------------------- | ------------------------------------------- |
| **Sistema ERP** | Catalogo prodotti, prezzi, inventario |
| **Piattaforma e-commerce** | Ordini, clienti, aggiornamenti dei prodotti |
| **Data warehouse** | Metriche aggregate, dati arricchiti |
| **Sistema di inventario** | Livelli di stock, avvisi di riordino |
## Correlati
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Gestire gli array nelle azioni Code](/l/it/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,130 @@
---
title: Importa gli invii di Typeform in Twenty
description: Gestisci il payload del webhook di Typeform per creare lead dalle risposte del modulo.
---
Per la configurazione standard del webhook, vedi [Configura un trigger webhook](/l/it/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger). Questo articolo tratta la gestione specifica richiesta dalla struttura di payload personalizzata di Typeform.
### Passaggio 1: Crea un flusso di lavoro webhook
1. Vai a **Impostazioni → Flussi di lavoro**
2. Fai clic su **+ Nuovo flusso di lavoro**
3. Seleziona **Webhook** come trigger
4. Copia l'URL del webhook
### Passaggio 2: Configura Typeform
1. In Typeform, apri il tuo modulo
2. Vai a **Connect → Webhooks**
3. Incolla l'URL del webhook di Twenty
4. Salva
### Passaggio 3: Comprendi il payload di Typeform
Typeform invia una struttura JSON annidata. Ecco un esempio semplificato:
```json
{
"event_type": "form_response",
"form_response": {
"form_id": "abc123",
"submitted_at": "2025-01-15T10:30:00Z",
"answers": [
{
"text": "Jane",
"type": "text",
"field": { "id": "field1", "type": "short_text", "title": "First Name" }
},
{
"text": "Smith",
"type": "text",
"field": { "id": "field2", "type": "short_text", "title": "Last Name" }
},
{
"text": "Acme Corp",
"type": "text",
"field": { "id": "field3", "type": "short_text", "title": "Company" }
},
{
"email": "jane@acme.com",
"type": "email",
"field": { "id": "field4", "type": "email", "title": "Email" }
},
{
"type": "choice",
"field": { "id": "field5", "type": "dropdown", "title": "Team Size" },
"choice": { "label": "10-50" }
}
]
}
}
```
Punti chiave da notare:
* I dati del modulo sono annidati in `form_response`
* **Le risposte vengono restituite come array**, non come campi denominati
* Ogni risposta include il tipo e il titolo del campo come riferimento
### Passaggio 4: Estrai i campi dall'array delle risposte
Poiché `answers` è un array, nei passaggi successivi puoi selezionare solo l'intero array — non i singoli campi. Aggiungi un'azione **Code** per estrarre i campi di cui hai bisogno:
```javascript
export const main = async (params: {
answers: any;
}): Promise<object> => {
const { answers } = params;
// Handle input that may come as a string or an array
const answersFormatted = typeof answers === "string"
? JSON.parse(answers)
: answers;
// Extract fields by position or by finding the field type
const firstName = answersFormatted[0]?.text || "";
const lastName = answersFormatted[1]?.text || "";
const company = answersFormatted[2]?.text || "";
const email = answersFormatted.find(a => a.type === "email")?.email || "";
const teamSize = answersFormatted.find(a => a.type === "choice")?.choice?.label || "";
return {
contact: {
firstName,
lastName,
company,
email,
teamSize
}
};
};
```
Ora, nei passaggi successivi, puoi selezionare `contact.firstName`, `contact.email`, ecc. dal selettore delle variabili.
<Tip>
Per maggiori dettagli sulla gestione degli array, vedi [Gestire gli array nelle azioni Code](/l/it/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
### Passaggio 5: Crea il record
Aggiungi un'azione **Create Record**:
| Campo | Valore |
| ----------- | ------------------------------------------------- |
| **Oggetto** | Persone |
| **Nome** | `{{code.contact.firstName}}` |
| **Cognome** | `{{code.contact.lastName}}` |
| **Email** | `{{code.contact.email}}` |
| **Azienda** | Cerca o crea in base a `{{code.contact.company}}` |
### Passaggio 6: Testa e attiva
1. Invia una risposta di prova in Typeform
2. Controlla l'esecuzione del flusso di lavoro per verificare che i dati siano stati acquisiti
3. Attiva il flusso di lavoro
## Correlati
* [Configura un trigger webhook](/l/it/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)
* [Gestire gli array nelle azioni Code](/l/it/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,263 @@
---
title: Genera un PDF da Twenty
description: Crea un flusso di lavoro per generare e allegare un PDF (ad esempio un preventivo) a un record.
---
Genera o recupera automaticamente un PDF e allegalo a un record in Twenty. Questo viene comunemente utilizzato per creare preventivi, fatture o report collegati ad Aziende, Opportunità o altri oggetti.
## Panoramica
Questo flusso di lavoro usa un **Attivatore manuale** così gli utenti possono generare un PDF su richiesta per qualsiasi record selezionato. Una **Funzione logica** gestisce:
1. Scaricare il PDF da un URL (da un servizio di generazione di PDF)
2. Caricare il file su Twenty
3. Creare un allegato collegato al record
## Prerequisiti
Prima di configurare il flusso di lavoro:
1. **Crea una chiave API**: vai su **Impostazioni → API** e crea una nuova chiave API. Avrai bisogno di questo token per la Funzione Logica.
2. **Configura un servizio di generazione PDF** (opzionale): se vuoi generare dinamicamente PDF (ad es. preventivi), usa un servizio come Carbone, PDFMonkey o DocuSeal per creare il PDF e ottenere un URL di download.
## Configurazione passo-passo
### Passaggio 1: Configura il trigger
1. Vai su **Flussi di lavoro** e crea un nuovo flusso di lavoro
2. Seleziona **Attivatore manuale**
3. Scegli l'oggetto a cui vuoi allegare i PDF (ad es. **Company** o **Opportunity**)
<Tip>
Con un Trigger manuale, gli utenti possono eseguire questo flusso di lavoro utilizzando un pulsante che appare in alto a destra una volta selezionato un record: questo esegue il flusso di lavoro per generare e allegare un PDF.
</Tip>
### Passaggio 2: aggiungi una Funzione logica
1. Aggiungi un'azione **Codice** (Funzione logica)
2. Crea una nuova funzione con il codice seguente
3. Configura i parametri di input
#### Parametri di input
| Parametro | Valore |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
Se alleghi a un oggetto diverso (Person, Opportunity, ecc.), rinomina di conseguenza il parametro (ad es. `personId`, `opportunityId`) e aggiorna la funzione logica.
</Note>
#### Codice della Funzione logica
```typescript
export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
// Replace with your PDF URL
// This could be from a PDF generation service or a static URL
const pdfUrl = 'https://your-pdf-service.com/generated-quote.pdf';
const filename = 'quote.pdf';
// Step 1: Download the PDF file
const pdfResponse = await fetch(pdfUrl);
if (!pdfResponse.ok) {
throw new Error(`Failed to download PDF: ${pdfResponse.status}`);
}
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
}
}
`;
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
});
const uploadResult = await uploadResponse.json();
if (uploadResult.errors?.length) {
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
if (!fileId) {
throw new Error('No file id returned from upload');
}
// Step 4: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
}
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: attachmentMutation,
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
},
},
}),
});
const attachmentResult = await attachmentResponse.json();
if (attachmentResult.errors?.length) {
throw new Error(`Attachment creation failed: ${attachmentResult.errors[0].message}`);
}
return attachmentResult.data?.createAttachment;
};
```
### Passaggio 3: personalizza per il tuo caso d'uso
#### Per allegare a un oggetto diverso
Sostituisci `targetCompanyId` con il campo appropriato:
| Oggetto | Nome del campo |
| ---------------------- | -------------------------- |
| Azienda | `targetCompanyId` |
| Persona | `targetPersonId` |
| Opportunità | `targetOpportunityId` |
| Oggetto personalizzato | `targetYourCustomObjectId` |
Aggiorna sia il parametro della funzione sia l'oggetto `variables.data` nella mutation dell'allegato.
#### Per usare un URL PDF dinamico
Se usi un servizio di generazione PDF, puoi:
1. Per prima cosa esegui un'azione HTTP Request per generare il PDF
2. Passa l'URL PDF restituito alla funzione logica come parametro
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### Passaggio 4: Testa e attiva
1. Salva il flusso di lavoro
2. Vai a un record di Company
3. Fai clic sul menu **⋮** e seleziona il tuo flusso di lavoro
4. Controlla la sezione **Attachments** nel record per verificare che il PDF sia stato allegato
5. Attiva il flusso di lavoro
## Integrazione con servizi di generazione PDF
Per creare preventivi o fatture dinamici:
### Esempio: Genera preventivo → Allega PDF
| Passaggio | Azione | Scopo |
| --------- | ---------------------------- | -------------------------------------------------------- |
| 1 | Attivatore manuale (Company) | L'utente avvia l'operazione su un record |
| 2 | Cerca record | Ottieni i dettagli di Opportunity o delle righe articolo |
| 3 | Richiesta HTTP | Chiama l'API di generazione PDF con i dati del record |
| 4 | Funzione logica | Scarica e allega il PDF generato |
### Servizi di generazione PDF più diffusi
* **Carbone** - Generazione di documenti basata su modelli
* **PDFMonkey** - Creazione dinamica di PDF da modelli
* **DocuSeal** - Piattaforma di automazione dei documenti
* **Documint** - Generazione di documenti API-first
Ogni servizio fornisce un'API che restituisce un URL PDF, che puoi poi passare alla Funzione Logica.
## Risoluzione dei problemi
| Problema | Soluzione |
| -------------------------------------- | ---------------------------------------------------------------------------------------- |
| "Download del PDF non riuscito" | Verifica che l'URL del PDF sia accessibile e restituisca un PDF valido |
| "Caricamento non riuscito" | Verifica che la tua chiave API sia valida e abbia i permessi di scrittura |
| "Creazione dell'allegato non riuscita" | Assicurati che il nome del campo ID dell'oggetto corrisponda all'oggetto di destinazione |
## Correlati
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Funzioni logiche](/l/it/user-guide/workflows/capabilities/workflow-actions#code)
* [Genera un preventivo o una fattura da Twenty](/l/it/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)

View File

@@ -0,0 +1,144 @@
---
title: Genera un preventivo o una fattura da Twenty
description: Crea automaticamente fatture negli strumenti esterni quando le opportunità vengono chiuse.
---
Invia automaticamente i dati dell'opportunità al tuo sistema di fatturazione (Stripe, QuickBooks, Xero, ecc.) quando un'opportunità è chiusa come vinta.
## Struttura del flusso di lavoro
1. **Trigger**: Il Record è Aggiornato (Opportunità)
2. **Filtro**: Fase = Closed Won
3. **Search Record**: Recupera i dettagli dell'azienda
4. **Code** (opzionale): Formatta il payload
5. **HTTP Request**: Invia al sistema di fatturazione
## Passaggio 1: Configura il trigger
1. Crea un nuovo flusso di lavoro
2. Seleziona il trigger **Record is Updated**
3. Scegli **Opportunity** come oggetto
## Passaggio 2: Filtra per Closed Won
Aggiungi un'azione **Filtro** per proseguire solo quando l'opportunità è chiusa come vinta:
| Impostazione | Valore |
| -------------- | --------------------------------------- |
| **Campo** | Fase |
| **Condizione** | Uguale a |
| **Valore** | `CLOSED_WON` (o il nome della tua fase) |
<Tip>
Il trigger si attiva a ogni aggiornamento di un'Opportunità. Il filtro garantisce che il flusso di lavoro prosegua solo quando la fase cambia in Closed Won.
</Tip>
## Passaggio 3: Ottieni i dettagli dell'azienda
Il record Opportunity potrebbe non includere tutti i campi dell'azienda necessari per la fattura. Aggiungi un'azione **Search Record**:
| Impostazione | Valore |
| ---------------------- | ------------------------------------------ |
| **Oggetto** | Azienda |
| **Corrispondenza per** | ID uguale a `{{trigger.object.companyId}}` |
Questo recupera il record completo dell'azienda con indirizzo di fatturazione, partita IVA, ecc.
## Passaggio 4: Formatta il payload (opzionale)
Se il tuo sistema di fatturazione richiede un formato specifico, aggiungi un'azione **Code**:
```javascript
export const main = async (params: {
opportunity: any;
company: any;
}): Promise<object> => {
const { opportunity, company } = params;
return {
invoice: {
// Customer info from Company
customer_name: company.name,
customer_email: company.email || "",
billing_address: {
line1: company.address?.street || "",
city: company.address?.city || "",
postal_code: company.address?.postalCode || "",
country: company.address?.country || ""
},
tax_id: company.taxId || null,
// Invoice details from Opportunity
amount: opportunity.amount,
currency: opportunity.currency || "USD",
description: `Invoice for ${opportunity.name}`,
due_days: 30,
// Reference back to Twenty
metadata: {
opportunity_id: opportunity.id,
company_id: company.id
}
}
};
};
```
## Passaggio 5: Invia al sistema di fatturazione
Aggiungi un'azione **HTTP Request**:
| Impostazione | Valore |
| ---------------- | ----------------------------------------------- |
| **Metodo** | POST |
| **URL** | L'endpoint API del tuo sistema di fatturazione |
| **Intestazioni** | `Authorization: Bearer YOUR_API_KEY` |
| **Corpo** | `{{code.invoice}}` o mappa direttamente i campi |
### Esempio: Fattura Stripe
```
POST https://api.stripe.com/v1/invoices
Headers:
Authorization: Bearer sk_live_xxx
Content-Type: application/x-www-form-urlencoded
Body:
customer: {{company.stripeCustomerId}}
collection_method: send_invoice
days_until_due: 30
```
### Esempio: Fattura QuickBooks
```
POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Body: {{code.invoice}}
```
## Riepilogo completo del flusso di lavoro
| Passaggio | Azione | Scopo |
| --------- | ----------------------- | --------------------------------------------------------- |
| 1 | Trigger: Record Updated | Si attiva a ogni modifica di un'Opportunità |
| 2 | Filtro | Procedi solo se Fase = Closed Won |
| 3 | Search Record | Ottieni tutti i dettagli dell'azienda per la fatturazione |
| 4 | Codice | Formatta i dati per l'API di fatturazione |
| 5 | Richiesta HTTP | Crea la fattura in un sistema esterno |
## Suggerimenti
* **Memorizza gli ID esterni**: Salva l'ID della fattura restituito dall'API nell'Opportunity utilizzando un'azione **Update Record**
* **Gestione degli errori**: Aggiungi un ramo per inviare una notifica se la richiesta HTTP non riesce
* **Prima prova**: Usa la modalità sandbox/test del tuo sistema di fatturazione prima di andare in produzione
## Correlati
* [Generare un PDF da Twenty](/l/it/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — allegare i PDF generati ai record
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Automazioni Closed Won](/l/it/user-guide/workflows/how-tos/crm-automations/closed-won-automations)

View File

@@ -0,0 +1,170 @@
---
title: Configura un trigger webhook
description: Ricevi dati da servizi esterni per attivare i flussi di lavoro.
---
I trigger webhook consentono ai servizi esterni di avviare i tuoi flussi di lavoro inviando dati a un URL univoco. Usali per connettere moduli, app di terze parti e integrazioni personalizzate.
## Quando utilizzare i webhook
| Caso d'uso | Esempio |
| ------------------------------- | -------------------------------------------- |
| **Moduli web** | Gli invii del modulo di contatto creano lead |
| **App di terze parti** | Pagamento Stripe → crea un record cliente |
| **Integrazioni personalizzate** | La tua app → automazione Twenty |
| **Strumenti no-code** | Connessioni Zapier, Make, n8n |
## Configurazione passo-passo
### Passaggio 1: Crea il flusso di lavoro
1. Vai a **Impostazioni → Flussi di lavoro**
2. Fai clic su **+ Nuovo flusso di lavoro**
3. Assegnagli un nome (ad es., "Invio modulo sito web")
### Passaggio 2: Configura il trigger webhook
1. Fai clic sul blocco del trigger
2. Seleziona **Webhook**
3. Riceverai un URL webhook univoco come:
```
https://api.twenty.com/webhooks/workflow/abc123...
```
4. Copia questo URL—ti servirà per il tuo servizio esterno
### Passaggio 3: Definisci la struttura dati attesa
Per le richieste **POST**, definisci la struttura del corpo prevista:
1. Fai clic su **Definisci corpo previsto**
2. Inserisci un JSON di esempio che corrisponda a ciò che il tuo servizio invierà:
```json
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in your product"
}
```
3. Fai clic su **Salva**—questo crea variabili che puoi usare nei passaggi successivi
### Passaggio 4: Aggiungi azioni
Ora aggiungi azioni che utilizzano i dati del webhook:
**Esempio: Crea un record Persona**
1. Aggiungi l'azione **Crea record**
2. Seleziona l'oggetto **People**
3. Mappa i campi:
| Campo | Valore |
| ------- | ------------------------------------------------- |
| Nome | `{{trigger.body.firstName}}` |
| Cognome | `{{trigger.body.lastName}}` |
| Email | `{{trigger.body.email}}` |
| Azienda | Cerca o crea in base a `{{trigger.body.company}}` |
### Passaggio 5: Testa il webhook
Prima di attivare, testa il tuo webhook:
**Utilizzando cURL**:
```bash
curl -X POST https://api.twenty.com/webhooks/workflow/abc123... \
-H "Content-Type: application/json" \
-d '{"firstName":"Test","lastName":"User","email":"test@example.com"}'
```
**Utilizzando Postman o simili**:
1. Crea una richiesta POST verso l'URL del tuo webhook
2. Imposta l'header Content-Type su `application/json`
3. Aggiungi il body JSON di test
4. Invia e controlla le esecuzioni del flusso di lavoro
### Passaggio 6: Attiva
Una volta testato, fai clic su **Attiva** per rendere operativo il flusso di lavoro.
## Gestione di strutture dati diverse
### Dati annidati
Se il tuo webhook invia dati annidati:
```json
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
},
"source": "website"
}
```
Fai riferimento con: `{{trigger.body.contact.email}}`
### Array
Se i dati includono array:
```json
{
"items": [
{"name": "Product A", "qty": 2},
{"name": "Product B", "qty": 1}
]
}
```
Il modo in cui gestisci gli array dipende dal caso d'uso:
**Numero di elementi sconosciuto → Usa Iterator**
Se devi elaborare ogni elemento dell'array (ad es., creare un record per ciascuno), aggiungi un'azione **Code** per analizzare l'array, quindi usa **Iterator**:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return { items };
};
```
Quindi usa Iterator per iterare su: `{{code.items}}`
**Campi noti/specifici → Estrai in campi denominati**
Se l'array contiene campi specifici a cui vuoi accedere singolarmente (ad es., risposte di un modulo in cui la posizione 0 è sempre "nome", la posizione 1 è sempre "cognome"), aggiungi un'azione **Code** per estrarli:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return {
product: {
name: items[0]?.name || "",
qty: items[0]?.qty || 0
}
};
};
```
Ora puoi selezionare `product.name` e `product.qty` singolarmente nei passaggi successivi.
<Tip>
Per maggiori dettagli sulla gestione degli array, vedi [Gestire gli array nelle azioni Code](/l/it/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions).
</Tip>
## Correlati
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,121 @@
---
title: Risposta automatica alle email in arrivo
description: Crea un flusso di lavoro che utilizza l'IA per smistare le email in arrivo e inviare automaticamente risposte in thread.
---
Rispondi alle email in arrivo in pochi secondi — non ore. Questo flusso di lavoro utilizza un agente IA per filtrare il rumore (newsletter, spam, auto-replies) e redigere una risposta personalizzata ai messaggi reali. Poi, AI lo invia come risposta filettata all'interno della conversazione originale.
## Come funziona il threading delle email
Ogni email include un'intestazione nascosta `Message-ID` — un'impronta univoca assegnata dal server di posta del mittente. Quando rispondi a un'email, il tuo client di posta imposta un'intestazione `In-Reply-To` che fa riferimento a quell'impronta. È così che Gmail, Outlook e tutti gli altri client raggruppano i messaggi nei thread.
In Twenty, quell'impronta è memorizzata come `headerMessageId` sull'oggetto Message. Il tuo flusso di lavoro la recupera e la passa al campo In-Reply-To dell'azione Send Email.
## Creazione del flusso di lavoro
### Passaggio 1: Crea un nuovo flusso di lavoro
Vai a **Impostazioni -> Flussi di lavoro** e fai clic su **+ New Workflow**.
### Passaggio 2: Imposta il trigger sui messaggi in arrivo
Scegli **When a Record is Created** e seleziona **Messages**.
Ogni volta che un'email arriva in Twenty, questo si attiva.
<img src="/images/user-guide/workflows/auto-reply/workflow-overview.png" style={{width:'100%'}} />
### Passaggio 3: Cerca chi l'ha inviato
Aggiungi un'azione **Search Records**.
L'indirizzo del mittente non è presente sul messaggio stesso — si trova nel record Message Participant correlato.
| Campo | Valore |
| ----------- | ------------------------------- |
| **Oggetto** | Partecipanti al messaggio |
| **Filtro** | Message **is** `{{trigger.id}}` |
| **Filtro** | Role **is** From |
| **Limite** | 1 |
Questo ti fornisce l'email del mittente in `handle` e il suo nome in `displayName`.
<img src="/images/user-guide/workflows/auto-reply/find-sender.png" style={{width:'100%'}} />
### Passo 4: Triage e bozza una risposta con AI
Aggiungi un'azione **AI Agent**. Questo singolo passaggio fa due cose: decide se l'email merita una risposta e, in tal caso, ne scrive una.
Usa un prompt come:
```
You are an email triage assistant for a sales team. Read the following
inbound email and decide if it deserves a reply.
Subject: {{trigger.subject}}
Body: {{trigger.text}}
From: {{Find Sender.first.displayName}} ({{Find Sender.first.handle}})
If this email is spam, a newsletter, an automated notification, or
otherwise does not need a human reply, respond with exactly: SKIP
Otherwise, write a short, professional reply (3-4 sentences max) that:
- Acknowledges their specific message
- Lets them know someone from the team will follow up shortly
- Is warm but not overly casual
Respond with only the reply text, no subject line or greeting prefix.
```
AI Agent produce la sua risposta in un campo `response` a cui i passaggi successivi possono fare riferimento.
<img src="/images/user-guide/workflows/auto-reply/ai-triage.png" style={{width:'100%'}} />
### Passaggio 5: Ramifica in base alla decisione dell'AI
Aggiungi un'azione **If/Else** per verificare se l'AI ha deciso di rispondere o saltare.
| Campo | Valore |
| -------------- | ----------------------------------------------- |
| **Condizione** | AI Agent `response` **does not contain** `SKIP` |
| **Se vero** | Continua con Send Email |
| **Altrimenti** | Non fare nulla (il flusso di lavoro termina) |
Spam, newsletter e messaggi generati automaticamente vengono scartati. Tutto il resto passa al passaggio successivo.
<img src="/images/user-guide/workflows/auto-reply/should-reply.png" style={{width:'100%'}} />
### Passaggio 6: Invia una risposta nello stesso thread
Aggiungi un'azione **Send Email** nel ramo "if true". Fai clic su **Advanced options**, quindi su **Add In-Reply-To**.
| Campo | Valore |
| --------------- | -------------------------------------- |
| **A** | `{{Find Sender.first.handle}}` |
| **Oggetto** | `Re: {{trigger.subject}}` |
| **Corpo** | `{{AI Triage & Draft Reply.response}}` |
| **In-Reply-To** | `{{trigger.headerMessageId}}` |
Il campo In-Reply-To è ciò che rende questo un messaggio di risposta invece di una nuova conversazione. Il destinatario lo vede nel thread sotto l'email originale in Gmail, Outlook o qualsiasi altro client.
<img src="/images/user-guide/workflows/auto-reply/send-email.png" style={{width:'100%'}} />
<Tip>
**In-Reply-To** si aspetta un `message.headerMessageId` dal trigger — è l'impronta univoca dell'email, non un indirizzo del destinatario. Se lo lasci vuoto, l'email viene comunque inviata, solo come messaggio autonomo.
</Tip>
<Warning>
Gmail usa l'oggetto per raggruppare i messaggi nei thread. L'oggetto **deve** iniziare con `Re:` (inclusi i due punti e lo spazio) affinché Gmail visualizzi la risposta all'interno del thread originale. Senza di esso, la risposta apparirà come una conversazione separata — anche se l'intestazione In-Reply-To è impostata correttamente.
</Warning>
### Passaggio 7: Testa e attiva
Fai clic su **Test**, quindi controlla il tuo client di posta. La risposta dovrebbe comparire annidata sotto il messaggio originale.
Attivalo quando sei soddisfatto.
## Idee per sviluppare ulteriormente
* **Rispondi solo ai VIP** — aggiungi un ramo che controlli il dominio del mittente o se esiste come Contatto in Twenty
* **Instrada in base all'intento** — usa prompt distinti di AI Agent per gestire le richieste commerciali in modo diverso rispetto a quelle di supporto
* **Arricchisci prima di rispondere** — aggiungi un passaggio Search Records per inserire nel prompt dell'AI l'azienda del mittente o la cronologia delle trattative per risposte più personalizzate

View File

@@ -0,0 +1,179 @@
---
title: Automazioni per trattative chiuse come vinte
description: Automatizza le attività post-vittoria quando le opportunità vengono chiuse.
---
Quando una trattativa si chiude, devono succedere più cose: aggiornare lo stato dell'azienda, notificare i membri del team, creare attività di onboarding. Automatizza tutto questo con un unico flusso di lavoro.
## Il problema
Quando un'opportunità passa a "Chiusa vinta":
* Il tipo di azienda deve passare da "Potenziale cliente" a "Cliente"
* Devono essere create le attività di onboarding
* Il team di Customer Success deve essere avvisato
* Il rappresentante commerciale ha bisogno di una conferma
Farlo manualmente richiede tempo ed è soggetto a errori.
## La soluzione
Crea un flusso di lavoro che gestisca automaticamente tutte le attività post-vittoria.
## Configurazione completa del flusso di lavoro
### Passaggio 1: Crea il flusso di lavoro
1. Vai a **Impostazioni → Flussi di lavoro**
2. Fai clic su **+ Nuovo flusso di lavoro**
3. Assegna il nome "Trattativa vinta - Automazione post-vittoria"
### Passaggio 2: Configura il trigger
1. Seleziona **Record is Updated**
2. Seleziona **Opportunità**
3. In "Fields to monitor", seleziona **Stage**
### Passaggio 3: Aggiungi un filtro sulla fase
1. Aggiungi l'azione **Filter**
2. Condizione: `{{trigger.object.stage}}` uguale a "Chiusa vinta"
### Passaggio 4: Aggiorna il tipo di azienda
1. Aggiungi l'azione **Update Record**
2. Configura:
| Campo | Valore |
| ------------------------------- | ------------------------------- |
| **Oggetto** | Aziende |
| **Record** | `{{trigger.object.company.id}}` |
| **Tipo** | Cliente |
| **Data della prima trattativa** | `{{trigger.object.closedAt}}` |
| **Proprietario dell'account** | `{{trigger.object.owner.id}}` |
### Passaggio 5: Crea un'attività di onboarding
1. Aggiungi l'azione **Create Record**
2. Configura:
| Campo | Valore |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Oggetto** | Attività |
| **Titolo** | `Onboarding: {{trigger.object.name}}` |
| **Assegnatario** | Membro del team di Customer Success |
| **Data di scadenza** | Tra 3 giorni |
| **Priorità** | Alta |
| **Azienda correlata** | `{{trigger.object.company.id}}` |
| **Opportunità correlata** | `{{trigger.object.id}}` |
| **Descrizione** | `Onboarding del nuovo cliente per {{trigger.object.company.name}}. Valore della trattativa: {{trigger.object.amount}}` |
### Passaggio 6: Notifica il team di Customer Success
1. Aggiungi l'azione **Send Email**
2. Configura:
| Campo | Valore |
| ----------- | --------------------------------------------------- |
| **A** | customer-success@yourcompany.com |
| **Oggetto** | `🎉 Nuovo cliente: {{trigger.object.company.name}}` |
| **Corpo** | Vedi l'esempio sotto |
**Esempio di corpo dell'email**:
```
Hi CS Team,
We have a new customer!
Company: {{trigger.object.company.name}}
Deal: {{trigger.object.name}}
Value: {{trigger.object.amount}}
Sales Rep: {{trigger.object.owner.name}}
Close Date: {{trigger.object.closedAt}}
An onboarding task has been created automatically.
Let's give them a great start!
```
### Passaggio 7: Conferma al rappresentante commerciale
1. Aggiungi un'altra azione **Send Email**
2. Configura:
| Campo | Valore |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **A** | `{{trigger.object.owner.email}}` |
| **Oggetto** | `✅ Trattativa chiusa: {{trigger.object.name}}` |
| **Corpo** | Congratulazioni! La tua trattativa è stata elaborata. Il team di Customer Success è stato avvisato e l'onboarding è iniziato. |
### Passaggio 8: Testa e attiva
1. Esegui un test spostando un'opportunità di prova su "Chiusa vinta"
2. Verifica:
* Il tipo di azienda è stato modificato in "Cliente"
* Attività di onboarding creata
* Il team di Customer Success ha ricevuto l'email
* Il rappresentante commerciale ha ricevuto la conferma
3. Attiva quando sei pronto
## Gestione di "Chiusa persa"
Crea un flusso di lavoro simile per le trattative perse:
### Scatenante
* Record is Updated (Opportunità, Fase = "Chiusa persa")
### Azioni
1. **Create Record**: Attività per "Analisi trattativa persa"
2. **Update Record**: Aggiungi il motivo della perdita al record dell'azienda
3. **Send Email**: Notifica il responsabile della trattativa persa
## Avanzato: Onboarding a più fasi
Per un onboarding complesso, crea più attività:
```javascript
export const main = async (params) => {
const tasks = [
{ title: "Welcome call", daysFromNow: 1, assignee: "CS" },
{ title: "Send onboarding materials", daysFromNow: 2, assignee: "CS" },
{ title: "Technical setup", daysFromNow: 5, assignee: "Support" },
{ title: "30-day check-in", daysFromNow: 30, assignee: "CS" }
];
return { tasks };
};
```
Usa **Iterator** per creare ogni attività a partire dall'array.
## Idee di personalizzazione
### Mantieni aggiornati gli altri strumenti
* Crea il cliente nel sistema di fatturazione con una **HTTP Request**
### Azioni condizionali
Usa le azioni **Filter** per:
* Onboarding diverso per Enterprise rispetto alle PMI
* Assegnatari diversi in base alla regione
* Salta le notifiche per le trattative di piccolo valore
### Includi dettagli offerta
Usa l'azione **Code** per formattare:
* Documenti riepilogativi della trattativa
* Note di passaggio per il team di Customer Success
* Checklist di onboarding personalizzate
## Correlati
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Inviare email dai flussi di lavoro](/l/it/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,136 @@
---
title: Rileva opportunità stagnanti
description: Notifica automaticamente i responsabili quando le opportunità non sono state aggiornate.
---
Mantieni in salute la tua pipeline avvisando i responsabili quando le opportunità diventano stagnanti. Questo flusso di lavoro verifica le opportunità che non sono state aggiornate in un numero di giorni specificato.
## Il problema
Le opportunità senza aggiornamenti comportano:
* Trattative che si raffreddano
* Previsioni inaffidabili
* Perdita di ricavi
## La soluzione
Crea un flusso di lavoro pianificato che individua le opportunità stagnanti e invia un'email ai loro responsabili.
## Configurazione passo-passo
### Passaggio 1: Crea il flusso di lavoro
1. Vai a **Impostazioni → Flussi di lavoro**
2. Fai clic su **+ Nuovo flusso di lavoro**
3. Assegna il nome "Avviso di opportunità stagnanti"
### Passaggio 2: Configura il trigger
1. Seleziona **On a Schedule**
2. Imposta l'esecuzione giornaliera (ad es., ogni giorno alle 8:00)
### Passaggio 3: Cerca le opportunità stagnanti
1. Aggiungi un'azione **Search Records**
2. Configura:
| Campo | Valore |
| ----------- | ---------------------------------------------- |
| **Oggetto** | Opportunità |
| **Filtro** | Updated At è precedente a (oggi - 7 giorni) |
| **Filtro** | Stage non è "Closed Won" AND non "Closed Lost" |
| **Limite** | 100 |
### Passaggio 4: Verifica se ne sono state trovate
1. Aggiungi l'azione **Filter**
2. Condizione: `{{searchRecords.length}}` è maggiore di 0
3. Se non ci sono opportunità stagnanti, il flusso di lavoro si ferma qui
### Passaggio 5: Formatta l'avviso (azione Code)
Aggiungi un'azione **Code** per formattare l'email:
```javascript
export const main = async (params) => {
const opportunities = params.opportunities;
// Group opportunities by owner
const byOwner = {};
opportunities.forEach(opp => {
const ownerEmail = opp.owner?.email || 'unassigned';
if (!byOwner[ownerEmail]) {
byOwner[ownerEmail] = [];
}
byOwner[ownerEmail].push({
name: opp.name,
amount: opp.amount,
lastUpdated: opp.updatedAt,
stage: opp.stage
});
});
// Format summary for manager
let summary = "Stale Opportunities Report\n\n";
Object.entries(byOwner).forEach(([owner, opps]) => {
summary += `${owner}: ${opps.length} stale opportunities\n`;
opps.forEach(opp => {
summary += ` - ${opp.name} (${opp.stage})\n`;
});
summary += "\n";
});
return {
summary,
totalCount: opportunities.length
};
};
```
### Passaggio 6: Invia l'email di avviso
Aggiungi l'azione **Send Email**:
| Campo | Valore |
| ----------- | -------------------------------------------------------------------- |
| **A** | sales-manager@yourcompany.com |
| **Oggetto** | `🚨 {{code.totalCount}} opportunità stagnanti richiedono attenzione` |
| **Corpo** | `{{code.summary}}` |
### Passaggio 7: Testa e attiva
1. Fai clic su **Test** per eseguire il flusso di lavoro
2. Verifica che l'email contenga i dati corretti
3. Attiva quando sei pronto
## Opzioni di personalizzazione
### Modifica la soglia di stagnazione
Modifica il filtro di **Search Records** per passare da 7 giorni al periodo preferito:
* 3 giorni per vendite ad alta velocità
* 14 giorni per trattative enterprise
* 30 giorni per cicli di vendita lunghi
### Avvisa i singoli rappresentanti
Invece di un'unica email al responsabile, usa **Iterator** per inviare email personalizzate a ciascun rappresentante sulle proprie trattative stagnanti.
### Aggiungi un'escalation
Crea più flussi di lavoro con gravità crescente:
1. Giorno 7: Email al rappresentante
2. Giorno 14: Email al rappresentante + al responsabile
3. Giorno 21: Crea un'attività per il responsabile affinché intervenga
### Includi in Slack
Usa **HTTP Request** per inviare a un webhook Slack invece o in aggiunta all'email.
## Correlati
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Inviare email dai flussi di lavoro](/l/it/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,69 @@
---
title: Visualizza il numero di email ricevute
description: Crea un flusso di lavoro per contare e visualizzare automaticamente il numero di email ricevute da ciascun contatto.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/workflow-number-of-emails-received.png" style={{width:'100%'}} />
## Panoramica
Questo flusso di lavoro si attiva ogni volta che viene ricevuta una nuova email e aggiorna un campo personalizzato sul record Persona con il conteggio totale delle email provenienti da quel mittente.
## Prerequisiti
Prima di configurare questo flusso di lavoro, crea un campo personalizzato sull'oggetto **People**:
1. Vai a **Impostazioni → Modello dati → People**
2. Aggiungi un nuovo campo **Number**
3. Assegnagli un nome come "Numero di email ricevute da questa persona"
## Configurazione passo-passo
<VimeoEmbed videoId="1148700336" title="Dimostrazione video" />
### Passaggio 1: Configura il trigger
1. Vai su **Flussi di lavoro** e crea un nuovo flusso di lavoro
2. Seleziona **Record is Created** come trigger
3. Scegli **Message Participants** (disponibile sotto Oggetti avanzati)
<Note>
Un Message Participant è una combinazione di un ID messaggio e di un ID persona, che crea un record univoco per ogni messaggio. Questo è più semplice da tracciare rispetto ai Messaggi direttamente perché possiamo accedere al campo `handle`, che contiene l'indirizzo email del mittente (o del destinatario).
</Note>
### Passaggio 2: Filtra per ruolo
1. Aggiungi l'azione **Filter**
2. Imposta la condizione: **Role** uguale a **FROM**
Questo garantisce che tu conteggi solo i messaggi inviati da questa persona, non quelli inviati a questa persona.
### Passaggio 3: Cerca tutti i Message Participants con lo stesso handle
1. Aggiungi un'azione **Search Records**
2. Seleziona **Message Participants** come oggetto
3. Aggiungi dei filtri: **Handle** uguale all'handle del trigger (l'indirizzo email del mittente) e **Role** uguale a **FROM**
4. Aumenta il **Limit** da 1 a **200** (il massimo)
In questo modo si trovano tutti i messaggi provenienti da questo indirizzo email per ottenere il conteggio totale.
<Note>
L'azione Search Records è limitata a restituire al massimo 200 record. Tuttavia, poiché stai usando solo il valore `totalCount` (non i singoli record), questo passaggio restituirà il numero totale di email inviate da questa persona.
</Note>
### Passaggio 4: Aggiorna il record Persona con un'azione Create or Update Record
1. Aggiungi un'azione **Create or Update Record**
2. Seleziona **People** come oggetto
3. Trova la persona facendo corrispondere la sua email all'`handle` del Message Participant
4. Imposta il tuo campo personalizzato "Numero di email ricevute" su `{{searchRecords.totalCount}}`
Il valore `totalCount` dell'azione Search Records rappresenta il numero totale di email ricevute da questa persona.
## Correlati
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Crea campi personalizzati](/l/it/user-guide/data-model/how-tos/customize-your-data-model)
* [Azione Search Records](/l/it/user-guide/workflows/capabilities/workflow-actions#search-records)

View File

@@ -0,0 +1,170 @@
---
title: Visualizza i dati dei record correlati
description: Mostra i dati dei record correlati (ad es., informazioni sull'Azienda nelle Opportunità) usando i flussi di lavoro.
---
Visualizza i dati dei record correlati direttamente sui tuoi record — ad esempio, mostra il numero di dipendenti di un'Azienda nelle sue Opportunità. Questa soluzione alternativa con i flussi di lavoro è utile finché i campi annidati non sono disponibili nativamente.
## Casi d'Uso Comuni
| Fonte | Destinazione | Campi da copiare |
| ----------- | ------------ | --------------------------------------------------------- |
| Azienda | Opportunità | Settore, Dimensione aziendale, ARR |
| Persona | Opportunità | Email, Telefono, Qualifica |
| Opportunità | Azienda | Importo dell'ultimo affare, Data dell'ultimo affare vinto |
## Copia di campi di base
### Esempio: copia l'Email del contatto nell'Opportunità
**Obiettivo**: Quando imposti un Punto di contatto su un'opportunità, copia la sua email nell'opportunità per un accesso rapido.
### Prerequisito
Crea i campi di destinazione in **Impostazioni → Modello dati → Opportunità** prima di creare il flusso di lavoro:
* Email del contatto (tipo: Email)
* Telefono del contatto (tipo: Telefono)
### Impostazione
1. **Trigger**: Il record è aggiornato (Opportunità, campo Punto di contatto)
2. **Filtro**: Verifica che il Punto di contatto non sia vuoto
3. **Cerca record**: Trova la persona collegata
* Oggetto: Persone
* Filtro: ID uguale a `{{trigger.object.pointOfContact.id}}`
4. **Aggiorna record**:
* Oggetto: Opportunità
* Record: `{{trigger.object.id}}`
* Email del contatto: `{{searchRecords[0].email}}`
* Telefono del contatto: `{{searchRecords[0].phone}}`
## Copia di più campi
### Esempio: sincronizza le informazioni dell'Azienda con tutte le Opportunità correlate
**Obiettivo**: Quando i dettagli dell'azienda cambiano, aggiorna tutte le opportunità correlate.
### Impostazione
1. **Trigger**: Il record è aggiornato (Aziende)
* Campi: Settore, Dimensione aziendale, Ricavi annuali
2. **Cerca record**: Trova tutte le opportunità per questa azienda
* Oggetto: Opportunità
* Filtro: ID Azienda uguale a `{{trigger.object.id}}`
3. **Iteratore**: Itera su ciascuna opportunità
4. **Aggiorna record** (all'interno dell'iteratore):
* Oggetto: Opportunità
* Record: `{{iterator.currentItem.id}}`
* Settore aziendale: `{{trigger.object.industry}}`
* Dimensione aziendale: `{{trigger.object.companySize}}`
* ARR dell'azienda: `{{trigger.object.annualRevenue}}`
## Copia alla creazione del record
### Esempio: precompila l'Opportunità con i dati dell'Azienda
**Obiettivo**: Quando crei un'opportunità collegata a un'azienda, copia automaticamente le informazioni principali dell'azienda.
### Prerequisito
Crea i campi di destinazione in **Impostazioni → Modello dati → Opportunità**:
* Settore aziendale (tipo: Testo)
* Dimensione aziendale (tipo: Numero)
### Impostazione
1. **Trigger**: Il record è creato (Opportunità)
* Filtro: l'Azienda non è vuota
2. **Cerca record**: Recupera i dettagli dell'azienda collegata
* Oggetto: Aziende
* Filtro: ID uguale a `{{trigger.object.company.id}}`
3. **Aggiorna record**:
* Oggetto: Opportunità
* Record: `{{trigger.object.id}}`
* Settore aziendale: `{{searchRecords[0].industry}}`
* Dimensione aziendale: `{{searchRecords[0].employees}}`
<Note>
**Limitazione di Attività e Note**: Le relazioni su Attività e Note sono codificate come molte-a-molte e non sono ancora disponibili nei trigger o nelle azioni dei flussi di lavoro. Per accedere a queste relazioni, usa invece le [API](/l/it/developers/extend/api).
</Note>
## Sincronizzazione bidirezionale
### Esempio: mantieni sincronizzato il Contatto principale
**Obiettivo**: Quando cambia il contatto principale di un'azienda, aggiorna il contatto. Quando una persona diventa principale, aggiorna l'azienda.
### Flusso di lavoro 1: Azienda → Persona
1. **Trigger**: Il record è aggiornato (Aziende, campo Contatto principale)
2. **Aggiorna record**: Imposta "È Contatto principale" della persona su true
3. **Cerca record**: Trova il precedente contatto principale
4. **Aggiorna record**: Imposta "È Contatto principale" del contatto precedente su false
### Flusso di lavoro 2: Persona → Azienda
1. **Trigger**: Il record è aggiornato (Persone, È Contatto principale = true)
2. **Aggiorna record**: Imposta il Contatto principale dell'azienda su questa persona
<Note>
Fai attenzione con le sincronizzazioni bidirezionali per evitare loop infiniti. Usa filtri per verificare che il valore sia effettivamente cambiato prima di aggiornare.
</Note>
## Uso del codice per mappature complesse
### Esempio: trasformare i dati durante la copia
**Obiettivo**: Copia e formatta il numero di telefono dalla persona all'opportunità.
```javascript
export const main = async (params) => {
const { phone } = params;
if (!phone) return { formattedPhone: null };
// Remove non-numeric characters
const digits = phone.replace(/\D/g, '');
// Format as (XXX) XXX-XXXX
const formatted = digits.length === 10
? `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}`
: phone;
return { formattedPhone: formatted };
};
```
## Migliori Pratiche
### Evita i loop
* Non creare flussi di lavoro che si attivano a vicenda all'infinito
* Usa condizioni specifiche sui campi
* Aggiungi controlli per verificare se il valore è effettivamente cambiato
### Gestisci i dati mancanti
* Verifica sempre che il record di origine esista prima di copiare
* Fornisci valori predefiniti per i campi facoltativi
* Usa filtri per saltare quando il campo di origine è vuoto
### Prestazioni
* Esegui aggiornamenti in batch quando copi su molti record
* Usa flussi di lavoro pianificati per operazioni di sincronizzazione in blocco
* Valuta l'uso dell'Iteratore per aggiornamenti di più record
## Correlati
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)

View File

@@ -0,0 +1,202 @@
---
title: Campi formula
description: Crea campi formula usando i flussi di lavoro finché il supporto nativo non sarà disponibile.
---
Venti non supportano ancora i campi di formula nativi (arrivando nel 2026), ma puoi ottenere lo stesso risultato usando i flussi di lavoro. Questa soluzione alternativa consente di calcolare e compilare automaticamente i valori dei campi — dalle semplici concatenazioni alla logica aziendale più complessa.
## Casi d'Uso Comuni
| Caso d'uso | Esempio di formula |
| ------------------------ | ---------------------------------- |
| **Nome completo** | Nome + " " + Cognome |
| **Importo previsto** | Importo × Probabilità |
| **Giorni alla scadenza** | Data di scadenza - Oggi |
| **Giorni nella fase** | Oggi - Data di ingresso nella fase |
| **Punteggio lead** | Punti in base a criteri multipli |
<Tip>
Per un esempio completo del monitoraggio del tempo nelle fasi della pipeline, vedi [Traccia quanto tempo le opportunità restano in ogni fase](/l/it/user-guide/views-pipelines/how-tos/track-time-in-stage).
</Tip>
## Formula di base: concatenazione
### Esempio: compilazione automatica del nome completo
**Obiettivo**: combinare automaticamente nome e cognome in un campo nome completo.
### Impostazione
1. **Trigger**: il record viene aggiornato o creato (Persone)
2. **Filtro**: verifica che il nome o il cognome siano cambiati
3. **Azione di codice**:
```javascript
export const main = async (params) => {
const { firstName, lastName } = params;
const fullName = [firstName, lastName]
.filter(Boolean)
.join(' ');
return { fullName };
};
```
4. **Aggiorna record**: imposta Nome completo su `{{code.fullName}}`
## Formula numerica: importo previsto
### Esempio: calcolare il ricavo previsto
**Obiettivo**: moltiplicare l'importo dell'opportunità per la probabilità per ottenere l'importo previsto.
Vedi [Come mostrare l'importo previsto nella pipeline](/l/it/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline) per il flusso di lavoro completo.
### Impostazione
1. **Trigger**: il record viene aggiornato (Opportunità, campo Importo oppure Probabilità)
2. **Azione di codice**:
```javascript
export const main = async (params) => {
const { amount, probability } = params;
const expectedAmount = (amount || 0) * (probability || 0) / 100;
return { expectedAmount };
};
```
3. **Aggiorna record**: imposta Importo previsto su `{{code.expectedAmount}}`
## Formula sulle date: calcolo dei giorni
### Esempio: giorni alla scadenza dell'attività
**Obiettivo**: calcolare quanti giorni mancano alla data di scadenza di un'attività.
### Impostazione
1. **Trigger**: il record viene aggiornato o creato (Attività, campo Data di scadenza)
2. **Azione di codice**:
```javascript
export const main = async (params) => {
const { dueDate } = params;
if (!dueDate) {
return { daysUntilDue: null };
}
const due = new Date(dueDate);
const today = new Date();
const diffTime = due - today;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return { daysUntilDue: diffDays };
};
```
3. **Aggiorna record**: imposta Giorni alla scadenza su `{{code.daysUntilDue}}`
<Note>
I valori negativi indicano attività scadute. Puoi usare questo campo per filtrare o ordinare le attività per urgenza.
</Note>
## Formula condizionale: punteggio lead
### Esempio: calcolare il punteggio lead in base a criteri
**Obiettivo**: assegnare un punteggio ai lead in base alla dimensione aziendale, al settore e al coinvolgimento.
### Impostazione
1. **Trigger**: il record viene aggiornato (Persone o Aziende)
2. **Azione di codice**:
```javascript
export const main = async (params) => {
const { companySize, industry, hasEmail, hasPhone, source } = params;
let score = 0;
// Company size scoring
if (companySize === 'Enterprise') score += 30;
else if (companySize === 'Mid-Market') score += 20;
else if (companySize === 'SMB') score += 10;
// Industry scoring
const targetIndustries = ['Technology', 'Finance', 'Healthcare'];
if (targetIndustries.includes(industry)) score += 25;
// Contact info scoring
if (hasEmail) score += 10;
if (hasPhone) score += 15;
// Source scoring
if (source === 'Referral') score += 20;
else if (source === 'Website') score += 10;
return { leadScore: score };
};
```
3. **Aggiorna record**: imposta Punteggio lead su `{{code.leadScore}}`
## Formula di testo: estrazione del dominio
### Esempio: estrarre il dominio dall'email
**Obiettivo**: estrarre e archiviare automaticamente il dominio dell'email.
### Impostazione
1. **Trigger**: il record viene aggiornato (Persone, campo Email)
2. **Azione di codice**:
```javascript
export const main = async (params) => {
const { email } = params;
if (!email) return { domain: null };
const domain = email.split('@')[1]?.toLowerCase();
return { domain };
};
```
3. **Aggiorna record**: imposta il campo Dominio su `{{code.domain}}`
## Migliori Pratiche
### Prestazioni
* Attiva solo in caso di modifiche ai campi rilevanti
* Usa filtri per ignorare i record che non richiedono calcolo
* Evita calcoli complessi in flussi di lavoro ad alto volume
### Gestione degli errori
* Verifica i valori null/undefined prima dei calcoli
* Usa valori predefiniti quando i dati mancano
* Restituisci messaggi di errore chiari quando i calcoli falliscono
### Testing
* Esegui test con casi limite (campi vuoti, valori zero)
* Verifica manualmente i calcoli prima dell'attivazione
* Monitora le esecuzioni dei flussi di lavoro per risultati imprevisti
## Correlati
* [Come mostrare l'importo previsto nella pipeline](/l/it/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)
* [Come tracciare il tempo in fase](/l/it/user-guide/views-pipelines/how-tos/track-time-in-stage)
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,127 @@
---
title: Notificare ai colleghi una nota da rivedere
description: Invia notifiche email automatiche quando assegni a un collega la revisione di una nota.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>
**Menzionare direttamente i colleghi nelle note non è ancora supportato.** Questa funzionalità è prevista per la seconda metà del 2026. Nel frattempo, questo flusso di lavoro fornisce una soluzione alternativa pratica.
</Note>
## La sfida
Vuoi menzionare un collega in una nota affinché riceva una notifica per rivederla. Al momento, Twenty non supporta le @menzioni nelle note o nelle attività.
## La soluzione alternativa
Invece di menzionare nel testo della nota, puoi:
1. Crea un campo di relazione **Reviewer** sull'oggetto Note
2. Assegna il revisore quando crei o modifichi la nota
3. Avere un flusso di lavoro inviare automaticamente una e-mail al revisore con un link alla nota
<Warning>
**Limitazione**: Questo approccio consente di assegnare un solo revisore per campo. Se devi notificare più persone, crea campi di relazione aggiuntivi (ad es., Reviewer 1, Reviewer 2). Questo non scala bene se devi taggare molti collaboratori in una volta sola.
</Warning>
## Configurazione passo-passo
### Passaggio 1: Crea il campo Reviewer
<img src="/images/user-guide/workflows/reviewer-field-for-note.png" style={{width:'100%'}} />
1. Vai a **Impostazioni → Modello dati → Note**
2. Fai clic su **Aggiungi campo**
3. Configura:
| Impostazione | Valore |
| --------------------- | --------------------------------------------- |
| **Name** | Reviewer |
| **Tipo** | Relazione |
| **Oggetto correlato** | Membro dello spazio di lavoro |
| **Tipo di relazione** | Molte note → Un membro dello spazio di lavoro |
4. Salva il campo
### Passaggio 2: Crea il flusso di lavoro
<VimeoEmbed videoId="1151897168" title="Dimostrazione video" />
#### Crea il trigger del flusso di lavoro
1. Vai alla vista principale e crea un nuovo flusso di lavoro
2. Seleziona **Record Updated** come trigger
3. Scegli **Note** come oggetto e specifica il campo Reviewer.
4. Questo si attiverà ogni volta che il revisore di una nota viene aggiornato.
#### Aggiungi un filtro per verificare che il revisore sia stato appena assegnato
Aggiungi un'azione **Filtro** per garantire che il flusso di lavoro prosegua solo quando è stato assegnato un revisore:
| Condizione | Valore |
| ------------ | ----------- |
| **Reviewer** | non è vuoto |
#### Cerca il membro dello spazio di lavoro
1. Aggiungi un'azione **Search Records**
2. Seleziona **Workspace Member** (nella sezione Oggetti avanzati)
3. Aggiungi un filtro:
| Condizione | Valore |
| ---------- | ------------------------------ |
| **ID** | uguale a `{{note.reviewerId}}` |
Questo recupera il record completo del membro dello spazio di lavoro, inclusi l'indirizzo email e il nome.
#### Invia l'email di notifica
1. Aggiungi un'azione **Send Email**
2. Configura:
| Campo | Valore |
| ----------- | --------------------------------------------------- |
| **A** | Search Record → First Workspace Member → User Email |
| **Oggetto** | Una nota richiede la tua revisione |
| **Corpo** | Vedi l'esempio sotto |
**Esempio di corpo dell'email:**
```text
Hi {{searchRecord.firstWorkspaceMember.name.firstName}},
You've been assigned to review a note.
View the note here:
https://yourSubDomain.twenty.com/object/note/{{recordIsUpdated.id}}
Best,
Twenty
```
<Note>
Sostituisci `yourSubDomain` con il tuo effettivo sottodominio dello spazio di lavoro Twenty.
</Note>
#### Esegui il test e attiva
1. Fai clic su **Test** per eseguire manualmente il flusso di lavoro
2. Crea o modifica una nota e assegna un revisore
3. Controlla la casella di posta del revisore per l'email di notifica
4. Attiva il flusso di lavoro
## Utilizzo
Una volta configurato, il tuo team può utilizzare questo flusso di lavoro:
1. Creando una nuova nota (o modificandone una esistente)
2. Selezionando un collega nel campo **Reviewer**
3. Il collega riceve automaticamente un'email con un link diretto alla nota
## Correlati
* [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers)
* [Inviare email dai flussi di lavoro](/l/it/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Crea campi personalizzati](/l/it/user-guide/data-model/how-tos/create-custom-fields)
* [Crea campi di relazione](/l/it/user-guide/data-model/how-tos/create-relation-fields)

View File

@@ -0,0 +1,106 @@
---
title: Invia avvisi via email per le attività in scadenza
description: Notifica automaticamente ai membri del team le attività imminenti o in ritardo.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/email_tasks_due.png" style={{width:'100%'}} />
Invia promemoria email quotidiani a ciascun membro del team sulle attività in scadenza oggi.
## Panoramica
Questo flusso di lavoro viene eseguito secondo una pianificazione e:
1. Recupera tutti i membri dello spazio di lavoro
2. Scorre ciascun membro
3. Trova le loro attività in scadenza oggi
4. Formatta e invia un'email personalizzata
## Configurazione passo-passo
<VimeoEmbed videoId="1147463514" title="Dimostrazione video" />
### Passaggio 1: Configura il trigger
1. Vai su **Impostazioni → Flussi di lavoro** e crea un nuovo flusso di lavoro
2. Seleziona **On a Schedule** come trigger
3. Usa un'espressione cron per ogni giorno alle 8:00: `0 8 * * *`
### Passaggio 2: Cerca tutti i membri dello spazio di lavoro
1. Aggiungi un'azione **Search Records**
2. Seleziona **Workspace Members** (nella sezione Oggetti avanzati)
3. Non sono necessari filtri — questo restituisce tutti i membri
### Passaggio 3: Aggiungi un Iterator
1. Aggiungi un'azione **Iterator**
2. Imposta l'array di input con i membri dello spazio di lavoro del passaggio precedente
3. Tutte le azioni all'interno dell'Iterator verranno eseguite una volta per membro
### Passaggio 4: Cerca le attività in scadenza oggi (all'interno dell'Iterator)
1. All'interno dell'Iterator, aggiungi un'azione **Search Records**
2. Seleziona **Tasks** come oggetto
3. Aggiungi filtri:
* **Assignee** = membro corrente dello spazio di lavoro (dall'Iterator)
* **Due Date** = oggi
### Passaggio 5: Formatta le attività nel corpo dell'email (all'interno dell'Iterator)
Aggiungi un'azione **Code** per formattare le attività in un elenco leggibile con link:
```javascript
export const main = async (params: {
tasksDue?: Array<{ id: string; title: string }> | null | string;
}) => {
const tasksDue =
typeof params.tasksDue === "string"
? JSON.parse(params.tasksDue)
: params.tasksDue;
if (!Array.isArray(tasksDue) || tasksDue.length === 0) {
return {
formattedTasks: "No tasks due today."
};
}
const formattedTasks = tasksDue
.map(
t =>
`${t.title}\nhttps://yourSubDomain.twenty.com/object/task/${t.id}`
)
.join("\n\n");
return { formattedTasks };
};
```
<Note>
Sostituisci `yourSubDomain` con il tuo effettivo sottodominio dello spazio di lavoro Twenty.
</Note>
### Passaggio 6: Invia email (all'interno dell'Iterator)
1. Aggiungi un'azione **Send Email** (sempre all'interno dell'Iterator)
2. Configura:
| Campo | Valore |
| ----------- | ------------------------------------------------------------------------------ |
| **A** | `{{iterator.currentItem.userEmail}}` (email del membro dello spazio di lavoro) |
| **Oggetto** | Le tue attività in scadenza oggi |
| **Corpo** | `{{code.formattedTasks}}` |
### Passaggio 7: Testa e attiva
1. Fai clic su **Test** per eseguire manualmente il flusso di lavoro
2. Controlla le caselle di posta per le email
3. Attiva il flusso di lavoro
## Correlati
* [Azioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-actions)
* [Inviare email dai flussi di lavoro](/l/it/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Gestire gli array nelle azioni Code](/l/it/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,21 @@
---
title: Servizi Professionali
description: Ottieni assistenza professionale per creare flussi di lavoro e automazioni complessi dal team di Twenty e dai partner certificati.
---
## Quando Hai Bisogno di Aiuto Professionale?
Considera i servizi professionali per:
* Complesse integrazioni multi-sistema
* Logica aziendale avanzata e regole di automazione
* Flussi di lavoro di elaborazione dati su larga scala
* Sviluppo API personalizzato
* Formazione del team e ottimizzazione dei flussi di lavoro
* Quando non si hanno risorse interne
## Opzioni di Servizio
### Partner di Implementazione
Collabora con partner certificati per personalizzazioni avanzate. Contattaci all'indirizzo [contact@twenty.com](mailto:contact@twenty.com) per entrare in contatto con i nostri [partner di implementazione](https://twenty.com/partners).

View File

@@ -0,0 +1,170 @@
---
title: Risoluzione dei problemi del flusso di lavoro
description: Problemi comuni dei flussi di lavoro e come risolverli.
---
## Problemi comuni e soluzioni
### Flusso di lavoro non attivato
**Sintomi**: Il flusso di lavoro non viene eseguito quando previsto.
**Possibili cause**:
1. **Flusso di lavoro non attivato**: Assicurati che il flusso di lavoro sia impostato su "Attivo" e non su "Bozza"
2. **Condizioni del trigger non soddisfatte**: Verifica che il trigger corrisponda all'evento previsto
3. **Campo non monitorato**: Per i trigger "Record is Updated", assicurati che il campo specifico sia monitorato
4. **Autorizzazioni**: Verifica di avere l'autorizzazione per eseguire i flussi di lavoro
**Soluzioni**:
* Verifica lo stato del flusso di lavoro nell'elenco dei flussi di lavoro
* Esegui un test con l'azione specifica che dovrebbe attivarlo
* Rivedi la configurazione del trigger
* Contatta l'amministratore per le autorizzazioni
### Il flusso di lavoro si attiva troppo presto (campi vuoti)
**Sintomi**: Quando crei manualmente un record nell'interfaccia utente, il flusso di lavoro si attiva prima che tu abbia il tempo di compilare tutti i campi. Il flusso di lavoro viene eseguito con valori di campo per la maggior parte vuoti.
**Perché accade**: Twenty salva tutto in tempo reale — non esiste una modalità separata "modifica" vs "lettura". Quando crei un record, questo viene salvato immediatamente, attivando l'evento "Record is created" prima che tu possa compilare i campi aggiuntivi.
**Quando "Record is created" funziona bene**:
* Record creati tramite chiamate API (i campi vengono popolati in un'unica richiesta)
* Record creati tramite importazione
* Creazione automatizzata di record da altri flussi di lavoro
**Soluzione**: Per i record creati manualmente nell'interfaccia utente, usa **"Record is created or updated"** come trigger. In questo modo:
* Il flusso di lavoro si attiva dopo che l'utente ha terminato di compilare e salvare i campi
* Ottieni i dati completi invece di valori vuoti
<Tip>
Se desideri che il flusso di lavoro venga eseguito solo una volta per record, aggiungi un'azione Filter per verificare un campo come `createdAt equals updatedAt` (primo salvataggio) oppure usa un campo casella di controllo personalizzato per tracciare se il flusso di lavoro è già stato eseguito.
</Tip>
### Azioni non riuscite
**Sintomi**: Il flusso di lavoro viene eseguito ma alcune azioni non riescono.
**Possibili cause**:
1. **Dati mancanti**: I campi obbligatori sono vuoti
2. **Riferimenti non validi**: Le variabili dei passaggi precedenti non esistono
3. **Errori API**: I servizi esterni restituiscono errori
4. **Problemi di autorizzazioni**: L'azione richiede autorizzazioni che non possiedi
**Soluzioni**:
* Controlla i dettagli dell'esecuzione del flusso di lavoro per eventuali messaggi di errore
* Verifica che tutti i campi obbligatori abbiano un valore
* Testa le connessioni API in modo indipendente
* Rivedi le autorizzazioni del ruolo
### Errori delle richieste HTTP
**Sintomi**: Le azioni HTTP Request non riescono o restituiscono risultati inattesi.
**Codici di errore comuni**:
* **400**: Richiesta non valida - controlla il formato del corpo della richiesta
* **401**: Non autorizzato - verifica la chiave API
* **403**: Vietato - controlla le autorizzazioni dell'API
* **404**: Non trovato - verifica l'URL dell'endpoint
* **429**: Troppe richieste - implementa la limitazione della frequenza
* **500**: Errore del server - problema del servizio esterno
**Soluzioni**:
* Verifica l'URL dell'endpoint API
* Controlla le intestazioni di autenticazione
* Esegui prima il test della chiamata API al di fuori di Twenty
* Aggiungi la gestione degli errori nelle azioni di Codice
### Errori delle azioni di Codice
**Sintomi**: Il codice JavaScript non viene eseguito.
**Problemi comuni**:
1. **Errori di sintassi**: Refusi o JavaScript non valido
2. **Variabili non definite**: Riferimento a variabili che non esistono
3. **Errori di tipo**: Operazioni su tipi di dati errati
4. **Timeout**: Il codice impiega troppo tempo per essere eseguito
**Soluzioni**:
* Usa la convalida dell'editor di codice integrato
* Verifica prima la logica del codice in una console JavaScript
* Aggiungi istruzioni console.log per il debug
* Semplifica le operazioni complesse
### Le email non vengono inviate
**Sintomi**: L'azione Invia email non recapita le email.
**Possibili cause**:
1. **Nessun account email connesso**: Controlla Impostazioni → Account
2. **Indirizzo email non valido**: L'email del destinatario non è valida
3. **Limiti di invio**: Raggiunti i limiti di frequenza del provider email
4. **Filtri antispam**: Le email vengono bloccate
**Soluzioni**:
* Verifica la connessione dell'account email
* Convalida gli indirizzi email dei destinatari
* Controlla i limiti del provider email
* Rivedi il contenuto dell'email per possibili trigger di spam
## Debug dei flussi di lavoro
### Utilizzo delle esecuzioni del flusso di lavoro
1. Vai all'editor dei flussi di lavoro
2. Apri il pannello **Esecuzioni**
3. Trova l'esecuzione non riuscita
4. Fai clic per vedere i dettagli passo dopo passo
5. Rivedi i messaggi di errore e i dati di output
### Test dei singoli passaggi
1. Per le azioni di Codice, usa il pulsante **Test**
2. Per le richieste HTTP, testa separatamente l'endpoint
3. Crea record di test per attivare i flussi di lavoro
4. Usa trigger manuali per test controllati
### Schemi comuni di debug
**Aggiungi logging**:
Usa le azioni di Codice per registrare valori intermedi per il debug.
**Isola i passaggi**:
Testa ogni passaggio in modo indipendente per identificare gli errori.
**Controlla il flusso dei dati**:
Verifica che ogni passaggio riceva i dati di input previsti.
## Buone pratiche per evitare problemi
### Prima dell'attivazione
* Esegui test approfonditi in modalità bozza
* Convalida tutte le connessioni API
* Rivedi attentamente le condizioni del trigger
* Documenta il comportamento previsto
### Durante lo sviluppo
* Usa nomi di passaggio descrittivi
* Aggiungi commenti nelle azioni di Codice
* Testa con dati realistici
* Pianifica i casi limite
### Dopo l'attivazione
* Monitora attentamente le esecuzioni iniziali
* Configura avvisi per gli errori
* Rivedi regolarmente la cronologia delle esecuzioni
* Mantieni i flussi di lavoro semplici quando possibile

View File

@@ -0,0 +1,236 @@
---
title: FAQ sui flussi di lavoro
description: Domande frequenti sui flussi di lavoro in Twenty.
---
<AccordionGroup>
<Accordion title="Perché non riesco ad attivare un flusso di lavoro?">
È probabilmente un problema di permessi. Hai bisogno dell'accesso ai flussi di lavoro per crearli e attivarli.
**Soluzione**: Contatta l'amministratore dello spazio di lavoro per concederti l'accesso ai flussi di lavoro in **Impostazioni → Membri → Ruoli**.
Se non vedi affatto la sezione Flussi di lavoro nella barra laterale, ciò conferma che è un problema di permessi.
</Accordion>
<Accordion title="Perché non vedo il pulsante del flusso di lavoro manuale nella barra di navigazione superiore?">
I flussi di lavoro manuali compaiono nella barra di navigazione solo se configurati correttamente:
1. Il flusso di lavoro deve essere **attivato** (non in modalità bozza)
2. Il posizionamento nella barra di navigazione deve essere impostato su **Pinned**
3. Per i trigger Single/Bulk, devi trovarti nella pagina dell'oggetto corretta
**Per verificare**: Apri il flusso di lavoro → fai clic sul trigger → verifica che "Navbar placement" sia impostato su "Pinned".
Puoi sempre accedere ai flussi di lavoro manuali tramite **Cmd + K** (o **Ctrl + K**) indipendentemente dalle impostazioni della barra di navigazione.
</Accordion>
<Accordion title="Qual è la differenza tra i trigger manuali Single, Global e Bulk?">
| Tipo | Record richiesti | Esecuzioni del workflow |
| ---- | ---------------- | ----------------------- |
\| **Global** | Nessuno | Una volta, senza input di record |
\| **Single** | Uno o più selezionati | Una volta per ogni record selezionato |
\| **Bulk** | Uno o più selezionati | Una volta, con tutti i record come array |
* **Global**: Usalo quando il flusso di lavoro non richiede alcun contesto di record (ad es., generare un report)
* **Single**: Usalo quando vuoi elaborare ogni record selezionato in modo indipendente (ad es., inviare email singole)
* **Bulk**: Usalo quando devi elaborare i record insieme o ottimizzare l'uso dei crediti (richiede l'azione Iterator)
Vedi [Trigger dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-triggers) per i dettagli.
</Accordion>
<Accordion title="Non vedo un nodo If/Else. Cosa devo fare?">
Un nodo If/Else esplicito non è ancora disponibile ma è nella nostra roadmap.
**Soluzione temporanea**: Crea più diramazioni dal tuo passaggio, ciascuna iniziando con un'azione **Filtro**:
```
Step 1
│
├── Branch A: Filter (condition = true) → Actions...
│
└── Branch B: Filter (condition = false) → Actions...
```
Solo la diramazione in cui la condizione del filtro è soddisfatta eseguirà le azioni successive.
Consulta [Come usare le diramazioni](/l/it/user-guide/workflows/capabilities/workflow-branches) per una guida passo passo.
</Accordion>
<Accordion title="Le diramazioni del flusso di lavoro vengono eseguite in parallelo?">
**Sì**, le diramazioni vengono eseguite in parallelo per impostazione predefinita.
Se vuoi che venga eseguita solo una diramazione:
* Aggiungi un'azione **Filtro** all'inizio di ogni diramazione
* Imposta condizioni opposte (ad es., Diramazione A: stato = "Open", Diramazione B: stato ≠ "Open")
Le diramazioni che non soddisfano la loro condizione di filtro interrompono l'esecuzione, mentre le altre continuano.
</Accordion>
<Accordion title="Posso riunire le diramazioni?">
**Sì**. Dopo che le diramazioni parallele sono state completate, puoi aggiungere un passaggio a cui si colleghino entrambe.
Nell'editor dei flussi di lavoro:
1. Completa le azioni nelle diramazioni
2. Aggiungi un nuovo passaggio dopo le diramazioni
3. Trascina le connessioni dalla fine di ciascuna diramazione verso questo nuovo passaggio
Il passaggio unificato verrà eseguito dopo il completamento di tutte le diramazioni collegate.
</Accordion>
<Accordion title="Qual è il limite dei risultati di Search Records?">
**Search Records restituisce un massimo di 200 record.**
Se devi elaborarne di più:
* Aggiungi filtri più specifici per ridurre i risultati
* Usa flussi di lavoro pianificati per elaborare in lotti
* Valuta di usare le API per operazioni in blocco
Per la maggior parte dei flussi di lavoro, 200 record sono sufficienti. Se raggiungi regolarmente questo limite, valuta di ristrutturare l'automazione.
</Accordion>
<Accordion title="Posso mettere in CC persone nelle email dei flussi di lavoro?">
**Non ancora.** I campi CC e BCC per l'azione Send Email sono nella nostra roadmap.
**Soluzione temporanea**: Aggiungi più azioni Send Email per inviare ad altri destinatari, oppure usa una HTTP Request per inviare tramite un servizio email esterno che supporti CC.
</Accordion>
<Accordion title="Come si usano gli output dai passaggi precedenti?">
Ogni azione produce dati di output che possono essere utilizzati nei passaggi successivi.
**Per fare riferimento ai dati del passaggio precedente**:
* Usa il selettore di variabili durante la configurazione di un campo
* Oppure digita `{{stepName.fieldName}}` direttamente
**Esempi**:
* Dati del trigger: `{{trigger.object.email}}`
* Risultati della ricerca: `{{searchRecords[0].name}}`
* Output del codice: `{{code.calculatedValue}}`
Passa il mouse su qualsiasi campo nella configurazione dell'azione per vedere le variabili disponibili dai passaggi precedenti.
</Accordion>
<Accordion title="Perché il mio Iterator non riesce?">
**Iterator richiede un input di tipo array.** Problemi comuni:
1. **L'input non è un array**: Assicurati di passare i risultati di Search Records o di un'altra azione che restituisce un array
2. **L'array è vuoto**: Aggiungi un filtro prima di Iterator per verificare `{{searchRecords.length}} > 0`
3. **Variabile sbagliata selezionata**: Assicurati di selezionare l'array stesso, non un singolo record
**Configurazione corretta**:
1. Search Records (restituisce un array)
2. Filtro: length > 0
3. Iterator: seleziona `{{searchRecords}}`
4. Le azioni all'interno di Iterator usano `{{iterator.currentItem.fieldName}}`
</Accordion>
<Accordion title="Qual è il tempo massimo di esecuzione per le azioni di codice?">
Le azioni di codice (funzioni serverless) hanno un **timeout predefinito di 5 minuti** (300 secondi).
Il timeout massimo configurabile è di **15 minuti** (900 secondi).
Se il tuo codice supera questo limite, l'azione non riuscirà con un errore di timeout.
**Suggerimenti per evitare timeout**:
* Suddividi le operazioni grandi in parti più piccole usando Iterator
* Evita elaborazioni pesanti; usa servizi esterni tramite HTTP Request per processi intensivi
* Ottimizza il tuo codice per ridurre il tempo di esecuzione
* Se hai bisogno di elaborazioni più lunghe, valuta l'uso di flussi di lavoro pianificati che elaborano i dati in lotti
</Accordion>
<Accordion title="Come leggere le esecuzioni dei flussi di lavoro?">
Le esecuzioni dei flussi di lavoro mostrano la cronologia di esecuzione e ti aiutano a eseguire il debug dei problemi.
**Accesso alle esecuzioni**:
* Nell'editor dei flussi di lavoro → pannello **Runs** a destra
* Oppure vai a **Workflow Runs** nella barra laterale
**Comprendere un'esecuzione**:
* **Stato**: In esecuzione, Completato, Non riuscito, In attesa
* **Passaggi**: Vedi quali passaggi sono stati eseguiti e il loro output
* **Errori**: Fai clic sui passaggi non riusciti per vedere i messaggi di errore
* **Dati**: Visualizza i dati di input/output a ogni passaggio
Vedi [Esecuzioni dei flussi di lavoro](/l/it/user-guide/workflows/capabilities/workflow-runs) per i dettagli.
</Accordion>
<Accordion title="Perché non vedo le esecuzioni dei miei flussi di lavoro?">
Le esecuzioni dei flussi di lavoro potrebbero non riuscire immediatamente a causa dei limiti di frequenza. Twenty impone un limite rigido di 5.000 esecuzioni all'ora per spazio di lavoro.
Se superi questo limite, i flussi di lavoro vengono immediatamente contrassegnati come non riusciti e non appariranno nell'elenco delle esecuzioni.
**Scenari comuni che raggiungono questo limite**:
* Selezionare più di 5.000 record con un trigger manuale Single
* Più flussi di lavoro in esecuzione simultaneamente nel tuo spazio di lavoro
* Trigger automatici ad alta frequenza (ad es., Record Updated su un oggetto molto attivo)
**Soluzioni**:
* Usa i trigger **Bulk** invece di Single per elaborare molti record in un'unica esecuzione
* Scagliona nel tempo le operazioni in grandi batch
* Usa filtri per ridurre la frequenza dei trigger
* Pianifica i flussi di lavoro pesanti nelle ore di minore traffico
</Accordion>
<Accordion title="Quali sono i limiti di frequenza dei flussi di lavoro?">
Twenty ha due limiti di frequenza per garantire la stabilità del sistema:
| Limite | Valore | Comportamento |
| ------ | ------ | ------------- |
\| **Soft limit** | 100 esecuzioni/minuto | Le esecuzioni vanno in coda con stato "Not Started", elaborate gradualmente |
\| **Hard limit** | 5.000 esecuzioni/ora | Le esecuzioni falliscono immediatamente |
**Limite soft (100/min)**: I tuoi flussi di lavoro non falliranno: semplicemente attendono in coda e vengono elaborati nel tempo. Puoi attivare più di 100 record; l'esecuzione sarà più lenta.
**Limite hard (5.000/ora)**: Si applica all'intero spazio di lavoro. Se tutti i tuoi flussi di lavoro combinati superano 5.000 esecuzioni in un'ora, le esecuzioni aggiuntive falliranno immediatamente.
**Suggerimenti per rimanere entro i limiti**:
* Usa trigger Bulk con Iterator invece dei trigger Single per grandi batch
* Combina automazioni correlate in un numero minore di flussi di lavoro
* Usa flussi di lavoro pianificati per distribuire il carico nel tempo
</Accordion>
<Accordion title="Posso ritentare un'esecuzione di flusso di lavoro non riuscita?">
**No, al momento non esiste una funzionalità di ritentativo automatico.**
Se un'esecuzione di flusso di lavoro non riesce, dovrai:
1. Rivedi l'errore in **Impostazioni → Flussi di lavoro → [Il tuo flusso di lavoro] → Runs**
2. Risolvi il problema (dati, configurazione o servizio esterno)
3. Attiva manualmente di nuovo il flusso di lavoro sui record interessati
**Suggerimenti per ridurre gli errori**:
* Aggiungi nodi **Filtro** per convalidare i dati prima delle azioni
* Usa **Search Records** per verificare se esistono record correlati
* Esegui test accurati con pochi record prima delle operazioni in blocco
La funzionalità di ritentativo automatico è nella nostra roadmap per un rilascio futuro.
</Accordion>
<Accordion title="Devo disattivare i flussi di lavoro prima delle importazioni CSV?">
**Sì, se i tuoi flussi di lavoro sono attivati dalla creazione o dall'aggiornamento dei record.**
Quando importi dati tramite CSV, ogni record creato o aggiornato può attivare flussi di lavoro. Una grande importazione (migliaia di record) potrebbe:
* Raggiungere il limite di 5.000 esecuzioni/ora
* Consumare una quantità significativa di crediti dei flussi di lavoro
* Inviare email o notifiche inattese
* Creare attività o record duplicati
**Prima di un'importazione massiva**:
1. Vai a **Impostazioni → Flussi di lavoro**
2. Identifica i flussi di lavoro attivati dall'oggetto che stai importando
3. **Disattivali** temporaneamente
4. Esegui l'importazione CSV
5. **Riattiva** i flussi di lavoro al termine
**Alternativa**: Se hai bisogno che i flussi di lavoro vengano eseguiti sui dati importati, importa in batch più piccoli per rimanere entro i limiti di frequenza.
</Accordion>
<Accordion title="Come posso pulire il layout del mio flusso di lavoro?">
Se il canvas del flusso di lavoro sembra disordinato, con nodi sparsi, puoi organizzarli automaticamente:
1. Fai clic con il pulsante destro in un punto qualsiasi del canvas del flusso di lavoro
2. Fai clic su **Tidy up workflow**
Questo riorganizzerà automaticamente tutti i nodi in un layout pulito e ordinato.
</Accordion>
</AccordionGroup>