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,167 @@
---
title: 워크플로우를 통해 이메일 보내기
description: 워크플로우 액션을 사용해 개인화된 이메일을 자동으로 보냅니다.
---
CRM에서 특정 이벤트가 발생하면 자동으로 이메일을 보냅니다—신규 연락처 환영, 기회 팔로업, 팀원 알림 등.
## 사전 준비
워크플로우에서 이메일을 보내기 전에:
1. **설정 → 계정**에서 이메일 계정을 연결하세요
2. 계정에 발송 권한이 활성화되어 있는지 확인하세요
## 기본 이메일 워크플로우
### 예시: 신규 연락처 환영 이메일
**목표**: CRM에 새 연락처가 추가되면 환영 이메일을 보냅니다.
**설정**:
1. **워크플로우 생성**: **설정 → 워크플로우**로 이동하여 **+ 새 워크플로우**를 클릭하세요
2. **트리거 추가**: **레코드가 생성됨** → **People**를 선택하세요
3. **이메일 보내기 액션 추가**:
* 액션을 추가하려면 \*\*+\*\*를 클릭하세요
* **이메일 보내기**를 선택하세요
* 이메일을 구성하세요:
| 필드 | 값 |
| --------- | -------------------------------------- |
| **받는 사람** | `{{trigger.object.email}}` |
| **제목** | `{{Your Company Name}}에 오신 것을 환영합니다` |
| **본문** | `Hi {{trigger.object.firstName}}, ...` |
4. **테스트 및 활성화**: 샘플 레코드로 테스트한 다음 활성화하세요
## 이메일에서 변수 사용
`{{variable}}` 구문을 사용하여 이전 단계의 데이터를 참조하세요:
```text
Hi {{trigger.object.firstName}},
Thank you for connecting with us!
Your company, {{trigger.object.company.name}}, is now in our system.
Best regards,
The Team
```
### 트리거에서 사용 가능한 변수
| 트리거 유형 | 공통 변수 |
| ---------------- | -------------------------------------- |
| **레코드 생성/업데이트됨** | `{{trigger.object.fieldName}}` |
| **수동** | `{{trigger.selectedRecord.fieldName}}` |
| **Webhook** | `{{trigger.body.fieldName}}` |
## 고급: 조건부 이메일
### 예시: 리드 소스에 따라 다른 이메일
**목표**: 리드의 유입 경로에 따라 서로 다른 환영 이메일을 보냅니다.
**설정**:
1. **트리거**: 레코드가 생성됨 (People)
2. **필터 액션 추가**:
* 조건: `{{trigger.object.source}}`가 "Website"와 같음
* 참이면 → 웹사이트 환영 이메일로 계속
3. **다른 소스를 위한 분기**:
* 서로 다른 소스에 대해 병렬 분기를 만드세요
* 각 분기에는 자체 이메일 보내기 액션이 있습니다
## 여러 수신자에게 이메일 보내기
### 예시: 거래 성사 시 팀에 알림
**목표**: 기회가 성사되면 영업 담당자와 해당 관리자에게 이메일을 보냅니다.
**설정**:
1. **트리거**: 레코드가 업데이트됨 (기회, 단계 = "Closed Won")
2. **레코드 검색**: 기회 소유자의 관리자를 찾으세요
3. **이메일 보내기 #1**: 기회 소유자에게
* 받는 사람: `{{trigger.object.owner.email}}`
* 제목: `Congratulations on closing {{trigger.object.name}}!`
4. **이메일 보내기 #2**: 관리자에게
* 받는 사람: `{{searchRecords.manager.email}}`
* 제목: `Deal Won: {{trigger.object.name}}`
## 예약된 후속 이메일
### 예시: 회의 3일 후 팔로업
**목표**: 회의가 기록된 후 3일 뒤에 팔로업 이메일을 보냅니다.
**설정**:
1. **트리거**: 레코드가 생성됨 (활동, 유형 = "회의")
2. **지연 액션**: 3일 대기
3. **이메일 보내기**:
* 받는 사람: 회의 참석자
* 제목: 우리의 대화에 대한 후속 안내
* 본문: 트리거에서 회의 세부 정보를 참조하세요
## 이메일 첨부 파일
워크플로우에서 발송되는 이메일에 파일을 첨부할 수 있습니다. 첨부 파일은 워크플로우를 구성할 때 업로드하는 정적 파일입니다.
### 첨부 파일 추가 방법
1. **이메일 보내기** 작업에서 **첨부 파일** 버튼을 클릭합니다
2. 클릭하여 컴퓨터에서 파일을 업로드합니다
3. 이 작업으로 전송되는 모든 이메일에 해당 파일이 첨부됩니다
### 일반적인 사용 사례
| 사용 사례 | 예시 파일 |
| ------------ | --------------- |
| **환영 이메일** | 회사 소개 브로셔(PDF) |
| **온보딩 시퀀스** | 시작 가이드 |
| **영업 후속 조치** | 제품 카탈로그 또는 가격표 |
| **이벤트 확인** | 이벤트 세부 정보 또는 일정 |
<Note>
첨부 파일은 정적입니다—모든 수신자에게 동일한 파일이 전송됩니다. 개인화된 견적서와 같은 동적 문서의 경우 [로직 함수](/l/ko/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty)를 사용하여 파일을 생성하고 첨부합니다.
</Note>
## 모범 사례
### 이메일 콘텐츠
* 제목은 간결하고 관련성 있게 유지하세요
* 수신자 이름으로 개인화하세요
* 명확한 행동 유도를 포함하세요
* 활성화 전에 이메일을 테스트하세요
### 도달률
* 너무 많은 이메일을 너무 빠르게 보내지 마세요
* 전문적인 이메일 서명을 사용하세요
* 스팸 트리거 단어를 피하세요
* 마케팅 이메일에 수신 거부 옵션이 있도록 하세요
### 문제 해결
* 이메일 계정이 연결되어 있고 활성 상태인지 확인하세요
* 수신자 이메일 주소가 유효한지 확인하세요
* 워크플로우 실행 내역에서 오류 메시지를 확인하세요
* 먼저 자신의 이메일 주소로 테스트하세요
## 관련 항목
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,88 @@
---
title: 워크플로우에서 분기 사용
description: 분기가 어떻게 작동하는지와 어떤 경로가 실행될지 제어하는 방법을 이해하세요.
---
## 분기 작동 방식
워크플로우 편집기에서 단일 노드에서 여러 경로(분기)를 만들 수 있습니다. 이를 통해 서로 다른 결과를 갖는 복잡한 자동화를 구축할 수 있습니다.
**중요**: 워크플로우가 실행될 때, **기본적으로 모든 분기가 병렬로 실행됩니다**. 한 분기를 다른 분기보다 선택하는 내장된 "if/else" 로직은 없습니다—모든 경로가 동시에 실행됩니다.
## 어떤 분기가 실행될지 제어하기
특정 조건에 따라 하나의 분기만 실행하려면, **각 분기의 시작 부분에 필터 노드를 추가하세요**.
### 예시 설정
1. 단일 노드에서 여러 분기로 워크플로우를 생성하세요
2. 각 분기의 첫 단계로 **필터** 노드를 추가하세요
3. 각 필터에 조건을 설정하여 해당 분기를 언제 계속할지 결정하세요
4. 필터 조건을 충족하는 분기만 진행됩니다
### 필터 작동 방식
* 필터 조건이 **충족되면**: 분기가 계속 실행됩니다
* 필터 조건이 **충족되지 않으면**: 분기는 필터 노드에서 중지됩니다
이렇게 하면 데이터에 따라 해당되는 분기만 실행되는 조건부 로직이 사실상 만들어집니다.
## 예시: 거래 규모별 라우팅
**시나리오**: 거래가 마감되면 거래 규모에 따라 다른 알림을 보냅니다.
1. **트리거**: 기회가 업데이트됨 (단계 = Closed Won)
2. **분기 1**: 금액 > $10,000에 대한 필터 → Slack 메시지를 #big-deals에 보내기
3. **분기 2**: 금액 ≤ $10,000에 대한 필터 → 영업 관리자에게 이메일 보내기
두 분기 모두 시작되지만, 거래 금액과 일치하는 분기만 필터를 통과해 계속됩니다.
## 분기 생성
<Note>
기존 단계에서 새 분기를 만들려면 해당 단계의 **+** 버튼을 클릭하고 액션을 추가하세요. \*\*+\*\*를 여러 번 클릭하여 여러 분기를 추가할 수 있습니다.
</Note>
1. 워크플로우 편집기에서 분기를 만들고자 하는 단계를 선택하세요
2. 액션을 추가하려면 **+** 버튼을 클릭하세요
3. 이렇게 하면 하나의 분기가 생성됩니다
4. 같은 단계에서 \*\*+\*\*를 다시 클릭하여 추가 분기를 만드세요
5. 각 분기는 자체 액션 시퀀스를 가질 수 있습니다
## 분기를 다시 병합하기
병렬 분기가 작업을 완료한 후, 다시 하나의 경로로 병합할 수 있습니다:
1. 분기된 액션을 완료하세요
2. 모든 분기 이후에 실행되어야 하는 새 단계를 추가하세요
3. 각 분기의 마지막 단계에서 이 새 단계로 연결선을 드래그하세요
4. 병합된 단계는 연결된 모든 분기가 완료될 때까지 대기한 후 실행됩니다
### 예시: 처리 후 알림
```
Trigger
│
├── Branch A: Update Customer Record
│
└── Branch B: Create Support Ticket
↘ ↙
Merged Step: Send Confirmation Email
```
확인 이메일은 고객 업데이트와 티켓 생성이 모두 완료된 후에만 전송됩니다.
## 모범 사례
* 조건부 실행을 원할 때는 분기 시작에서 항상 **필터 노드**를 사용하세요
* 중복 액션을 피하려면 분기 조건을 **서로 배타적**으로 유지하세요
* 올바른 분기가 실행되는지 확인하기 위해 다양한 데이터로 워크플로우를 테스트하세요
* **분기 단계의 이름을 바꾸어** 각 경로가 무엇을 하는지 명확히 알 수 있도록 설명적으로 지정하세요
* 병렬 처리 후 최종 액션이 필요할 때는 **분기를 병합**하세요
## 관련 항목
* [워크플로우 FAQ](/l/ko/user-guide/workflows/how-tos/need-more-help/workflows-faq) — 병렬 실행에 대한 답변
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions) — 분기에서 사용할 수 있는 액션

View File

@@ -0,0 +1,183 @@
---
title: Iterator 사용
description: 레코드 배열을 순회하여 각 항목에 대해 작업을 수행합니다.
---
Iterator를 사용하면 레코드 배열을 반복하면서 각 항목에 대해 작업을 수행할 수 있습니다. 이는 Search Records가 반환하거나 웹훅으로 수신한 여러 레코드를 처리해야 하는 워크플로우에 필수적입니다.
## Iterator 사용 시기
| 시나리오 | 예시 |
| ------------ | -------------------- |
| **검색 결과 처리** | 찾은 각 사람에게 이메일 보내기 |
| **웹훅 배열 처리** | 각 항목에 대해 순서대로 레코드 생성 |
| **대량 업데이트** | 계산된 값으로 여러 레코드를 업데이트 |
| **알림** | 이벤트에 대해 여러 사람에게 알림 |
## Iterator 이해하기
Iterator는 입력으로 **배열**을 필요로 합니다. 그다음:
1. 배열에서 첫 번째 항목을 가져옵니다
2. 해당 항목으로 Iterator 내부의 모든 액션을 실행합니다
3. 다음 항목으로 이동합니다
4. 모든 항목이 처리될 때까지 반복합니다
### 반복할 배열 선택하기
대부분의 배열 소스(레코드 검색, 웹훅 배열 필드, 일괄 수동 트리거)는 변수 선택기에서 직접 선택할 수 있습니다.
**Code** 또는 **Logic Function** 단계가 *최상위 배열*을 반환하면, 그 출력은 변수 선택기에서 인덱스가 매겨진 항목들(`0`, `1`, `2`, …)로 표시됩니다. 배열 전체를 순회하려면 해당 단계에서 **Whole list** 옵션을 선택하세요. 그러면 Iterator가 목록에서 각 항목의 구조를 자동으로 추론합니다.
## 기본 설정
### 예시: 검색 결과의 모든 사람에게 이메일 보내기
**목표**: 특정 회사의 모든 연락처를 찾아 각 사람에게 개인화된 이메일을 보냅니다.
### 1단계: 레코드 검색
1. **레코드 검색** 액션을 추가합니다
2. 오브젝트: **People**
3. 필터: Company가 "Acme Inc"와 같음
4. 이렇게 하면 사람들의 배열이 반환됩니다
### 2단계: 결과 존재 여부 확인
1. **필터** 액션을 추가합니다
2. 조건: `{{searchRecords.length}}`가 0보다 큼
3. 이렇게 하면 결과가 비어 있을 때 발생하는 Iterator 오류를 방지합니다
### 3단계: 이터레이터 추가
1. **Iterator** 액션을 추가합니다
2. 배열 입력: `{{searchRecords}}` 선택
3. 이렇게 하면 루프가 생성됩니다
### 4단계: Iterator 내부에 액션 추가
Iterator 아래에 배치된 액션은 각 항목에 대해 실행됩니다:
1. Iterator 내부에 **Send Email** 액션을 추가합니다
2. 받는 사람: `{{iterator.currentItem.email}}`
3. 제목: 안녕하세요 `{{iterator.currentItem.firstName}}`!
4. 본문: 현재 항목의 필드를 사용한 개인화 메시지
### 결과
Search Records가 5명의 사람을 반환하면, Iterator는 다음을 수행합니다:
* 사람 1에게 이메일을 보냅니다
* 사람 2에게 이메일을 보냅니다
* ... 5명 모두에 대해 계속 진행합니다
## 현재 항목 데이터에 접근하기
Iterator 내부에서 현재 레코드에 접근하려면 `{{iterator.currentItem}}`를 사용하세요:
| 변수 | 설명 |
| --------------------------------------- | ------------------- |
| `{{iterator.currentItem}}` | 현재 레코드 객체 전체 |
| `{{iterator.currentItem.id}}` | 레코드 ID |
| `{{iterator.currentItem.email}}` | 이메일 필드 |
| `{{iterator.currentItem.company.name}}` | 연관된 회사 이름 |
| `{{iterator.index}}` | 배열에서의 현재 위치(0부터 시작) |
변수 선택기에서 현재 항목의 특정 필드로 들어가거나, **Use the whole item**을 선택해 현재 항목 전체(`{{iterator.currentItem}}`)를 참조할 수 있습니다. 전체 항목을 선택하면, 필드를 하나씩 다시 구성하는 대신 전체 레코드를 그대로 후속 단계로 전달하고자 할 때 유용합니다.
## 일반적인 패턴
### 여러 레코드 업데이트
**목표**: 기한이 지난 모든 작업을 "Late"로 표시
```
1. Search Records (Tasks, Due Date < Today, Status ≠ Completed)
2. Filter (length > 0)
3. Iterator (searchRecords)
└── Update Record
- Object: Tasks
- Record: {{iterator.currentItem.id}}
- Status: Late
```
### 배열에서 레코드 생성
**목표**: 웹훅이 여러 항목이 있는 주문을 수신하면 각 항목에 대해 레코드를 생성
```
1. Webhook Trigger (receives items array)
2. Filter (items.length > 0)
3. Iterator (trigger.body.items)
└── Create Record
- Object: Order Items
- Name: {{iterator.currentItem.name}}
- Quantity: {{iterator.currentItem.qty}}
- Related Order: {{trigger.body.orderId}}
```
### 루프 내부 조건부 처리
**목표**: 유효한 이메일이 있는 연락처에게만 이메일 보내기
```
1. Search Records (People)
2. Iterator (searchRecords)
└── Filter (currentItem.email is not empty)
└── Send Email
- To: {{iterator.currentItem.email}}
```
## 문제 해결
### "Iterator는 배열을 필요로 합니다"
**원인**: 배열이 아닌 단일 레코드를 전달했습니다.
**해결 방법**: 단일 레코드가 아니라 Search Records의 결과 또는 배열 필드를 전달했는지 확인하세요.
```
✅ Correct: {{searchRecords}}
❌ Wrong: {{searchRecords[0]}}
```
### Iterator가 실행되지 않음
**원인**: 배열이 비어 있습니다.
**해결 방법**: 배열 길이를 확인하기 위해 Iterator 앞에 Filter를 추가하세요:
```
Filter: {{searchRecords.length}} > 0
```
### 액션이 너무 많이 실행됨
**원인**: Search Records가 예상보다 많은 레코드를 반환했습니다.
**해결 방법**:
* Search Records에 더 구체적인 필터를 추가하세요
* Search Records에 한도를 설정하세요(최대 200)
* 추가 조건을 위해 Iterator 내부에 Filter를 추가하세요
## 성능 고려 사항
* **크레딧 사용량**: 각 반복은 해당 액션 실행에 크레딧을 소모합니다
* **시간**: 큰 배열일수록 처리에 더 오래 걸립니다
* **제한**: 매우 큰 작업은 배치 처리하는 것을 고려하세요
* **요청 제한**: 반복이 많으면 외부 API 호출이 요청 제한에 걸릴 수 있습니다
## 모범 사례
1. 오류를 피하려면 Iterator 전에 **항상 배열 길이를 확인하세요**
2. 모든 항목을 처리할 필요가 없는 경우 **루프 내부에 필터를 추가하세요**
3. 순회 대상을 설명하도록 **Iterator 단계의 이름을 바꾸세요**
4. **작은 배열로 테스트하세요** 대용량 데이터를 처리하기 전에
5. 반복이 예상대로 완료되는지 확인하려면 **워크플로우 실행을 모니터링하세요**
## 관련 항목
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [브랜치 사용 방법](/l/ko/user-guide/workflows/capabilities/use-branches-in-workflows)
* [워크플로우 FAQ](/l/ko/user-guide/workflows/how-tos/need-more-help/workflows-faq)

View File

@@ -0,0 +1,332 @@
---
title: 워크플로우 액션
description: Twenty 워크플로에서 사용할 수 있는 동작에 대해 알아보세요.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
## 동작 소개
트리거가 발생한 후의 동작 정의. 여러 동작을 서로 연결해 복잡한 자동화를 구축할 수 있습니다.
<Tip>
* 변수 선택기(`(x+)` 아이콘)을 클릭하여 이전 단계의 사용 가능한 데이터를 탐색하세요.
* 어떤 입력 필드 위에 마우스를 올려두면 변수가 어느 단계에서 왔는지 확인할 수 있습니다 — 동일한 필드(예: ID)가 여러 이전 단계에 존재하는 경우 유용합니다.
* 유지 관리를 쉽게 하려면 각 동작에 설명적인 이름을 지정하세요.
</Tip>
## 레코드 동작
<img src="/images/user-guide/workflows/workflow_actions.png" style={{width:'100%'}} />
### 레코드 생성
선택된 객체에 새 레코드 추가.
**구성**:
* 대상 객체 선택
* 필수 및 선택 필드 작성
* 이전 단계의 데이터를 사용하거나 값을 수동으로 입력하여 필드를 채우세요.
**출력**: 새로 생성된 레코드 데이터는 다음 단계에서 사용 가능.
### 레코드 업데이트
선택된 객체에서 기존 레코드 수정.
<img src="/images/user-guide/workflows/update_record.png" style={{width:'100%'}} />
**구성**:
* 대상 객체 선택
* 업데이트할 특정 레코드 선택.
* 사용 가능한 모든 레코드를 표시하는 드롭다운 메뉴에서 고정된 레코드를 선택할 수도 있습니다.
* 또는 `(x+)`를 사용해 이전 단계에서 찾은 레코드를 지정하여 레코드를 동적으로 선택하도록 할 수 있습니다. 이 단계에서는 서로 다른 기준으로 레코드를 검색할 수 없습니다. 아직 레코드를 식별하지 못한 경우, 이 `Update Record` 단계 전에 `Search Record` 단계를 추가하세요.
* 수정할 필드 선택 및 새 값 입력
**출력**: 업데이트된 레코드 데이터는 다음 단계에서 사용 가능.
### 레코드 삭제
선택된 객체에서 레코드를 제거.
**구성**:
* 대상 객체 선택
* 삭제할 특정 레코드 선택
**출력**: 삭제된 레코드 데이터는 다음 단계에서 사용 가능.
### 레코드 검색
필터 조건을 사용하여 선택된 객체 내에서 레코드 찾기.
**구성**:
* 검색할 객체 선택
* 결과 범위를 좁히기 위한 필터 기준 설정
* 정렬 및 제한 구성
**출력**: 매칭된 레코드를 반환하여 다음 단계에서 사용 가능.
<Note>
**제한**: Search Records는 최대 **200개 레코드**를 반환합니다. 더 많이 처리해야 하는 경우, 결과를 줄이기 위해 특정 필터를 추가하거나 예약된 워크플로를 사용해 배치로 처리하세요.
</Note>
**모범 사례**: Search Records 이후 [분기](/l/ko/user-guide/workflows/capabilities/workflow-branches)를 사용하여 "발견됨" vs "찾을 수 없음" 시나리오를 처리하세요.
### 레코드 업서트
일치 기준에 따라 새 레코드를 생성하거나 기존 레코드를 업데이트합니다. 레코드가 이미 존재하는지 확실하지 않을 때 유용합니다.
<img src="/images/user-guide/workflows/upsert_double_identifier.png" style={{width:'100%'}} />
**구성**:
* 대상 객체 선택
* 일치에 사용할 수 있는 필드를 확인하세요: People에는 이메일, Companies에는 도메인, 모든 오브젝트에는 ID, 또는 고유(Unique)로 표시된 임의의 필드. 아래 항목 중 최소 하나는 채워야 합니다.
* 필드 값을 입력하세요. 고유 식별자 중 최소 하나는 반드시 입력하세요.
<Note>
**고유 식별자를 하나만 추가하면 일치가 더 잘 작동하는 경우가 많습니다.** 예를 들어 아래 스크린샷은 도메인을 기준으로 회사를 일치시킵니다. ID가 반드시 필요한 것은 아닙니다.
</Note>
<img src="/images/user-guide/workflows/upsert_simple_identifier.png" style={{width:'100%'}} />
* 이전 단계의 데이터를 사용하여 필드 채우기
**작동 원리**:
1. 조건에 맞는 레코드를 검색합니다
2. 찾으면 → 기존 레코드를 업데이트합니다
3. 찾지 못하면 → 새 레코드를 생성합니다
**출력**: 생성되었거나 업데이트된 레코드 데이터를 이후 단계에서 사용할 수 있습니다.
## 플로우 작업
### 반복자
**이전 단계에서 반환된 레코드 배열을 반복 처리**하여 각 레코드에 대해 개별적으로 작업을 수행할 수 있습니다.
**구성**:
* 이전 단계의 레코드 배열을 선택합니다(예: Search Records 결과, 대량 사용이 가능한 수동 트리거, 코드 노드). Code 또는 Logic Function 단계가 최상위 배열을 반환하는 경우, **Whole list** 옵션을 선택하여 전체 출력에 대해 루프를 실행합니다.
* 반복 내에서 각 레코드에 대해 수행할 액션 정의.
<Note>
- 이터레이터 내에 여러 작업을 추가할 수 있습니다.
- 이터레이터 내부에 분기를 사용할 때, 각 분기의 마지막 단계가 루프를 닫도록 이터레이터로 다시 연결되는지 확인하세요.
</Note>
* `Current Item` 필드에 접근: 현재 처리 중인 레코드의 필드를 사용하려면 **Iterator** 단계에서 **Current item**을 선택하세요. 해당 레코드에서 사용 가능한 필드 목록이 표시되며, 이후 작업에서 사용하도록 선택할 수 있습니다. **Use the whole item**을 선택하여 현재 항목 전체를 다운스트림 단계로 전달할 수도 있습니다.
<VimeoEmbed videoId="1146577247" title="동영상 시연" />
### 필터
지정한 조건에 따라 레코드를 필터링하여 기준을 충족하는 레코드만 통과시킵니다.
**구성**:
* 필터링할 레코드를 선택합니다
* 필터 조건 및 기준 정의
* 다음 단계로 전달할 레코드 구성
<Note>
1. **출력**: 필터 노드는 데이터를 반환하지 않으며 게이트 역할을 합니다. 조건을 충족하면 워크플로우가 계속됩니다. 그렇지 않으면 해당 분기에서 워크플로우가 중지됩니다.
2. 숫자 필드에서 `IS` 연산자를 사용할 수 있습니다. 이는 `EQUAL`과 동일하게 동작합니다.
</Note>
### 지연
지정한 기간 또는 특정 날짜/시간까지 워크플로우 실행을 일시 중지합니다.
**지연 유형**:
| 유형 | 설명 |
| --------- | --------------------------- |
| **기간** | 지정한 시간(일, 시간, 분, 초)만큼 기다립니다 |
| **예약 날짜** | 특정 날짜와 시간까지 기다립니다 |
**기간 설정**:
* 일, 시간, 분 및/또는 초를 설정합니다
* 여러 단위를 조합할 수 있습니다(예: 2일 4시간).
**예약 날짜 설정**:
* 날짜와 시간을 선택합니다
* 이전 단계의 날짜 필드를 참조할 수 있습니다(예: 회의 3일 후에 후속 조치).
**사용 사례**:
* 후속 이메일을 보내기 전에 24시간 대기
* 영업 기회의 마감일까지 일시 중지
* 영업시간에 작업을 예약
<Note>
예약 날짜는 과거일 수 없습니다. 이전 단계의 날짜 필드를 사용했고 그 날짜가 이미 지났다면, 워크플로우가 실패합니다.
</Note>
**제한 및 크레딧**:
* **최대 기간 제한 없음**—분, 일, 주 또는 그 이상으로 지연을 설정할 수 있습니다
* 지속 시간과 무관하게 Delay 노드가 실행될 때 **크레딧 1개가 소모됩니다**
* 대기 중에는 **크레딧이 소모되지 않습니다**—5분 지연과 5일 지연의 비용은 동일합니다
## 커뮤니케이션 작업
### 이메일 보내기
워크플로우에서 이메일 전송. 템플릿이 있는 그룹 이메일에 적합합니다. 이메일은 사용자의 메일함에서 보내는 이메일과 동일하게 보입니다.
풍부한 서식이 필요한 뉴스레터나 자동 이메일 시퀀스에는 적합하지 않습니다.
**필수 조건**: 설정 > 계정에 이메일 계정 추가
**구성**:
* 발신 이메일 계정을 선택합니다
<Note>
자신의 Twenty 계정과 동기화된 메일함에서만 이메일을 보낼 수 있습니다. 다른 팀원의 메일함(예: 계정 소유자의 이메일)에서 보내는 기능은 향후 지원 예정입니다.
</Note>
이후 모든 단계에서 개인화를 위해 이전 단계의 변수를 참조할 수 있습니다.
* 수신자 이메일 주소 입력.
<Note>
현재는 수신자를 한 명만 지정할 수 있습니다.
</Note>
* 제목 설정.
* 메시지 본문 작성. 링크 서식 지정, 번호 매기기 목록 및 글머리 기호 목록 생성, 첨부 파일 추가가 가능합니다.
<Note>
현재는 HTML 서명을 추가할 수 없습니다.
</Note>
### 양식
워크플로우 실행 중에 양식을 표시하여 사용자 입력을 수집합니다. 이 응답은 이후 단계에서 입력을 기반으로 레코드 생성, 이메일 전송 또는 기타 작업 수행에 사용할 수 있습니다.
<Warning>
**양식은 수동 트리거 전용으로 설계되었습니다**. 다른 트리거 (레코드 생성, 업데이트 등)와 함께하는 워크플로우의 경우, 형식은 워크플로우 실행 인터페이스를 통해서만 접근 가능하여 기대와 달라짐. 2026년 자동 워크플로우에서 양식을 적절히 지원하기 위한 알림 센터가 출시될 예정입니다.
</Warning>
**구성**:
* 사용자가 입력해야 할 필드를 구성합니다. 각 필드에 대해 다음을 선택합니다
* 텍스트, 숫자, 날짜, 지정된 레코드, 선택 필드 중 유형 모든 오브젝트의 필드를 선택하여 사용할 수 있습니다.
* 라벨
* `Placeholder` 아래의 기본값(선택 사항)
* 양식 제목 편집
**출력**: 양식 응답은 다음 단계에서 사용 가능.
**예시**: "Quick Lead" 워크플로우는 모든 워크스페이스에서 기본으로 제공되며, 명령 메뉴 `Cmd+K` 어디에서나 사용할 수 있습니다.
**양식 작성 방법**:
* 명령 메뉴 `Cmd+K`에서 수동 워크플로우를 트리거하세요
* 사이드 패널에 표시된 양식을 작성하고 `Submit`을 클릭하세요.
<Note>
필드를 필수로 지정할 수 없습니다.
</Note>
<VimeoEmbed videoId="1147277989" title="동영상 시연" />
## 통합 작업
### 코드
워크플로우 내에서 사용자 정의 JavaScript를 실행합니다.
백그라운드에서는 각 Code 액션이 자체 **logic function**으로 지원됩니다. 이는 Twenty 플랫폼에서 실행되는 서버 측 TypeScript 함수입니다. Code 액션을 추가하면 Twenty가 해당 단계에 대한 전용 logic function을 생성하고 편집기를 인라인으로 노출하므로, 작성한 코드는 워크플로와 함께 존재하게 됩니다.
**구성**:
* 이전 단계의 변수에 접근. 변수 이름을 동적으로 편집할 수 있습니다.
<VimeoEmbed videoId="1147281795" title="동영상 시연" />
* 에디터에서 자바스크립트 코드 작성
* 다음 단계에서 사용할 변수를 반환
* 해당 단계에서 코드를 직접 테스트하세요
<Note>
코드에서 외부 API 키를 사용해야 하는 경우, 함수 본문에 직접 입력해야 합니다. 다른 곳에서 API 키를 구성한 뒤 로직 함수에서 참조할 수는 없습니다.
</Note>
<Tip>
**배열을 다루고 있나요?** 외부 시스템이나 이전 단계에서 전달된 배열이 문자열로 올 수 있습니다. 해결 방법은 [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)를 참조하세요.
</Tip>
<Tip>
코드 편집기 오른쪽 상단의 사각형 아이콘을 클릭하여 전체 화면으로 표시하세요 — 기본 편집기 너비가 제한되어 있어 유용합니다.
</Tip>
#### 워크플로 간에 logic function 재사용하기
인라인 Code 액션은 단발성 로직에는 유용하지만, 그 안의 코드는 해당 단일 단계에만 속합니다. 여러 워크플로 전반에서 동일한 로직을 공유하거나 앱에서 버전 관리되는 소스로 유지하려면, 각 Code 액션에 코드를 복사해 붙여넣는 대신 **재사용 가능한 logic function**을 정의하세요.
logic function은 앱에서 한 번(`defineLogicFunction`을 사용해) 정의되고, `workflowActionTriggerSettings`를 추가하여 워크플로 빌더에 노출됩니다. 앱이 설치되면, 해당 함수는 기본 제공 액션과 함께 워크플로 빌더에서 자체 액션으로 표시됩니다. 이를 선택하면 선언한 입력 스키마에서 렌더링된 입력 필드(다른 액션과 마찬가지로 변수 선택기 포함)가 표시되고, 인라인 JavaScript 없이 공유 코드를 실행합니다.
| | Code 액션 | 재사용 가능한 logic function |
| -------------- | ----------------------- | ------------------------ |
| **코드가 위치하는 곳** | 인라인 편집기, 단일 워크플로 단계에 연결 | 앱에서 정의되고, 소스 관리에서 버전 관리됨 |
| **재사용성** | 단계별로 복사해 붙여넣기 | 모든 워크플로에서 액션으로 추가 |
| **입력값** | 편집기에서 연결된 변수들 | 선언한 입력 스키마에서 렌더링된 필드 |
| **적합한 용도** | 빠른, 워크플로 전용 로직 | 여러 워크플로 전반에서 사용하는 공유 로직 |
둘 다 동일한 logic function 런타임에서 실행되므로, 인라인으로 프로토타이핑한 Code 액션은 최소한의 변경으로 재사용 가능한 logic function으로 승격할 수 있습니다.
<Tip>
logic function 정의 및 워크플로 빌더에 노출하는 방법에 대한 개발자용 레퍼런스는 [Logic Functions](/l/ko/developers/extend/apps/logic/logic-functions)를 참고하세요. 특히 **Exposing a function as an AI tool or workflow action** 섹션과 `workflowActionTriggerSettings`를 확인하세요.
</Tip>
### HTTP 요청
워크플로우의 일부로 외부 API에 요청을 보냅니다.
<img src="/images/user-guide/workflows/http_action.png" style={{width:'100%'}} />
**구성**:
* API 엔드포인트 URL 입력. 이전 단계의 매개변수를 사용할 수 있습니다.
* HTTP 메소드 선택 (GET, POST, PUT, PATCH, DELETE)
* 필수 헤더 및 값 추가
* 구조 미리보기를 위한 샘플 응답 제공
## AI 작업
### AI 에이전트 - 곧 제공 예정
워크플로우 내에서 AI 에이전트를 실행하여 지능형 작업을 수행합니다.
**구성**:
* **Agent**: 기존 AI 에이전트를 선택하거나 기본 에이전트를 사용합니다
* **Prompt**: AI 에이전트에 대한 지시문을 작성합니다
* 프롬프트에서 이전 단계의 변수를 참조합니다.
**AI 에이전트가 할 수 있는 일**:
* 데이터를 분석하고 요약합니다
* 레코드를 분류하거나 범주화합니다
* 텍스트 콘텐츠를 생성합니다
* 데이터를 기반으로 의사결정을 내립니다
* 도구를 사용하여 CRM 데이터와 상호작용합니다
**출력**: AI 에이전트의 응답을 이후 단계에서 사용할 수 있습니다. 에이전트에 구조화된 출력 스키마가 있는 경우 응답은 해당 형식을 따릅니다.
<Note>
AI 에이전트 작업은 사용된 AI 모델에 따라 워크플로우 크레딧을 소모합니다. 자세한 내용은 [워크플로우 크레딧](/l/ko/user-guide/workflows/capabilities/workflow-credits)을 참조하세요.
</Note>
<Note>
AI 에이전트는 역할 기반 권한을 준수합니다. **설정 → 멤버 → 역할**에서 에이전트에 특정 역할을 할당해 액세스할 수 있는 데이터를 제어할 수 있습니다. 자세한 내용은 [권한](/l/ko/user-guide/permissions-access/capabilities/permissions)을 참조하세요.
</Note>

View File

@@ -0,0 +1,64 @@
---
title: 워크플로우 분기
description: 워크플로우에서 병렬 경로와 조건부 로직을 생성하세요.
---
분기는 워크플로우를 여러 경로로 나누어 데이터에 따라 동시에 또는 조건부로 실행할 수 있게 해줍니다.
<img src="/images/user-guide/workflows/branches.png" style={{width:'100%'}} />
## 분기가 작동하는 방식
단일 노드에서 여러 연결을 만들면 각 경로가 분기가 됩니다. 기본적으로 **모든 분기는 병렬로 실행**되며—서로를 기다리지 않습니다.
## 분기 만들기
### 새 분기 추가
1. 워크플로우의 **메인 캔버스**(기존 노드가 아닌 곳)를 마우스 오른쪽 버튼으로 클릭하세요
2. **노드 추가**를 클릭하세요
3. 새 분기를 위한 노드 유형을 선택하세요
4. 이전 단계의 하단에서 이 새 작업의 상단으로 화살표를 드래그하세요
5. 같은 노드에서 더 많은 분기를 추가하려면 이 과정을 반복하세요
<Note>
각 분기는 독립적입니다. 분기를 추가해도 해당 노드의 다른 기존 경로에는 영향을 주지 않습니다.
</Note>
### 시각적 레이아웃
분기는 워크플로우 편집기에서 병렬 경로로 표시됩니다. 실행에 영향을 주지 않고 노드를 드래그하여 시각적 레이아웃을 재배치할 수 있습니다.
## 조건부 분기
모든 분기가 기본적으로 실행되므로, 실제로 어떤 경로를 실행할지 제어하려면 **Filter** 노드를 사용하세요:
| 분기 | 필터 조건 | 작업 |
| -- | --------- | ---------- |
| A | 단계 = "성공" | 축하 이메일 보내기 |
| B | 단계 = "실패" | 후속 작업 생성 |
| C | 단계 = "협상" | 관리자에게 알리기 |
1. 트리거 또는 작업에서 분기를 만드세요
2. 각 분기의 첫 단계로 **Filter** 노드를 추가하세요
3. 각 필터를 상호 배타적인 조건으로 구성하세요
4. 각 필터 뒤에 작업을 추가하세요
필터 조건을 충족하는 분기만 실행을 계속합니다.
## 분기 병합
**분기는 자동으로 병합되지 않습니다.** 각 분기는 끝날 때까지 독립적으로 실행됩니다. 처리 방식은 전적으로 유연합니다:
* **옵션 1: 분기를 분리된 상태로 유지**\n 각 분기는 후속 작업을 독립적으로 처리합니다. 분기가 합쳐질 필요가 없을 때 가장 간단한 접근 방식입니다.
* **옵션 2: 분기를 수동으로 병합**\n 워크플로우를 만들 때 여러 분기를 동일한 다운스트림 작업에 수동으로 연결할 수 있습니다. 각 분기의 끝에서 공통 노드로 화살표를 드래그하기만 하면 됩니다.
<Note>
실행을 일시 중지하려면 [Delay](/l/ko/user-guide/workflows/capabilities/workflow-actions#delay) 노드를 사용할 수 있지만, 현재는 "다른 분기가 끝날 때까지" 대기하도록 설정할 수 없습니다.
</Note>
## 관련 항목
* [워크플로우에서 분기 사용하는 방법](/l/ko/user-guide/workflows/capabilities/use-branches-in-workflows) - 단계별 가이드
* [워크플로우 작업](/l/ko/user-guide/workflows/capabilities/workflow-actions) - Filter를 포함한 사용 가능한 작업

View File

@@ -0,0 +1,75 @@
---
title: 워크플로우 크레딧
description: 워크플로우 크레딧 소모 및 관리 이해하기.
---
워크플로우 크레딧은 Twenty에서 자동화를 지원합니다. 작동 방식을 이해하면 비용을 최적화하고 자동화 예산을 효과적으로 관리할 수 있습니다.
## 크레딧 할당
워크플로우 크레딧은 요금제 티어가 아니라 청구 주기에 따라 할당됩니다:
| 청구 주기 | 크레딧 |
| --------- | --------- |
| **월간 구독** | 매월 5 크레딧 |
| **연간 구독** | 매년 50 크레딧 |
<Note>
표준 자동화에는 매월 5 크레딧이면 넉넉합니다. 대부분의 팀은 일반적인 워크플로우 사용으로 이 한도를 초과하지 않습니다. 추가 크레딧은 주로 고급 Code 작업 및 AI 기반 워크플로우에 필요합니다.
</Note>
## 크레딧 소비 방식 이해
워크플로우가 실행될 때 크레딧이 소모되며, 생성할 때는 아닙니다. 각 워크플로우 동작은 그 복잡성에 따라 크레딧을 소모합니다:
### 동작 유형별 크레딧 소비
* **기본 내부 작업**: 크레딧 소모량 매우 낮음
* 레코드 검색
* 레코드 생성
* 레코드 업데이트
* 레코드 삭제
* 양식 작업
* **복잡한 작업**: 높은 크레딧 소비
* 코드 작업 (JavaScript 실행)
* 외부 서비스로의 HTTP 요청
* **AI 기능**: 크레딧 소모량 높음
* AI 에이전트 작업은 사용된 AI 모델에 따라 워크플로우 크레딧을 소모합니다
* 더 복잡한 프롬프트와 더 긴 출력은 더 많은 크레딧을 사용합니다
* **Delay 작업**: 최소한의 크레딧 소모
* Delay 노드는 실행될 때 **크레딧 1개**를 소모합니다
* 대기 기간 동안에는 **크레딧이 소모되지 않습니다**
* 5분 지연은 5일 지연과 같은 크레딧이 소모됩니다
### 실시간 차감
워크플로우가 실행될 때 크레딧이 실시간으로 차감됩니다. 이는 다음을 의미합니다:
* 초안 워크플로우는 크레딧을 소모하지 않습니다
* 활성화된 실행 중인 워크플로우만 크레딧 할당을 사용합니다
* 실패한 워크플로우도 완료된 단계에 대해 크레딧을 소모합니다
## 크레딧 관리
### 크레딧 사용량 확인
1. **설정 → 결제**로 이동
2. 현재 크레딧 소비량과 잔액 확인
3. 사용 패턴을 모니터링하여 워크플로우 최적화
### 추가 크레딧 구입
플랜 할당을 초과하는 추가 크레딧이 필요한 경우:
1. **설정 → 결제**로 이동
2. 추가 크레딧 구매 옵션을 클릭합니다. 다양한 크기의 패키지가 제공됩니다.
3. 크레딧이 현재 잔액에 추가됩니다
## 모범 사례
* **배치 처리**: 대량 작업 및 이터레이터 작업을 효율적으로 사용하여 개별 작업 호출을 줄이세요
* **수동 트리거 최적화**: 수동 트리거의 경우, 한 번의 워크플로우 실행에서 여러 레코드를 처리하려면 `Bulk` 가용성을 선택하세요
* 효율성을 위한 코드 작업 최적화

View File

@@ -0,0 +1,93 @@
---
title: Workflow 실행들
description: 워크플로우 실행을 모니터링하고 관리합니다.
---
## 실행에 대하여
**실행**은 워크플로우 실행의 기록입니다. 레코드 이벤트, 일정, 수동 작업 또는 웹훅 등으로 워크플로우가 트리거될 때마다 새 실행이 생성됩니다.
## 실행 보기
### 워크플로우 편집기에서
1. 모니터링하려는 워크플로우를 엽니다
2. 오른쪽의 **실행** 패널을 클릭합니다
3. 최근 실행과 해당 상태 목록을 확인합니다
### 워크플로우 실행 보기에서
1. 사이드바에서 **워크플로우 실행**으로 이동합니다
2. 모든 워크플로우의 실행을 확인합니다
3. 상태, 워크플로우 또는 날짜로 필터링합니다
## 실행 상태
| 상태 | 설명 |
| -------- | --------------------------------------------- |
| **실행 중** | 워크플로우가 현재 실행 중입니다 |
| **완료됨** | 워크플로우가 성공적으로 완료되었습니다 |
| **실패** | 워크플로우에서 오류가 발생하여 중지되었습니다 |
| **대기 중** | 워크플로우가 일시 중지되었습니다(예: Delay 액션 대기 또는 양식 제출 대기) |
## 실행 세부정보
임의의 실행을 클릭하면 다음을 확인할 수 있습니다:
* **상태**: 실행의 현재 상태
* **시작 시각**: 실행이 시작된 시점
* **소요 시간**: 실행에 걸린 시간
* **트리거 데이터**: 워크플로우를 시작시킨 입력값
* **단계 출력**: 각 단계에서 반환된 데이터
* **오류 메시지**: 실행이 실패한 경우 어떤 문제가 있었는지
## 단계별 실행
각 실행에는 워크플로우의 진행 상황이 표시됩니다:
1. 성공적으로 완료된 단계를 확인합니다
2. 실패가 발생한 위치를 파악합니다
3. 단계 간에 전달된 데이터를 확인합니다
4. 단계 입력과 출력을 확인하여 문제를 디버깅합니다
## 오류 처리
실행이 실패한 경우:
1. 실패한 실행을 엽니다
2. 실패를 유발한 단계를 찾습니다
3. 오류 메시지에서 세부정보를 확인합니다
일반적인 문제:
* 필수 필드 누락
* 잘못된 데이터 형식
* 외부 API 오류
* 권한 문제
## 워크플로우 재실행
실행이 실패한 경우 다음을 수행할 수 있습니다:
* 근본 원인을 수정하고 다음 트리거를 기다립니다
* 수동 워크플로우의 경우 동일한 데이터 또는 업데이트된 데이터로 다시 트리거합니다
* 향후 실패를 방지하기 위해 워크플로우 논리를 점검합니다
## 성능 팁
### 실행 기록 관리
* 실행은 이력 참고를 위해 보관됩니다
* 매우 오래된 실행은 자동으로 보관될 수 있습니다
* 기록을 유지해야 하는 경우 실행 데이터를 내보냅니다
### 모니터링 모범 사례
* 새 워크플로우를 활성화한 후 실행을 정기적으로 확인합니다
* 실패한 실행을 검토하여 패턴을 파악합니다
## 관련 항목
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [워크플로우 문제 해결](/l/ko/user-guide/workflows/how-tos/need-more-help/workflow-troubleshooting)

View File

@@ -0,0 +1,137 @@
---
title: 워크플로우 트리거
description: 워크플로우를 시작하는 다양한 트리거에 대해 알아보세요.
---
## 트리거에 대하여
워크플로우는 항상 자동화가 실행되어야 하는 시기를 정의하는 단일 트리거로 시작합니다.
<img src="/images/user-guide/workflows/workflow_triggers.png" style={{width:'100%'}} />
<Tip>
**고급 객체도 지원됩니다!** 표준 CRM 객체(사람, 회사, 영업 기회) 외에도 다음 대상으로 워크플로우를 트리거하고 작업을 수행할 수 있습니다:
* 워크스페이스 구성원
* 캘린더 이벤트
* 메시지(이메일)
* 작업, 노트 및 기타 많은 시스템 객체
이렇게 하면 캘린더 이벤트가 생성될 때 팀 구성원에게 알림을 보내거나, 수신 이메일을 자동으로 처리하는 등의 강력한 자동화를 구현할 수 있습니다.
</Tip>
## 레코드 생성됨
선택한 객체(사람, 회사, 기회 또는 기타 사용자 정의 객체)에 새 레코드가 생성될 때 워크플로우를 시작합니다.
**구성**: 새 레코드를 모니터링할 객체 유형을 선택합니다.
<Note>
* 이 트리거는 CSV, 메일박스 및 캘린더 동기화 또는 API로 생성된 레코드에 적합합니다.
* **수동으로 생성된 레코드에는 권장되지 않습니다**: 이 트리거를 사용하면 레코드가 생성되는 즉시 워크플로우가 시작됩니다. Twenty UI는 실시간 자동 저장을 제공하므로(레코드를 저장하기 위한 별도의 편집 모드와 검증 단계가 없습니다), 사용자가 모든 필드를 입력하기 전에 워크플로우가 트리거됩니다.
수동으로 생성된 레코드에서 이 워크플로우를 트리거하려면, 대신 트리거 `Record is created or updated`를 사용하는 것이 좋습니다.
</Note>
## 레코드 업데이트됨
기존 레코드에 변경 사항이 있을 때 워크플로우를 시작합니다.
**구성**:
* 객체 유형을 선택합니다
* 변경 사항을 모니터링할 필드를 선택적으로 지정
## 레코드 업데이트 또는 생성됨
선택한 객체에서 레코드가 생성되거나 업데이트될 때 워크플로우를 시작합니다.
**중요한 이유**: 이 트리거는 다양한 방식으로 생성된 레코드가 서로 다르게 동작하기 때문에 특히 유용합니다:
* **API/CSV 가져오기**: 필드가 즉시 채워진 상태로 레코드가 생성됩니다
* **수동 생성**: 레코드는 먼저 생성되고, 그 후 필드가 추가됩니다
**구성**:
* 모니터링할 객체 유형을 선택
* 변경 사항을 모니터링할 필드를 선택적으로 지정
* 워크플로우는 초기 생성과 모든 후속 업데이트에서 트리거됩니다
## 레코드 삭제됨
객체에서 레코드가 제거될 때 워크플로우를 시작합니다.
**구성**: 삭제를 모니터링할 객체 유형을 선택합니다.
## 수동 트리거
사용자 작업에 의해 트리거될 때 워크플로우가 시작됩니다. 이 트리거는 `Cmd+K` 메뉴를 통해 액세스하거나, 레코드를 선택한 후 상단 내비게이션 바에 표시되는 사용자 정의 버튼을 통해 사용할 수 있습니다.
<img src="/images/user-guide/workflows/add_to_newsletter_button.png" style={{width:'100%'}} />
**가용성 구성**:
워크플로우가 레코드 선택을 어떻게 처리할지 선택합니다:
* **전역**: 이 워크플로우를 트리거하려면 레코드가 필요하지 않습니다. 워크플로우는 어디서든(어떤 객체에서도) 명령 메뉴 `Cmd+K`에서 트리거되며 레코드를 입력으로 사용하지 않습니다.
**Single** 및 **Bulk**에서 선택된 레코드가 워크플로우로 전달됩니다. 이는 특정 객체에 대해 구성되며, 워크플로우를 트리거하기 전에 여러 레코드를 선택할 수 있습니다.
* **Single**: 선택된 레코드 수만큼 워크플로우가 처음부터 끝까지 실행됩니다.
<Note>
**소프트 제한: 분당 100회 실행**. 이를 초과하면 워크플로우는 "Not Started" 상태로 유지되며, 백그라운드 작업에 의해 또는 다른 워크플로우가 대기열에 들어올 때 점진적으로 처리됩니다. 즉, 단일 트리거로 100개가 넘는 레코드를 선택할 수 있으며, 실행이 다소 느려질 뿐입니다.
</Note>
* **Bulk**: 전체 레코드 목록을 입력으로 하여 워크플로우가 한 번 실행됩니다. 이는 워크플로우에 [이터레이터 작업](/l/ko/user-guide/workflows/capabilities/workflow-actions#iterator)이 포함되어야 함을 의미합니다.
<Note>
이 방식은 더 고급이며, 워크플로우 실행 횟수를 최적화하려는 사용자에게 가장 적합합니다.
</Note>
<img src="/images/user-guide/workflows/manual_trigger.png" style={{width:'100%'}} />
**추가 구성**:
* 대상 객체 선택(단일 및 대량 사용 가능)
* 워크플로우 트리거를 위한 명령 아이콘 선택
* 내비게이션 바 배치 구성 (고정 또는 비고정)
**접근 방법**:
* `Cmd+K` 메뉴에서 수동 워크플로우 찾기 및 실행
* 상단 내비게이션 바의 사용자 정의 버튼 (구성된 경우)
## 시간 기반 트리거: 일정에 따라
정의한 일정에 따라 반복적으로 워크플로우를 시작합니다.
**구성**:
* 시간 단위 선택 (분, 시간, 일)
* 값을 입력하거나 고급 스케줄링을 위한 사용자 정의 크론 표현식을 사용
<Note>
**시간대**: 예약된 워크플로우는 **UTC**로 실행됩니다. 일일 일정의 시간을 설정할 때는 로컬 시간을 UTC로 변환하세요.
</Note>
## 외부 트리거: 웹훅
외부 서비스로부터 GET 또는 POST 요청을 수신하면 워크플로우를 시작합니다.
<img src="/images/user-guide/workflows/webhook_trigger.png" style={{width:'100%'}} />
**구성**:
* 워크플로우가 고유한 웹훅 URL을 제공합니다. 이를 복사하여 외부 시스템에서 호출할 엔드포인트로 추가하세요.
* POST 요청의 경우, Twenty가 어떤 데이터를 받을지 알 수 있도록 예상되는 본문 구조를 정의하세요. 이후 워크플로우에서 필요하게 될 수신 필드를 여기에서 추가하세요.
* 인증을 구성합니다(곧 지원 예정).
## 적합한 트리거 선택
| 사용 사례 | 권장 트리거 |
| ---------------- | --------------- |
| 새 리드 처리 필요 | 레코드 생성됨 |
| 데이터 변경 사항 동기화 필요 | 레코드 업데이트됨 |
| 가져오기/수동 데이터 처리 | 레코드 업데이트 또는 생성됨 |
| 삭제 후 정리 | 레코드 삭제됨 |
| 사용자에 의해 시작된 작업 | 수동 실행 |
| 반복 보고서 | 일정에 따라 |
| 외부 통합 | 웹훅 또는 일정에 따라 |

View File

@@ -0,0 +1,84 @@
---
title: Workflow 버전들
description: 워크플로 버전과 초안을 관리합니다.
---
## 버전 정보
워크플로를 활성화할 때마다 새 버전이 생성됩니다. 이를 통해 시간 경과에 따른 변경 사항을 추적하고 필요 시 이전 구성으로 되돌릴 수 있습니다.
## 버전 상태
| 상태 | 설명 |
| --------- | -------------------- |
| **초안** | 편집 중이며 아직 게시되지 않음 |
| **활성** | 트리거에 반응하는 라이브 버전 |
| **비활성화됨** | 이전에 활성화되었지만 수동으로 중지됨 |
| **보관됨** | 기록을 위해 보관된 이전 버전 |
## 초안 작업
활성 워크플로를 편집하면 변경 사항이 **초안**으로 저장됩니다. 업데이트 작업을 하는 동안에도 활성 버전은 계속 실행됩니다.
편집을 마치면 다음을 수행할 수 있습니다:
* **활성화**: 초안을 새 활성 버전으로 게시합니다(이전 버전은 보관됩니다)
* **폐기**: 초안을 삭제하고 현재 활성 버전을 유지합니다
## 버전 히스토리
### 이전 버전 보기
1. 워크플로를 엽니다
2. **버전** 탭을 클릭합니다
3. 타임스탬프와 함께 모든 이전 버전을 확인합니다
### 버전 복원
1. 복원하려는 버전을 찾습니다
2. **초안으로 사용**을 클릭합니다
3. 해당 버전이 새 초안으로 복사됩니다
4. 필요한 업데이트를 수행합니다
5. 준비되면 활성화합니다
## 모범 사례
### 버전 관리
* 운영 환경 준비가 되었을 때만 활성화합니다
* 버전 간에는 의미 있는 변경만 반영합니다
* 워크플로 이름이나 설명의 주요 변경 사항을 문서화합니다
* 활성화하기 전에 초안 모드에서 테스트합니다
### 변경 사항 되돌리기
* 새 버전에서 문제가 발생하면 이전 버전을 복원합니다
* 버전 기록을 사용해 무엇이 변경되었는지 추적합니다
* 복원한 버전은 항상 활성화 전에 테스트합니다
## 일반적인 워크플로
### 빠른 편집
1. 활성 워크플로에 소규모 변경을 적용합니다
2. 초안 모드에서 테스트합니다
3. 새 버전을 활성화합니다
### 대규모 수정
1. 이전 버전을 시작점으로 사용합니다
2. 초안에서 중요한 변경을 수행합니다
3. 모든 시나리오를 철저히 테스트합니다
4. 확신이 서면 활성화합니다
### 롤백
1. 현재 버전의 문제를 식별합니다
2. 기록에서 마지막으로 정상 동작한 버전을 찾습니다
3. **초안으로 사용**을 클릭합니다
4. 활성화하여 이전 동작을 복원합니다
## 관련 항목
* [워크플로 시작하기](/l/ko/user-guide/workflows/overview)
* [워크플로 실행](/l/ko/user-guide/workflows/capabilities/workflow-runs)

View File

@@ -0,0 +1,82 @@
---
title: Code 액션에서 배열 처리
description: 워크플로의 Code 액션에서 배열 입력을 올바르게 처리하는 방법을 알아보세요.
---
Code 액션에서 배열을 다룰 때 흔히 마주하는 두 가지 문제가 있습니다:
1. **문자열로 전달된 배열** — 외부 시스템이나 이전 단계에서 실제 배열이 아닌 문자열로 전달됨
2. **개별 항목을 선택할 수 없음** — 배열 전체만 선택할 수 있고, 그 안의 특정 필드는 선택할 수 없음
둘 다 Code 노드로 해결할 수 있습니다.
## 문자열에서 배열 파싱하기
배열은 워크플로 단계 간에 네이티브 배열이 아니라 문자열이나 JSON으로 전달되는 경우가 자주 있습니다. 다음과 같은 경우에 발생합니다:
* HTTP Request로 외부 API에서 데이터를 받을 때
* 웹훅 페이로드를 처리할 때
* 워크플로 단계 간에 데이터를 전달할 때
**해결책**: 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(),
})),
};
};
```
핵심 구문 `typeof users === "string" ? `JSON.parse(users) : users\` 는 입력이 문자열인지 확인하고, 필요하면 파싱하며, 이미 배열인 경우 그대로 사용합니다.
## 배열에서 개별 필드 추출하기
웹훅이 `answers: [...]`와 같은 배열을 반환할 수 있지만, 이후 워크플로 단계에서는 **배열 전체**만 선택할 수 있고 그 안의 개별 항목은 선택할 수 없습니다.
**해결책**: 특정 필드를 추출하여 구조화된 객체로 반환하도록 Code 노드를 추가하세요:
```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
}
};
};
```
Code 노드는 배열 대신 구조화된 객체를 반환합니다. 이후 단계에서는 변수 선택기에서 `answer.firstname` 및 `answer.name`과 같은 개별 필드를 선택할 수 있습니다.
<Tip>
**배열에서 필드를 추출하는 대신 배열을 순회(loop)하고 싶으신가요?** Code 또는 Logic Function 단계가 최상위 배열을 반환하는 경우, 해당 결과를 바로 [Iterator](/l/ko/user-guide/workflows/capabilities/use-iterator)에 전달할 수 있습니다. 해당 단계의 **Whole list** 옵션을 Iterator의 입력으로 선택한 다음, 루프 안에서 각 요소를 `{{iterator.currentItem}}`으로 참조하면 됩니다. 그 경우에는 배열을 객체로 변환할 필요가 없습니다.
</Tip>
<Tip>
코드 편집기 오른쪽 상단의 사각형 아이콘을 클릭하여 전체 화면으로 표시하세요 — 기본 편집기 너비가 제한되어 있어 유용합니다.
</Tip>

View File

@@ -0,0 +1,182 @@
---
title: Twenty로 제품 데이터 가져오기
description: 일정에 따라 데이터 웨어하우스의 제품 카탈로그 데이터를 CRM으로 동기화하세요.
---
이 패턴을 사용하여 데이터 웨어하우스(예: Snowflake, BigQuery, PostgreSQL)의 제품 데이터와 Twenty를 동기화 상태로 유지하세요.
## 워크플로우 구조
1. **트리거**: 일정에 따라
2. **코드**: 데이터 웨어하우스에 질의
3. **코드**(선택): 데이터를 배열로 형식 지정
4. **Iterator**: 각 제품을 순회
5. **레코드 업서트**: Twenty에서 생성 또는 업데이트
<img src="/images/user-guide/workflows/create_users_workflow.png" style={{width:'100%'}} />
## 단계 1: 트리거 일정 설정
데이터 최신성 요구에 맞는 빈도로 워크플로우가 실행되도록 설정하세요:
* 거의 실시간 동기화를 위해 5분마다
* 중요도가 낮은 데이터의 경우 매시간
* 배치 업데이트의 경우 매일
## 단계 2: 데이터 웨어하우스 질의
최근 데이터를 가져오기 위해 **코드** 액션을 추가합니다:
```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>
`updated_at >= last X minutes`로 필터링하여 최근에 변경된 레코드만 가져옵니다. 동기화를 효율적으로 유지할 수 있습니다.
</Tip>
## 단계 3: 데이터 형식 지정(선택)
웨어하우스가 변환이 필요한 형식으로 데이터를 반환하는 경우, **코드** 액션을 하나 더 추가하세요. 일반적인 변환에는 타입 변환, 필드 이름 변경, 데이터 정리가 포함됩니다.
### 예시: 불리언 및 상태 필드를 포함한 사용자 데이터
```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),
})),
};
};
```
### 예시: 타입 변환이 포함된 제품 데이터
```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
}))
};
};
```
### 예시: 날짜 및 통화 형식 지정
```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, " ")
}))
};
};
```
### 일반적인 변환
| 소스 형식 | 대상 형식 | 코드 |
| -------------------- | ---------------- | ---------------------------------------- |
| `"true"` / `"false"` | `true` / `false` | `v === true \|\| v === "true"` |
| `"123.45"` | `123.45` | `parseFloat(value)` |
| `"active"` | `"ACTIVE"` | `value.toUpperCase()` |
| `1704067200` (Unix) | ISO 날짜 | `new Date(v * 1000).toISOString()` |
| `"$1,234.56"` | `1234.56` | `parseFloat(v.replace(/[^0-9.-]/g, ""))` |
| `null` / `undefined` | `""` | `value \|\| ""` |
## 단계 4: 제품 순회
**Iterator** 액션을 추가합니다:
* 입력: `{{code.products}}`
배열의 각 제품을 순회합니다.
## 5단계: 각 레코드를 업서트하기
이터레이터 내부에 **레코드 업서트** 액션을 추가합니다:
| 설정 | 값 |
| --------- | -------------------------- |
| **객체** | 사용자 지정 제품 객체 |
| **일치 기준** | External ID 또는 SKU(고유 식별자) |
| **Name** | `{{iterator.item.name}}` |
| **SKU** | `{{iterator.item.sku}}` |
| **Price** | `{{iterator.item.price}}` |
<Tip>
생성과 업데이트를 위한 분기 흐름을 따로 만들기보다 **Upsert**(업데이트 또는 생성)를 사용하세요. 구현이 더 빠르고 디버깅이 더 쉽습니다.
</Tip>
## 사용 예시
| 소스 | 데이터 |
| ------------- | --------------- |
| **ERP 시스템** | 제품 카탈로그, 가격, 재고 |
| **전자상거래 플랫폼** | 주문, 고객, 제품 업데이트 |
| **데이터 웨어하우스** | 집계 지표, 보강된 데이터 |
| **재고 시스템** | 재고 수준, 재주문 알림 |
## 관련 항목
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,130 @@
---
title: Typeform 응답을 Twenty로 가져오기
description: Typeform의 Webhook 페이로드를 처리하여 폼 제출에서 리드를 생성합니다.
---
표준 Webhook 설정은 [Webhook 트리거 설정](/l/ko/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)을 참조하세요. 이 문서는 Typeform의 맞춤형 페이로드 구조에 필요한 특정 처리 방법을 다룹니다.
### 1단계: Webhook 워크플로우 생성
1. **설정 → 워크플로우**로 이동
2. **+ 새 워크플로우**를 클릭
3. 트리거로 **Webhook**을 선택합니다
4. Webhook URL을 복사합니다
### 2단계: Typeform 구성
1. Typeform에서 폼을 엽니다
2. **Connect → Webhooks**로 이동
3. Twenty Webhook URL을 붙여넣습니다
4. 저장
### 3단계: Typeform 페이로드 이해하기
Typeform은 중첩된 JSON 구조를 전송합니다. 간단한 예시는 다음과 같습니다:
```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" }
}
]
}
}
```
주요 유의 사항:
* 폼 데이터는 `form_response` 아래에 중첩됩니다
* **답변은 명명된 필드가 아니라 배열로 반환됩니다**
* 각 답변에는 참고용으로 필드 유형과 제목이 포함됩니다
### 4단계: 답변 배열에서 필드 추출
`answers`가 배열이므로 이후 단계에서는 전체 배열만 선택할 수 있으며, 개별 필드는 선택할 수 없습니다. 필요한 필드를 추출하기 위해 **Code** 액션을 추가합니다:
```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
}
};
};
```
이후 단계에서는 변수 선택기에서 `contact.firstName` 및 `contact.email`과 같은 개별 필드를 선택할 수 있습니다.
<Tip>
배열 처리에 대한 자세한 내용은 [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)를 참조하세요.
</Tip>
### 5단계: 레코드 생성
**레코드 생성** 액션을 추가합니다:
| 필드 | 값 |
| ------- | ---------------------------------------- |
| **객체** | 사람들 |
| **이름** | `{{code.contact.firstName}}` |
| **성** | `{{code.contact.lastName}}` |
| **이메일** | `{{code.contact.email}}` |
| **회사** | `{{code.contact.company}}` 기준으로 검색 또는 생성 |
### 6단계: 테스트 및 활성화
1. Typeform에서 테스트 응답을 제출합니다
2. 워크플로우 실행을 확인해 데이터가 수집되었는지 검증합니다
3. 워크플로우를 활성화합니다
## 관련 항목
* [Webhook 트리거 설정](/l/ko/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)
* [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,263 @@
---
title: Twenty에서 PDF 생성
description: "레코드에 PDF(예: 견적서)를 생성해 첨부하는 워크플로우를 만드세요."
---
Twenty에서 PDF를 자동으로 생성하거나 가져와 레코드에 첨부합니다. 이는 Company, Opportunity 또는 기타 객체에 연결된 견적서, 송장, 보고서를 생성할 때 흔히 사용됩니다.
## 개요
이 워크플로우는 **수동 트리거**를 사용하므로 사용자가 선택한 레코드에 대해 필요할 때 PDF를 생성할 수 있습니다. **로직 함수**가 다음을 처리합니다:
1. URL에서 PDF를 다운로드(PDF 생성 서비스에서 제공)
2. 파일을 Twenty에 업로드
3. 레코드에 연결된 첨부 파일 생성
## 사전 준비
워크플로우를 설정하기 전에:
1. **API 키 생성**: **Settings → APIs**로 이동하여 새 API 키를 만드세요. 이 토큰은 로직 함수에 필요합니다.
2. **PDF 생성 서비스 설정**(선택 사항): PDF를 동적으로 생성하려면(예: 견적서) Carbone, PDFMonkey, DocuSeal 같은 서비스를 사용해 PDF를 만들고 다운로드 URL을 받으세요.
## 단계별 설정
### 1단계: 트리거 구성
1. **워크플로우**로 이동해 새 워크플로우를 생성합니다
2. **수동 트리거**를 선택합니다
3. PDF를 첨부할 객체를 선택하세요(예: **Company** 또는 **Opportunity**).
<Tip>
수동 트리거를 사용하면 사용자가 레코드를 선택한 후 우측 상단에 표시되는 버튼으로 이 워크플로우를 실행해 PDF를 생성하고 첨부할 수 있습니다.
</Tip>
### 2단계: 로직 함수 추가
1. **Code** 액션(로직 함수)을 추가합니다
2. 아래 코드로 새 함수를 만드세요
3. 입력 매개변수를 구성하세요
#### 입력 매개변수
| 매개변수 | 값 |
| ----------- | ----------------------- |
| `companyId` | `{{trigger.object.id}}` |
<Note>
다른 객체(Person, Opportunity 등)에 첨부하는 경우 해당 매개변수 이름을 바꾸고(예: `personId`, `opportunityId`) 로직 함수를 업데이트하세요.
</Note>
#### 로직 함수 코드
```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;
};
```
### 3단계: 사용 사례에 맞게 사용자 지정
#### 다른 객체에 첨부하려면
`targetCompanyId`를 해당 필드로 바꾸세요:
| 개체 | 필드 이름 |
| --------- | -------------------------- |
| 회사 | `targetCompanyId` |
| 사람 | `targetPersonId` |
| 기회 | `targetOpportunityId` |
| 사용자 지정 개체 | `targetYourCustomObjectId` |
함수 매개변수와 첨부 생성 mutation의 `variables.data` 객체를 모두 업데이트하세요.
#### 동적 PDF URL을 사용하려면
PDF 생성 서비스를 사용하는 경우 다음을 수행할 수 있습니다:
1. 먼저 PDF를 생성하기 위해 HTTP 요청 작업을 만듭니다
2. 반환된 PDF URL을 로직 함수의 매개변수로 전달합니다
```typescript
export const main = async (
params: { companyId: string; pdfUrl: string; filename: string },
) => {
const { companyId, pdfUrl, filename } = params;
// ... rest of the function
};
```
### 4단계: 테스트 및 활성화
1. 워크플로우를 저장합니다
2. Company 레코드로 이동합니다
3. **⋮** 메뉴를 클릭하고 워크플로우를 선택합니다
4. 레코드의 **Attachments** 섹션에서 PDF가 첨부되었는지 확인합니다
5. 워크플로우를 활성화합니다
## PDF 생성 서비스와 결합하기
동적 견적서나 송장을 생성하려면:
### 예시: 견적서 생성 → PDF 첨부
| 단계 | 작업 | 목적 |
| -- | --------------- | ------------------------------- |
| 1 | 수동 트리거(Company) | 사용자가 레코드에서 시작 |
| 2 | 레코드 검색 | Opportunity 또는 라인 아이템 세부정보 가져오기 |
| 3 | HTTP 요청 | 레코드 데이터를 사용해 PDF 생성 API 호출 |
| 4 | 로직 함수 | 생성된 PDF를 다운로드해 첨부 |
### 널리 사용되는 PDF 생성 서비스
* **Carbone** - 템플릿 기반 문서 생성
* **PDFMonkey** - 템플릿 기반 동적 PDF 생성
* **DocuSeal** - 문서 자동화 플랫폼
* **Documint** - API 우선 문서 생성
각 서비스는 PDF URL을 반환하는 API를 제공하며, 이 URL을 로직 함수에 전달할 수 있습니다.
## 문제 해결
| 문제 | 해결책 |
| ------------- | --------------------------------------- |
| "PDF 다운로드 실패" | PDF URL에 접근할 수 있으며 유효한 PDF를 반환하는지 확인하세요 |
| "업로드 실패" | API 키가 유효하고 쓰기 권한이 있는지 확인하세요 |
| "첨부 파일 생성 실패" | 객체 ID 필드 이름이 대상 객체와 일치하는지 확인하세요 |
## 관련 항목
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [로직 함수](/l/ko/user-guide/workflows/capabilities/workflow-actions#code)
* [Twenty에서 견적서 또는 송장 생성](/l/ko/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)

View File

@@ -0,0 +1,144 @@
---
title: Twenty에서 견적서 또는 인보이스 생성
description: 거래가 성사되면 외부 도구에서 인보이스를 자동으로 생성합니다.
---
거래 데이터를 인보이스 시스템(Stripe, QuickBooks, Xero 등)으로 자동으로 전송합니다. 기회가 성사되었을 때.
## 워크플로우 구조
1. **트리거**: 레코드가 업데이트됨(기회)
2. **필터**: Stage = Closed Won
3. **레코드 검색**: 회사 세부정보 가져오기
4. **코드**(선택): 페이로드 형식 지정
5. **HTTP 요청**: 인보이스 시스템으로 전송
## 단계 1: 트리거 설정
1. 새 워크플로우 만들기
2. **레코드가 업데이트됨** 트리거 선택
3. 객체로 **기회(Opportunity)** 선택
## 단계 2: Closed Won 필터링
거래가 성사된 경우에만 계속되도록 **필터** 작업을 추가합니다:
| 설정 | 값 |
| ------ | ------------------------------- |
| **필드** | 단계 |
| **조건** | 같음 |
| **값** | `CLOSED_WON` (또는 사용 중인 스테이지 이름) |
<Tip>
트리거는 모든 기회 업데이트에서 실행됩니다. 필터는 스테이지가 Closed Won으로 변경될 때만 워크플로우가 계속 진행되도록 보장합니다.
</Tip>
## 단계 3: 회사 세부정보 가져오기
기회 레코드에는 인보이스에 필요한 모든 회사 필드가 포함되지 않을 수 있습니다. **레코드 검색** 작업을 추가합니다:
| 설정 | 값 |
| --------- | -------------------------------------- |
| **객체** | 회사 |
| **일치 기준** | ID가 `{{trigger.object.companyId}}`와 같음 |
청구지 주소, 세금 ID 등과 함께 전체 회사 레코드를 가져옵니다.
## 단계 4: 페이로드 형식 지정(선택)
인보이스 시스템에서 특정 형식이 필요한 경우 **코드** 작업을 추가합니다:
```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
}
}
};
};
```
## 단계 5: 인보이스 시스템으로 전송
**HTTP 요청** 작업을 추가합니다:
| 설정 | 값 |
| ------- | ------------------------------------ |
| **방법** | POST |
| **URL** | 인보이스 API 엔드포인트 |
| **헤더** | `Authorization: Bearer YOUR_API_KEY` |
| **본문** | `{{code.invoice}}` 또는 필드를 직접 매핑 |
### 예시: 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
```
### 예시: QuickBooks 인보이스
```
POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Body: {{code.invoice}}
```
## 전체 워크플로우 요약
| 단계 | 작업 | 목적 |
| -- | -------------- | --------------------------- |
| 1 | 트리거: 레코드 업데이트됨 | 어떤 기회가 변경되면 실행 |
| 2 | 필터 | Stage = Closed Won인 경우에만 진행 |
| 3 | 레코드 검색 | 청구를 위한 전체 회사 세부정보 가져오기 |
| 4 | 코드 | 인보이스 API용 데이터 형식 지정 |
| 5 | HTTP 요청 | 외부 시스템에서 인보이스 생성 |
## 팁
* **외부 ID 저장**: **레코드 업데이트** 작업을 사용하여 API가 반환한 인보이스 ID를 기회에 다시 저장합니다
* **오류 처리**: HTTP 요청이 실패하면 알림을 보내는 분기 추가
* **먼저 테스트**: 실제 운영 전에 인보이스 시스템의 샌드박스/테스트 모드를 사용하세요
## 관련 항목
* [Twenty에서 PDF 생성](/l/ko/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty) — 생성된 PDF를 레코드에 첨부
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [Closed Won 자동화](/l/ko/user-guide/workflows/how-tos/crm-automations/closed-won-automations)

View File

@@ -0,0 +1,170 @@
---
title: 웹훅 트리거 설정
description: 외부 서비스에서 데이터를 받아 워크플로우를 트리거합니다.
---
웹훅 트리거는 고유한 URL로 데이터를 보내 외부 서비스가 워크플로우를 시작할 수 있게 합니다. 양식, 서드파티 앱 및 커스텀 통합과 연결하는 데 사용하세요.
## 웹훅을 언제 사용할지
| 사용 사례 | 예시 |
| ---------- | --------------------- |
| **웹 폼** | 문의 폼 제출 시 리드가 생성됩니다 |
| **서드파티 앱** | Stripe 결제 → 고객 레코드 생성 |
| **커스텀 통합** | 사용자 앱 → Twenty 자동화 |
| **노코드 도구** | Zapier, Make, n8n 연결 |
## 단계별 설정
### 1단계: 워크플로우 생성
1. **설정 → 워크플로우**로 이동
2. **+ 새 워크플로우**를 클릭
3. 이름을 지정합니다(예: "웹사이트 폼 제출")
### 2단계: 웹훅 트리거 구성
1. 트리거 블록을 클릭하세요
2. **웹훅**을 선택
3. 다음과 같은 고유한 웹훅 URL이 제공됩니다:
```
https://api.twenty.com/webhooks/workflow/abc123...
```
4. 이 URL을 복사하세요—외부 서비스에 필요합니다
### 3단계: 예상 데이터 구조 정의
**POST** 요청의 경우, 예상 본문 구조를 정의하세요:
1. **예상 본문 정의**를 클릭
2. 서비스가 보내는 형식과 일치하는 샘플 JSON을 입력하세요:
```json
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in your product"
}
```
3. **저장**을 클릭하세요—이렇게 하면 이후 단계에서 사용할 수 있는 변수가 생성됩니다
### 4단계: 액션 추가
이제 웹훅 데이터를 사용하는 액션을 추가하세요:
**예시: 사람 레코드 생성**
1. **레코드 생성** 액션을 추가
2. **People** 객체를 선택
3. 필드 매핑:
| 필드 | 값 |
| --- | ---------------------------------------- |
| 이름 | `{{trigger.body.firstName}}` |
| 성 | `{{trigger.body.lastName}}` |
| 이메일 | `{{trigger.body.email}}` |
| 회사 | `{{trigger.body.company}}` 기준으로 검색 또는 생성 |
### 5단계: 웹훅 테스트
활성화하기 전에 웹훅을 테스트하세요:
**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"}'
```
**Postman 등 사용**:
1. 웹훅 URL로 POST 요청을 생성
2. Content-Type 헤더를 `application/json`으로 설정
3. 테스트 JSON 본문을 추가
4. 전송하고 워크플로우 실행을 확인하세요.
### 6단계: 활성화
테스트가 완료되면 **활성화**를 클릭하여 워크플로우를 라이브로 전환하세요.
## 다양한 데이터 구조 처리
### 중첩 데이터
웹훅이 중첩 데이터를 보내는 경우:
```json
{
"contact": {
"name": "John Doe",
"email": "john@example.com"
},
"source": "website"
}
```
다음과 같이 참조: `{{trigger.body.contact.email}}`
### 배열
데이터에 배열이 포함된 경우:
```json
{
"items": [
{"name": "Product A", "qty": 2},
{"name": "Product B", "qty": 1}
]
}
```
배열 처리 방식은 사용 사례에 따라 다릅니다:
**항목 개수를 알 수 없음 → Iterator 사용**
배열의 각 항목을 처리해야 하는 경우(예: 각 항목마다 레코드 생성), 배열을 파싱하기 위해 **Code** 액션을 추가한 다음 **Iterator**를 사용하세요:
```javascript
export const main = async (params: { items: any }) => {
const items = typeof params.items === "string"
? JSON.parse(params.items)
: params.items;
return { items };
};
```
그런 다음 Iterator로 순회합니다: `{{code.items}}`
**알려진/특정 필드 → 명명된 필드로 추출**
배열에 개별적으로 접근하려는 특정 필드가 포함되어 있다면(예: 위치 0은 항상 "first name", 위치 1은 항상 "last name"인 폼 답변), 이를 추출하기 위해 **Code** 액션을 추가하세요:
```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
}
};
};
```
이제 이후 단계에서 `product.name` 및 `product.qty`를 개별적으로 선택할 수 있습니다.
<Tip>
배열 처리에 대한 자세한 내용은 [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)를 참조하세요.
</Tip>
## 관련 항목
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,121 @@
---
title: 수신 이메일 자동 회신
description: AI를 사용해 수신 이메일을 분류하고 스레드형 답장을 자동으로 보내는 워크플로를 구축하세요.
---
수신 이메일에 몇 시간이 아니라 몇 초 만에 응답하세요. 이 워크플로는 AI 에이전트를 사용해 잡음(뉴스레터, 스팸, 자동 회신)을 걸러내고 실제 메시지에는 개인화된 응답 초안을 작성합니다. 그런 다음 AI가 원본 대화 내에서 스레드형 답장으로 이를 전송합니다.
## 이메일 스레드 작동 방식
모든 이메일에는 숨겨진 `Message-ID` 헤더가 있으며, 이는 발신자의 메일 서버가 부여하는 고유한 식별자입니다. 이메일에 회신할 때, 메일 클라이언트는 해당 지문을 참조하는 `In-Reply-To` 헤더를 설정합니다. Gmail, Outlook 및 기타 모든 클라이언트가 메시지를 스레드로 묶는 방식입니다.
Twenty에서는 그 지문이 Message 객체의 `headerMessageId`로 저장됩니다. 워크플로우가 이를 가져와 Send Email 작업의 In-Reply-To 필드에 전달합니다.
## 워크플로우 구성
### 1단계: 새 워크플로우 생성
**설정 -> 워크플로우**로 이동한 다음 **+ 새 워크플로우**를 클릭하세요.
### 2단계: 수신 메시지에 트리거 설정
**When a Record is Created**를 선택하고 **Messages**를 선택하세요.
이메일이 Twenty에 도착할 때마다 이 트리거가 실행됩니다.
<img src="/images/user-guide/workflows/auto-reply/workflow-overview.png" style={{width:'100%'}} />
### 3단계: 발신자 찾기
**Search Records** 작업을 추가합니다.
발신자 주소는 메시지 자체에 있지 않고 — 연결된 Message Participant 레코드에 있습니다.
| 필드 | 값 |
| ------ | ------------------------------- |
| **객체** | 메시지 참가자 |
| **필터** | Message **값이** `{{trigger.id}}` |
| **필터** | Role **값이** From |
| **제한** | 1 |
이렇게 하면 발신자의 이메일은 `handle`에, 이름은 `displayName`에 담깁니다.
<img src="/images/user-guide/workflows/auto-reply/find-sender.png" style={{width:'100%'}} />
### 4단계: AI로 분류하고 응답 초안 작성
**AI Agent** 작업을 추가합니다. 이 단일 단계는 두 가지를 수행합니다: 이메일에 회신할지 여부를 결정하고, 필요하다면 회신 내용을 작성합니다.
다음과 같은 프롬프트를 사용하세요:
```
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는 다음 단계에서 참조할 수 있도록 `response` 필드에 응답을 출력합니다.
<img src="/images/user-guide/workflows/auto-reply/ai-triage.png" style={{width:'100%'}} />
### 5단계: AI 결정에 따라 분기
**If/Else** 작업을 추가하여 AI가 회신할지 건너뛸지를 확인합니다.
| 필드 | 값 |
| ----------- | ---------------------------------------- |
| **조건** | AI Agent `response`에 `SKIP`이 **포함되지 않음** |
| **참인 경우** | Send Email로 계속 |
| **그렇지 않으면** | 아무것도 하지 않음(워크플로우 종료) |
스팸, 뉴스레터, 자동 생성된 메시지는 제외됩니다. 그 외 모든 항목은 다음 단계로 이동합니다.
<img src="/images/user-guide/workflows/auto-reply/should-reply.png" style={{width:'100%'}} />
### 6단계: 스레드형 회신 보내기
"if true" 분기에서 **Send Email** 작업을 추가합니다. **Advanced options**를 클릭한 다음 **Add In-Reply-To**를 클릭합니다.
| 필드 | 값 |
| --------------- | -------------------------------------- |
| **받는 사람** | `{{Find Sender.first.handle}}` |
| **제목** | `Re: {{trigger.subject}}` |
| **Body** | `{{AI Triage & Draft Reply.response}}` |
| **In-Reply-To** | `{{trigger.headerMessageId}}` |
In-Reply-To 필드는 이 메시지를 새 대화가 아닌 회신으로 인식하게 합니다. 수신자는 Gmail, Outlook 등 모든 클라이언트에서 원본 이메일 아래에 스레드로 묶인 상태로 보게 됩니다.
<img src="/images/user-guide/workflows/auto-reply/send-email.png" style={{width:'100%'}} />
<Tip>
**In-Reply-To**에는 트리거에서 전달된 `message.headerMessageId`가 필요합니다 — 이는 수신자 주소가 아니라 이메일의 고유한 지문입니다. 비워 두면 이메일은 여전히 전송되지만 독립된 메시지로 보내집니다.
</Tip>
<Warning>
Gmail은 제목 줄을 사용하여 메시지를 스레드로 그룹화합니다. Gmail이 원래 스레드 내에 회신을 표시하려면 제목이 `Re:`(콜론과 공백 포함)로 시작해야 합니다. 이 표시가 없으면, In-Reply-To 헤더가 올바르게 설정되어 있어도 회신은 별도의 대화로 표시됩니다.
</Warning>
### 7단계: 테스트 및 활성화
**Test**를 클릭한 다음 메일 클라이언트를 확인하세요. 회신은 원본 메시지 아래에 스레드로 표시되어야 합니다.
만족스러우면 활성화하세요.
## 확장 아이디어
* **VIP에게만 회신** — 발신자의 도메인을 확인하거나 Twenty의 연락처로 존재하는지 확인하는 분기를 추가하세요
* **의도별 라우팅** — 영업 문의와 지원 요청을 다르게 처리하도록 별도의 AI Agent 프롬프트를 사용하세요
* **회신 전 정보 보강** — 보다 개인화된 회신을 위해 발신자의 회사나 거래 이력을 AI 프롬프트에 포함하도록 Search Records 단계를 추가하세요

View File

@@ -0,0 +1,179 @@
---
title: Closed Won 자동화
description: 기회가 성사되면 사후 작업을 자동화합니다.
---
거래가 성사되면 여러 작업이 필요합니다: 회사 상태 업데이트, 팀 구성원 알림, 온보딩 작업 생성. 워크플로우 하나로 이 모든 것을 자동화하세요.
## 문제
기회가 "Closed Won" 단계로 이동하면:
* 회사 유형을 "Prospect"에서 "Customer"로 변경해야 합니다
* 온보딩 작업을 생성해야 합니다
* Customer Success 팀에 알림을 보내야 합니다
* 영업 담당자에게 확인을 보내야 합니다
이 작업을 수동으로 하면 시간도 많이 들고 오류가 발생하기 쉽습니다.
## 해결책
성사 후 모든 활동을 자동으로 처리하는 워크플로우를 만드세요.
## 전체 워크플로우 설정
### 1단계: 워크플로우 생성
1. **설정 → 워크플로우**로 이동
2. **+ 새 워크플로우**를 클릭
3. 이름을 "Deal Won - Post-Win Automation"으로 지정
### 2단계: 트리거 구성
1. **레코드가 업데이트됨**을 선택
2. **Opportunities**를 선택
3. "Fields to monitor"에서 **Stage**를 선택
### 3단계: Stage 필터 추가
1. **필터** 액션을 추가합니다
2. 조건: `{{trigger.object.stage}}`이 "Closed Won"과 같음
### 4단계: 회사 유형 업데이트
1. **Update Record** 액션을 추가
2. 구성:
| 필드 | 값 |
| ----------- | ------------------------------- |
| **객체** | 회사 |
| **레코드** | `{{trigger.object.company.id}}` |
| **유형** | 고객 |
| **첫 거래 날짜** | `{{trigger.object.closedAt}}` |
| **계정 소유자** | `{{trigger.object.owner.id}}` |
### 5단계: 온보딩 작업 생성
1. **레코드 생성** 액션을 추가
2. 구성:
| 필드 | 값 |
| --------- | ------------------------------------------------------------------------------ |
| **객체** | 작업 |
| **제목** | `Onboarding: {{trigger.object.name}}` |
| **담당자** | Customer Success 팀 구성원 |
| **마감일** | 지금부터 3일 후 |
| **우선 순위** | 높음 |
| **관련 회사** | `{{trigger.object.company.id}}` |
| **관련 기회** | `{{trigger.object.id}}` |
| **설명** | `신규 고객 온보딩: {{trigger.object.company.name}}. 거래 금액: {{trigger.object.amount}}` |
### 6단계: Customer Success에 알림 보내기
1. 이메일 보내기 액션 추가
2. 구성:
| 필드 | 값 |
| --------- | -------------------------------------------------- |
| **받는 사람** | customer-success@yourcompany.com |
| **제목** | `🎉 New Customer: {{trigger.object.company.name}}` |
| **본문** | 아래 예시를 참조하세요 |
**이메일 본문 예시**:
```
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!
```
### 7단계: 영업 담당자에게 확인 보내기
1. 또 하나의 **이메일 보내기** 액션 추가
2. 구성:
| 필드 | 값 |
| --------- | ---------------------------------------------------------------- |
| **받는 사람** | `{{trigger.object.owner.email}}` |
| **제목** | `✅ Deal Closed: {{trigger.object.name}}` |
| **본문** | 축하합니다! 거래가 처리되었습니다. Customer Success 팀에 알림이 전송되었으며 온보딩이 시작되었습니다. |
### 8단계: 테스트 및 활성화
1. 테스트용 기회를 "Closed Won"으로 이동하여 테스트하세요
2. 확인:
* 회사 유형이 "Customer"로 변경됨
* 온보딩 작업 생성됨
* CS 팀이 이메일을 받음
* 영업 담당자가 확인을 받음
3. 준비되면 활성화합니다
## Closed Lost 처리
실패한 거래에 대해서도 유사한 워크플로우를 만드세요:
### 트리거
* 레코드가 업데이트됨 (Opportunities, Stage = "Closed Lost")
### 작업
1. **레코드 생성**: "Lost Deal Analysis"용 작업
2. **레코드 업데이트**: 회사 레코드에 실패 사유 추가
3. **이메일 보내기**: 실패한 거래를 관리자에게 알림
## 고급: 다단계 온보딩
복잡한 온보딩의 경우 여러 작업을 생성합니다:
```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 };
};
```
배열에서 각 작업을 생성하려면 **Iterator**를 사용하세요.
## 맞춤화 아이디어
### 다른 도구도 최신 상태로 유지하세요
* **HTTP Request**로 결제 시스템에 고객 생성
### 조건부 작업
**필터** 액션을 사용하여 다음을 수행:
* 엔터프라이즈와 SMB에 다른 온보딩 적용
* 지역에 따라 다른 담당자 지정
* 소액 거래는 알림 생략
### 거래 세부 정보 포함
형식을 지정하려면 **Code** 액션을 사용하세요:
* 거래 요약 문서
* CS 팀을 위한 인수인계 노트
* 맞춤형 온보딩 체크리스트
## 관련 항목
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [워크플로우에서 이메일 보내기](/l/ko/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,136 @@
---
title: 오래된 기회 감지
description: 기회가 업데이트되지 않았을 때 관리자에게 자동으로 알립니다.
---
기회가 오래되면 관리자에게 알림을 보내 파이프라인을 건강하게 유지하세요. 이 워크플로우는 지정한 일수 동안 업데이트되지 않은 기회를 확인합니다.
## 문제
업데이트 없이 방치된 기회는 다음을 초래합니다:
* 거래가 식어감
* 신뢰할 수 없는 매출 예측
* 매출 손실
## 해결책
오래된 기회를 찾아 해당 관리자에게 이메일을 보내는 예약 워크플로우를 만듭니다.
## 단계별 설정
### 1단계: 워크플로우 생성
1. **설정 → 워크플로우**로 이동
2. **+ 새 워크플로우**를 클릭
3. 이름을 "오래된 기회 경고"로 지정
### 2단계: 트리거 구성
1. **On a Schedule**를 선택
2. 매일 실행되도록 설정합니다(예: 매일 오전 8시).
### 3단계: 오래된 기회 검색
1. **레코드 검색** 액션을 추가합니다
2. 구성:
| 필드 | 값 |
| ------ | ------------------------------------------ |
| **객체** | 기회 |
| **필터** | Updated At이 (오늘 - 7일) 이전 |
| **필터** | Stage가 "Closed Won"이 아니며 "Closed Lost"도 아님 |
| **제한** | 100 |
### 4단계: 발견된 항목이 있는지 확인
1. **필터** 액션을 추가합니다
2. 조건: `{{searchRecords.length}}`가 0보다 큼
3. 오래된 기회가 없으면 워크플로우는 여기서 중지됩니다
### 5단계: 경고 형식 지정(코드 액션)
이메일을 형식 지정하기 위해 **코드** 액션을 추가합니다:
```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
};
};
```
### 6단계: 경고 이메일 보내기
이메일 보내기 액션 추가:
| 필드 | 값 |
| --------- | -------------------------------------------- |
| **받는 사람** | sales-manager@yourcompany.com |
| **제목** | `🚨 {{code.totalCount}}건의 오래된 기회에 주의가 필요합니다` |
| **본문** | `{{code.summary}}` |
### 7단계: 테스트 및 활성화
1. 워크플로우를 실행하려면 **Test**를 클릭합니다
2. 이메일에 올바른 데이터가 포함되어 있는지 확인합니다
3. 준비되면 활성화합니다
## 맞춤 설정 옵션
### 정체 임계값 변경
레코드 검색 필터를 수정하여 7일을 원하는 기간으로 변경합니다:
* 고속 영업의 경우 3일
* 엔터프라이즈 거래의 경우 14일
* 긴 영업 주기의 경우 30일
### 개별 영업 담당자 알림
관리자에게 보내는 단일 이메일 대신 **Iterator**를 사용하여 각 영업 담당자에게 자신의 오래된 거래에 대한 개인화된 이메일을 보냅니다.
### 에스컬레이션 추가
심각도가 점차 높아지도록 여러 개의 워크플로우를 만듭니다:
1. 7일차: 영업 담당자에게 이메일
2. 14일차: 영업 담당자 및 관리자에게 이메일
3. 21일차: 관리자가 개입할 수 있도록 작업 생성
### Slack에 포함
이메일 대신 또는 추가로 **HTTP Request**를 사용해 Slack 웹훅으로 게시합니다.
## 관련 항목
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [워크플로우에서 이메일 보내기](/l/ko/user-guide/workflows/capabilities/send-emails-from-workflows)

View File

@@ -0,0 +1,69 @@
---
title: 수신한 이메일 수 표시
description: 각 연락처로부터 받은 이메일 수를 자동으로 집계해 표시하는 워크플로우를 만듭니다.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/workflow-number-of-emails-received.png" style={{width:'100%'}} />
## 개요
이 워크플로우는 새 이메일을 받을 때마다 트리거되며, 해당 발신자로부터 받은 이메일의 총 개수를 사람 레코드의 사용자 지정 필드에 업데이트합니다.
## 사전 준비
이 워크플로우를 설정하기 전에 **사람** 객체에 사용자 지정 필드를 생성합니다:
1. **설정 → 데이터 모델 → 사람**으로 이동
2. 새 **숫자** 필드를 추가합니다
3. 필드 이름을 "이 사람에게서 받은 이메일 수"처럼 지정합니다
## 단계별 설정
<VimeoEmbed videoId="1148700336" title="동영상 시연" />
### 1단계: 트리거 구성
1. **워크플로우**로 이동해 새 워크플로우를 생성합니다
2. 트리거로 **레코드가 생성됨**을 선택합니다
3. **Message Participants**를 선택합니다(고급 객체에 있음)
<Note>
Message Participant는 메시지 ID와 사람 ID의 조합으로, 메시지마다 하나의 고유 레코드를 생성합니다. 이는 발신자(또는 수신자)의 이메일 주소가 들어 있는 `handle` 필드에 접근할 수 있으므로 메시지를 직접 추적하는 것보다 더 쉽습니다.
</Note>
### 2단계: Role 기준으로 필터링
1. **필터** 액션을 추가합니다
2. 조건을 설정합니다: **Role**이 **FROM**과 같음
이렇게 하면 해당 사람이 보낸 메시지만 집계되고, 그 사람에게 보낸 메시지는 제외됩니다.
### 3단계: 동일한 Handle을 가진 모든 Message Participants 검색
1. **레코드 검색** 액션을 추가합니다
2. 객체로 **Message Participants**를 선택합니다
3. 필터를 추가합니다: **Handle**이 트리거의 handle(발신자의 이메일 주소)과 같고, **Role**이 **FROM**과 같음
4. **Limit**을 1에서 최대값인 **200**으로 늘립니다
해당 이메일 주소에서 보낸 모든 메시지를 찾아 총 개수를 구합니다.
<Note>
Search Records 액션은 최대 200개의 레코드만 반환합니다. 그러나 개별 레코드가 아니라 `totalCount` 값만 사용하므로, 이 단계는 해당 사람이 보낸 이메일의 총 개수를 반환합니다.
</Note>
### 4단계: Create or Update Record 액션으로 사람 레코드를 업데이트합니다
1. **레코드 생성 또는 업데이트** 액션을 추가합니다
2. 객체로 **People**를 선택합니다
3. Message Participant의 `handle`과 해당 사람의 이메일을 일치시켜 사람을 찾습니다.
4. 사용자 지정 "수신한 이메일 수" 필드를 `{{searchRecords.totalCount}}`로 설정합니다
Search Records 액션의 `totalCount` 값은 이 사람에게서 받은 이메일의 총 개수를 나타냅니다.
## 관련 항목
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [사용자 지정 필드 만들기](/l/ko/user-guide/data-model/how-tos/customize-your-data-model)
* [레코드 검색 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions#search-records)

View File

@@ -0,0 +1,170 @@
---
title: 관련 레코드 데이터 표시
description: "워크플로우를 사용해 관련 레코드의 데이터를 표시합니다(예: Opportunities에 회사 정보 표시)."
---
관련 레코드의 데이터를 내 레코드에 직접 표시하세요 — 예를 들어, Company의 직원 수를 해당 Opportunities에 표시합니다. 중첩 필드가 기본적으로 제공될 때까지 이 워크플로우 우회 방법이 유용합니다.
## 일반적인 사용 사례
| 소스 | 대상 | 복사할 필드 |
| -- | -- | ------------------ |
| 회사 | 기회 | 산업, 회사 규모, ARR |
| 사람 | 기회 | 이메일, 전화, 직함 |
| 기회 | 회사 | 최근 거래 금액, 최근 성사 일자 |
## 기본 필드 복사
### 예시: 연락처 이메일을 Opportunity로 복사
**목표**: Opportunity에서 담당자를 설정할 때, 쉽게 액세스할 수 있도록 해당 이메일을 Opportunity로 복사합니다.
### 사전 준비
워크플로우를 만들기 전에 **설정 → 데이터 모델 → Opportunities**에서 대상 필드를 생성하세요:
* 연락처 이메일(유형: Email)
* 연락처 전화(유형: Phone)
### 설정
1. **트리거**: 레코드가 업데이트됨 (Opportunities, 담당자 필드)
2. **필터**: 담당자가 비어 있지 않은지 확인
3. **레코드 검색**: 연결된 사람 찾기
* 객체: People
* 필터: ID가 `{{trigger.object.pointOfContact.id}}`와 같음
4. **레코드 업데이트**:
* 객체: Opportunities
* 레코드: `{{trigger.object.id}}`
* 연락처 이메일: `{{searchRecords[0].email}}`
* 연락처 전화: `{{searchRecords[0].phone}}`
## 여러 필드 복사
### 예시: 모든 관련 Opportunities에 회사 정보 동기화
**목표**: 회사 세부 정보가 변경되면 관련된 모든 Opportunities를 업데이트합니다.
### 설정
1. **트리거**: 레코드가 업데이트됨 (Companies)
* 필드: 산업, 회사 규모, 연간 매출
2. **레코드 검색**: 이 회사의 모든 Opportunities 찾기
* 객체: Opportunities
* 필터: 회사 ID가 `{{trigger.object.id}}`와 같음
3. **Iterator**: 각 Opportunity를 순회
4. **레코드 업데이트** (iterator 내부):
* 객체: Opportunities
* 레코드: `{{iterator.currentItem.id}}`
* 회사 산업: `{{trigger.object.industry}}`
* 회사 규모: `{{trigger.object.companySize}}`
* 회사 ARR: `{{trigger.object.annualRevenue}}`
## 레코드 생성 시 복사
### 예시: 회사 데이터로 Opportunity를 미리 채우기
**목표**: 회사와 연결된 Opportunity를 생성할 때 핵심 회사 정보를 자동으로 복사합니다.
### 사전 준비
**설정 → 데이터 모델 → Opportunities**에서 대상 필드를 생성하세요:
* 회사 산업(유형: Text)
* 회사 규모(유형: Number)
### 설정
1. **트리거**: 레코드가 생성됨 (Opportunities)
* 필터: 회사가 비어 있지 않음
2. **레코드 검색**: 연결된 회사의 세부 정보 가져오기
* 객체: Companies
* 필터: ID가 `{{trigger.object.company.id}}`와 같음
3. **레코드 업데이트**:
* 객체: Opportunities
* 레코드: `{{trigger.object.id}}`
* 회사 산업: `{{searchRecords[0].industry}}`
* 회사 규모: `{{searchRecords[0].employees}}`
<Note>
**작업 및 노트 제한 사항**: 작업과 노트의 관계는 다대다로 하드코딩되어 있으며, 워크플로우 트리거 또는 액션에서 아직 사용할 수 없습니다. 이러한 관계에 액세스하려면 대신 [API](/l/ko/developers/extend/api)를 사용하세요.
</Note>
## 양방향 동기화
### 예시: 기본 담당자 동기화 유지
**목표**: 회사의 기본 담당자가 변경되면 담당자를 업데이트합니다. 사람이 기본 담당자가 되면 회사를 업데이트합니다.
### 워크플로우 1: Company → Person
1. **트리거**: 레코드가 업데이트됨 (Companies, 기본 담당자 필드)
2. **레코드 업데이트**: 사람의 "Is Primary Contact"를 true로 설정
3. **레코드 검색**: 이전 기본 담당자 찾기
4. **레코드 업데이트**: 이전 담당자의 "Is Primary Contact"를 false로 설정
### 워크플로우 2: Person → Company
1. **트리거**: 레코드가 업데이트됨 (People, Is Primary Contact = true)
2. **레코드 업데이트**: 회사의 기본 담당자를 이 사람으로 설정
<Note>
무한 루프를 피하기 위해 양방향 동기화에 주의하세요. 업데이트하기 전에 값이 실제로 변경되었는지 필터로 확인하세요.
</Note>
## 복잡한 매핑에 코드 사용
### 예시: 복사 중 데이터 변환
**목표**: 사람의 전화번호를 Opportunity로 복사하고 형식을 지정합니다.
```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 };
};
```
## 모범 사례
### 루프 방지
* 서로를 끝없이 트리거하는 워크플로우를 만들지 마세요
* 특정 필드 조건 사용
* 값이 실제로 변경되었는지 확인을 추가하세요
### 누락된 데이터 처리
* 복사하기 전에 소스 레코드가 존재하는지 항상 확인하세요
* 선택적 필드에 기본값 제공
* 소스 필드가 비어 있을 때 건너뛰도록 필터 사용
### 성능
* 여러 레코드에 복사할 때 일괄 업데이트 사용
* 대량 동기화 작업에는 예약된 워크플로우를 사용하세요
* 다수의 레코드를 업데이트할 때 Iterator 사용을 고려하세요
## 관련 항목
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)

View File

@@ -0,0 +1,202 @@
---
title: 수식 필드
description: 네이티브 지원이 제공될 때까지 워크플로우를 사용해 수식 필드를 생성하세요.
---
Twenty는 아직 네이티브 수식 필드를 지원하지 않습니다(2026년에 제공 예정이지만), 워크플로우를 사용하면 동일한 결과를 얻을 수 있습니다. 이 우회 방법을 사용하면 단순한 연결부터 복잡한 비즈니스 로직까지 필드 값을 자동으로 계산하고 채울 수 있습니다.
## 일반적인 사용 사례
| 사용 사례 | 수식 예시 |
| --------------- | ---------------------------- |
| **전체 이름** | First Name + " " + Last Name |
| **예상 금액** | Amount × Probability |
| **마감일까지 남은 일수** | Due Date - Today |
| **단계 경과 일수** | Today - Stage Entry Date |
| **리드 점수** | 여러 기준을 기반으로 한 점수 |
<Tip>
파이프라인 단계에서 시간을 추적하는 전체 예시는 [각 단계에서 기회가 머무는 기간 추적하기](/l/ko/user-guide/views-pipelines/how-tos/track-time-in-stage)를 참조하세요.
</Tip>
## 기본 수식: 연결
### 예시: 전체 이름 자동 채우기
**목표**: 이름과 성을 결합해 전체 이름 필드에 자동으로 입력합니다.
### 설정
1. **트리거**: 레코드가 업데이트되거나 생성됨 (People)
2. **필터**: 이름 또는 성이 변경되었는지 확인
3. **코드 액션**:
```javascript
export const main = async (params) => {
const { firstName, lastName } = params;
const fullName = [firstName, lastName]
.filter(Boolean)
.join(' ');
return { fullName };
};
```
4. **레코드 업데이트**: Full Name을 `{{code.fullName}}`(으)로 설정
## 수치 수식: 예상 금액
### 예시: 예상 매출 계산
**목표**: 기회 금액에 확률을 곱하여 예상 금액을 계산합니다.
전체 워크플로우는 [파이프라인에서 예상 금액 표시하기](/l/ko/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)를 참조하세요.
### 설정
1. **트리거**: 레코드가 업데이트됨 (Opportunities, Amount 또는 Probability 필드)
2. **코드 액션**:
```javascript
export const main = async (params) => {
const { amount, probability } = params;
const expectedAmount = (amount || 0) * (probability || 0) / 100;
return { expectedAmount };
};
```
3. **레코드 업데이트**: Expected Amount를 `{{code.expectedAmount}}`(으)로 설정
## 날짜 수식: 일수 계산
### 예시: 작업 마감일까지 남은 일수
**목표**: 작업 마감일까지 남은 일수를 계산합니다.
### 설정
1. **트리거**: 레코드가 업데이트되거나 생성됨 (Tasks, Due Date 필드)
2. **코드 액션**:
```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. **레코드 업데이트**: Days Until Due를 `{{code.daysUntilDue}}`(으)로 설정
<Note>
음수 값은 기한이 지난 작업을 의미합니다. 이 필드를 사용하여 긴급도에 따라 작업을 필터링하거나 정렬할 수 있습니다.
</Note>
## 조건 수식: 리드 점수
### 예시: 기준에 따라 리드 점수 계산
**목표**: 회사 규모, 산업, 참여도에 따라 리드 점수를 산정합니다.
### 설정
1. **트리거**: 레코드가 업데이트됨 (People 또는 Companies)
2. **코드 액션**:
```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. **레코드 업데이트**: Lead Score를 `{{code.leadScore}}`(으)로 설정
## 텍스트 수식: 도메인 추출
### 예시: 이메일에서 도메인 추출
**목표**: 이메일 도메인을 자동으로 추출하여 저장합니다.
### 설정
1. **트리거**: 레코드가 업데이트됨 (People, Email 필드)
2. **코드 액션**:
```javascript
export const main = async (params) => {
const { email } = params;
if (!email) return { domain: null };
const domain = email.split('@')[1]?.toLowerCase();
return { domain };
};
```
3. **레코드 업데이트**: Domain 필드를 `{{code.domain}}`(으)로 설정
## 모범 사례
### 성능
* 관련 필드 변경에만 트리거되도록 하세요.
* 계산이 필요 없는 레코드는 필터로 건너뛰세요.
* 대량 워크플로우에서는 복잡한 계산을 피하세요.
### 오류 처리
* 계산 전에 null/undefined 값을 확인하세요.
* 데이터가 없을 때는 기본값을 사용하세요.
* 계산이 실패하면 명확한 오류 메시지를 반환하세요.
### 테스트
* 엣지 케이스로 테스트하세요(빈 필드, 0 값 등).
* 활성화 전에 계산을 수동으로 검증하세요.
* 예상치 못한 결과가 있는지 워크플로우 실행을 모니터링하세요.
## 관련 항목
* [파이프라인에서 예상 금액 표시하기](/l/ko/user-guide/views-pipelines/how-tos/show-expected-amount-in-pipeline)
* [단계에서 시간 추적하기](/l/ko/user-guide/views-pipelines/how-tos/track-time-in-stage)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)

View File

@@ -0,0 +1,127 @@
---
title: 검토할 노트에 대해 팀원에게 알림 보내기
description: 노트 검토를 위해 동료를 지정할 때 자동 이메일 알림을 보냅니다.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>
**노트에서 팀원을 직접 태그하는 기능은 아직 지원되지 않습니다.** 이 기능은 2026년 하반기에 제공될 예정입니다. 그동안 이 워크플로우가 실질적인 대안을 제공합니다.
</Note>
## 과제
노트에서 동료를 태그해 검토 알림을 받도록 하고 싶습니다. 현재 Twenty는 노트나 작업에서 @멘션을 지원하지 않습니다.
## 대안 방법
노트 본문에서 태그하는 대신 다음을 수행할 수 있습니다:
1. 노트 개체에 **검토자** 관계 필드를 생성합니다
2. 노트를 생성하거나 편집할 때 검토자를 지정합니다
3. 워크플로우가 검토자에게 노트 링크가 포함된 이메일을 자동으로 보내도록 설정하세요
<Warning>
**제한 사항**: 이 방법은 필드당 한 명의 검토자만 지정할 수 있습니다. 여러 사람에게 알림이 필요하다면 추가 관계 필드를 생성하세요(예: 검토자 1, 검토자 2). 한 번에 많은 공동 작업자를 태그해야 하는 경우 확장성이 떨어집니다.
</Warning>
## 단계별 설정
### 1단계: 검토자 필드 생성
<img src="/images/user-guide/workflows/reviewer-field-for-note.png" style={{width:'100%'}} />
1. **설정 → 데이터 모델 → 노트**로 이동
2. **필드 추가**를 클릭합니다
3. 구성:
| 설정 | 값 |
| --------- | ---------------------- |
| **Name** | 검토자 |
| **유형** | 관계 |
| **관련 개체** | 워크스페이스 멤버 |
| **관계 유형** | 여러 노트 → 하나의 워크스페이스 구성원 |
4. 필드를 저장합니다
### 2단계: 워크플로우 생성
<VimeoEmbed videoId="1151897168" title="동영상 시연" />
#### 워크플로우 트리거 만들기
1. 메인 화면으로 이동해 새 워크플로우를 생성합니다
2. 트리거로 **레코드가 업데이트됨**을 선택합니다
3. 개체로 **노트**를 선택하고 검토자 필드를 지정합니다.
4. 노트의 검토자가 업데이트될 때마다 트리거됩니다.
#### 방금 검토자가 지정되었는지 확인하는 필터 추가
검토자가 지정된 경우에만 워크플로우가 계속되도록 **필터** 작업을 추가합니다:
| 조건 | 값 |
| ------- | -------- |
| **검토자** | 비어 있지 않음 |
#### 워크스페이스 구성원 검색
1. **레코드 검색** 작업을 추가합니다
2. 고급 객체 아래의 **Workspace Member**를 선택합니다
3. 필터 추가:
| 조건 | 값 |
| ------ | ------------------------- |
| **ID** | `{{note.reviewerId}}`와 같음 |
이렇게 하면 이메일 주소와 이름을 포함한 전체 워크스페이스 구성원 레코드를 가져옵니다.
#### 알림 이메일 보내기
1. **이메일 보내기** 작업을 추가합니다
2. 구성:
| 필드 | 값 |
| --------- | ---------------------------------- |
| **받는 사람** | 레코드 검색 → 첫 번째 워크스페이스 구성원 → 사용자 이메일 |
| **제목** | 노트 검토가 필요합니다 |
| **본문** | 아래 예시를 참조하세요 |
**이메일 본문 예시:**
```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>
`yourSubDomain`을 실제 Twenty 워크스페이스 서브도메인으로 바꾸세요.
</Note>
#### 테스트 및 활성화
1. 워크플로우를 수동으로 실행하려면 **Test**를 클릭합니다
2. 노트를 생성하거나 편집하고 검토자를 지정합니다
3. 검토자의 받은편지함에서 알림 이메일을 확인합니다
4. 워크플로우를 활성화합니다
## 사용법
설정이 완료되면 팀은 다음과 같이 이 워크플로우를 사용할 수 있습니다:
1. 새 노트를 생성(또는 기존 노트를 편집)합니다
2. **검토자** 필드에서 동료를 선택합니다
3. 동료는 노트로 바로 이동하는 링크가 포함된 이메일을 자동으로 받습니다
## 관련 항목
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우에서 이메일 보내기](/l/ko/user-guide/workflows/capabilities/send-emails-from-workflows)
* [사용자 지정 필드 만들기](/l/ko/user-guide/data-model/how-tos/create-custom-fields)
* [관계 필드 만들기](/l/ko/user-guide/data-model/how-tos/create-relation-fields)

View File

@@ -0,0 +1,106 @@
---
title: 마감 예정 작업에 대한 이메일 알림 보내기
description: 예정되었거나 기한이 지난 작업에 대해 팀 구성원에게 자동으로 알립니다.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<img src="/images/user-guide/workflows/email_tasks_due.png" style={{width:'100%'}} />
오늘 마감인 작업에 대해 각 팀원에게 매일 이메일 알림을 보냅니다.
## 개요
이 워크플로우는 일정에 따라 실행되며 다음을 수행합니다:
1. 모든 워크스페이스 구성원을 가져옵니다
2. 각 구성원을 순회합니다
3. 각 구성원의 오늘 마감 작업을 찾습니다
4. 개인화된 이메일을 작성해 전송합니다
## 단계별 설정
<VimeoEmbed videoId="1147463514" title="동영상 시연" />
### 1단계: 트리거 구성
1. **Settings → Workflows**로 이동해 새 워크플로우를 생성합니다
2. 트리거로 **On a Schedule**를 선택합니다
3. 매일 오전 8시에 실행되도록 다음 cron 표현식을 사용하세요: `0 8 * * *`
### 2단계: 모든 워크스페이스 구성원 검색
1. **레코드 검색** 액션을 추가합니다
2. 고급 객체(advanced objects) 아래의 **Workspace Members**를 선택합니다
3. 필터는 필요 없습니다 — 모든 구성원이 반환됩니다
### 3단계: 이터레이터 추가
1. **Iterator** 액션을 추가합니다
2. 입력 배열을 이전 단계의 워크스페이스 구성원으로 설정합니다
3. 이터레이터 내부의 모든 액션은 구성원당 한 번씩 실행됩니다
### 4단계: 오늘 마감 작업 검색(이터레이터 내부)
1. 이터레이터 내부에 **레코드 검색** 액션을 추가합니다
2. 객체로 **Tasks**를 선택합니다
3. 필터 추가:
* **Assignee** = 현재 워크스페이스 구성원(이터레이터에서 가져옴)
* **Due Date** = 오늘
### 5단계: 작업을 이메일 본문 형식으로 정리(이터레이터 내부)
작업을 링크가 포함된 읽기 쉬운 목록으로 형식화하기 위해 **Code** 액션을 추가합니다:
```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>
`yourSubDomain`을 실제 Twenty 워크스페이스 서브도메인으로 바꾸세요.
</Note>
### 6단계: 이메일 보내기(이터레이터 내부)
1. **Send Email** 액션을 추가합니다(여전히 이터레이터 내부)
2. 구성:
| 필드 | 값 |
| --------- | ------------------------------------------------------ |
| **받는 사람** | `{{iterator.currentItem.userEmail}}` (워크스페이스 구성원의 이메일) |
| **제목** | 오늘 마감인 작업 |
| **본문** | `{{code.formattedTasks}}` |
### 7단계: 테스트 및 활성화
1. 워크플로우를 수동으로 실행하려면 **Test**를 클릭합니다
2. 이메일이 도착했는지 받은편지함을 확인하세요
3. 워크플로우를 활성화합니다
## 관련 항목
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [워크플로우에서 이메일 보내기](/l/ko/user-guide/workflows/capabilities/send-emails-from-workflows)
* [Code 액션에서 배열 처리하기](/l/ko/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions)

View File

@@ -0,0 +1,21 @@
---
title: 전문 서비스
description: Twenty 팀과 인증된 파트너로부터 복잡한 워크플로우 및 자동화를 구축하는 데 전문가의 도움을 받으십시오.
---
## 언제 전문가의 도움이 필요합니까?
다음 항목에 대해 전문 서비스를 고려해 보세요:
* 복잡한 다중 시스템 통합
* 고급 비즈니스 논리 및 자동화 규칙
* 대규모 데이터 처리 워크플로우
* 맞춤형 API 개발
* 팀 교육 및 워크플로우 최적화
* 내부 리소스가 부족할 때
## 서비스 옵션
### 구현 파트너
고급 맞춤 설정을 위해 인증된 파트너와 협력하십시오. [contact@twenty.com](mailto:contact@twenty.com)으로 문의하시면 당사의 [구현 파트너](https://twenty.com/partners)와 연결해 드립니다.

View File

@@ -0,0 +1,170 @@
---
title: 워크플로우 문제 해결
description: 일반적인 워크플로우 문제와 해결 방법.
---
## 일반적인 문제와 해결책
### 워크플로우가 작동하지 않음
**증상**: 워크플로우가 예상한 시점에 실행되지 않습니다.
**가능한 원인**:
1. **워크플로우가 활성화되지 않음**: 워크플로우가 "Draft"가 아니라 "Active"로 설정되어 있는지 확인하세요
2. **트리거 조건 불충족**: 트리거가 예상한 이벤트와 일치하는지 확인하세요
3. **필드가 모니터링되지 않음**: "Record is Updated" 트리거의 경우 특정 필드가 모니터링되고 있는지 확인하세요
4. **권한**: 워크플로우를 실행할 권한이 있는지 확인하세요
**해결책**:
* 워크플로우 목록에서 워크플로우 상태를 확인하세요
* 해당 트리거를 유발할 것으로 예상되는 특정 작업으로 테스트하세요
* 트리거 구성을 검토하세요
* 권한과 관련해 관리자에게 문의하세요
### 워크플로우 트리거가 너무 일찍 발생함(빈 필드)
**증상**: UI에서 레코드를 수동으로 생성할 때 모든 필드를 채우기 전에 워크플로우가 트리거됩니다. 대부분의 필드 값이 비어 있는 상태로 워크플로우가 실행됩니다.
**발생 원인**: Twenty는 모든 내용을 실시간으로 저장합니다 — 별도의 "edit" 및 "read" 모드는 없습니다. 레코드를 생성하면 즉시 저장되어 추가 필드를 채우기 전에 "Record is created" 이벤트가 트리거됩니다.
**"Record is created"가 적절한 경우**:
* API 호출로 생성된 레코드(단일 요청으로 필드가 채워짐)
* 가져오기로 생성된 레코드
* 다른 워크플로우에서 자동으로 생성된 레코드
**해결 방법**: UI에서 수동으로 생성된 레코드의 경우 트리거로 \*\*"Record is created or updated"\*\*를 사용하세요. 이렇게 하면:
* 사용자가 필드를 모두 작성하고 저장한 후에 워크플로우가 트리거됩니다
* 빈 값이 아닌 완전한 데이터를 얻을 수 있습니다
<Tip>
레코드당 한 번만 워크플로우를 실행하려면, `createdAt equals updatedAt`(첫 저장)과 같은 필드를 확인하는 필터 액션을 추가하거나, 워크플로우가 이미 실행되었는지 추적하기 위해 사용자 지정 체크박스 필드를 사용하세요.
</Tip>
### 액션 실패
**증상**: 워크플로우는 실행되지만 일부 액션이 실패합니다.
**가능한 원인**:
1. **데이터 누락**: 필수 필드가 비어 있음
2. **잘못된 참조**: 이전 단계의 변수가 존재하지 않음
3. **API 오류**: 외부 서비스가 오류를 반환함
4. **권한 문제**: 액션에 필요한 권한이 없음
**해결책**:
* 워크플로우 실행 세부 정보에서 오류 메시지를 확인하세요
* 모든 필수 필드에 값이 있는지 확인하세요
* API 연결을 별도로 테스트하세요
* 역할 권한을 검토하세요
### HTTP 요청 오류
**증상**: HTTP 요청 액션이 실패하거나 예상치 못한 결과를 반환합니다.
**일반적인 오류 코드**:
* **400**: 잘못된 요청 - 요청 본문 형식을 확인하세요
* **401**: 인증되지 않음 - API 키를 확인하세요
* **403**: 접근 거부 - API 권한을 확인하세요
* **404**: 찾을 수 없음 - 엔드포인트 URL을 확인하세요
* **429**: 너무 많은 요청 - 요청 속도 제한을 구현하세요
* **500**: 서버 오류 - 외부 서비스 문제
**해결책**:
* API 엔드포인트 URL을 확인하세요
* 인증 헤더를 확인하세요
* 먼저 Twenty 밖에서 API 호출을 테스트하세요
* 코드 액션에 오류 처리를 추가하세요
### 코드 액션 오류
**증상**: JavaScript 코드가 실행되지 않습니다.
**일반적인 문제**:
1. **구문 오류**: 오탈자 또는 유효하지 않은 JavaScript
2. **정의되지 않은 변수**: 존재하지 않는 변수를 참조함
3. **타입 오류**: 잘못된 데이터 타입에 대한 연산
4. **시간 초과**: 코드 실행에 너무 오래 걸림
**해결책**:
* 내장 코드 편집기의 유효성 검사를 사용하세요
* 먼저 JavaScript 콘솔에서 코드 로직을 테스트하세요
* 디버깅을 위해 console.log 문을 추가하세요
* 복잡한 연산을 단순화하세요
### 이메일이 전송되지 않음
**증상**: 이메일 보내기 액션이 이메일을 전송하지 않습니다.
**가능한 원인**:
1. **이메일 계정이 연결되지 않음**: Settings → Accounts를 확인하세요
2. **잘못된 이메일 주소**: 수신자 이메일이 형식에 맞지 않음
3. **전송 한도**: 이메일 제공업체의 속도 제한에 도달함
4. **스팸 필터**: 이메일이 차단됨
**해결책**:
* 이메일 계정 연결 상태를 확인하세요
* 수신자 이메일 주소를 검증하세요
* 이메일 제공업체의 제한을 확인하세요
* 스팸 트리거가 될 수 있는 이메일 콘텐츠를 검토하세요
## 워크플로우 디버깅
### 워크플로우 실행 활용
1. 워크플로우 편집기로 이동하세요
2. **Runs** 패널을 엽니다
3. 실패한 실행을 찾으세요
4. 클릭하여 단계별 세부 정보를 확인하세요
5. 오류 메시지와 출력 데이터를 검토하세요
### 개별 단계 테스트
1. 코드 액션의 경우 **Test** 버튼을 사용하세요
2. HTTP 요청의 경우 엔드포인트를 별도로 테스트하세요
3. 워크플로우를 트리거하기 위한 테스트 레코드를 생성하세요
4. 제어된 테스트를 위해 수동 트리거를 사용하세요
### 일반적인 디버깅 패턴
**로깅 추가**:
디버깅을 위해 코드 액션을 사용해 중간 값을 기록하세요.
**단계 격리**:
실패를 식별하기 위해 각 단계를 개별적으로 테스트하세요.
**데이터 흐름 확인**:
각 단계가 예상된 입력 데이터를 수신하는지 확인하세요.
## 문제를 피하기 위한 모범 사례
### 활성화 이전
* 초안 모드에서 충분히 테스트하세요
* 모든 API 연결을 검증하세요
* 트리거 조건을 주의 깊게 검토하세요
* 예상 동작을 문서화하세요
### 개발 중
* 알기 쉬운 단계 이름을 사용하세요
* 코드 액션에 주석을 추가하세요
* 현실적인 데이터로 테스트하세요
* 엣지 케이스를 대비하세요
### 활성화 이후
* 초기 실행을 면밀히 모니터링하세요
* 실패에 대한 알림을 설정하세요
* 실행 기록을 정기적으로 검토하세요
* 가능한 경우 워크플로우를 단순하게 유지하세요

View File

@@ -0,0 +1,236 @@
---
title: 워크플로우 FAQ
description: Twenty의 워크플로우에 관한 자주 묻는 질문입니다.
---
<AccordionGroup>
<Accordion title="워크플로우를 활성화할 수 없는 이유는 무엇인가요?">
이는 권한 문제일 가능성이 높습니다. 워크플로우를 생성하고 활성화하려면 워크플로우에 대한 접근 권한이 필요합니다.
**해결 방법**: 워크스페이스 관리자에게 문의하여 **Settings → Members → Roles**에서 워크플로 액세스 권한을 부여받으세요.
사이드바에서 워크플로우 섹션이 전혀 보이지 않는다면, 이는 권한 문제임이 확실합니다.
</Accordion>
<Accordion title="상단 내비게이션 바에서 수동 워크플로우 버튼이 보이지 않는 이유는 무엇인가요?">
수동 워크플로우는 올바르게 구성된 경우에만 내비게이션 바에 표시됩니다:
1. 워크플로우가 **활성화**되어 있어야 합니다(초안 모드 아님)
2. navbar 배치는 **Pinned**로 설정되어 있어야 합니다
3. Single/Bulk 트리거의 경우 올바른 오브젝트 페이지에 있어야 합니다
**확인 방법**: 워크플로우를 연 다음 → 트리거를 클릭 → "Navbar placement"가 "Pinned"로 설정되어 있는지 확인합니다.
내비게이션 바 설정과 관계없이 **Cmd + K**(또는 **Ctrl + K**)를 통해 언제든지 수동 워크플로우에 접근할 수 있습니다.
</Accordion>
<Accordion title="Single, Global, Bulk 수동 트리거의 차이점은 무엇인가요?">
| 유형 | 필요한 레코드 | Workflow 실행들 |
| -- | ------- | ------------ |
\| **Global** | 없음 | 한 번, 레코드 입력 없음 |
\| **Single** | 하나 이상 선택됨 | 선택된 레코드마다 한 번씩 |
\| **Bulk** | 하나 이상 선택됨 | 한 번, 모든 레코드를 배열로 처리 |
* **Global**: 워크플로우에 레코드 컨텍스트가 필요하지 않을 때 사용합니다(예: 리포트 생성)
* **Single**: 선택된 각 레코드를 개별적으로 처리하려는 경우에 사용합니다(예: 개별 이메일 발송)
* **Bulk**: 레코드를 함께 처리하거나 크레딧 사용을 최적화해야 할 때 사용합니다(Iterator 액션 필요)
자세한 내용은 [Workflow Triggers](/l/ko/user-guide/workflows/capabilities/workflow-triggers)를 참조하세요.
</Accordion>
<Accordion title="If/Else 노드가 보이지 않습니다. 어떻게 해야 하나요?">
명시적인 If/Else 노드는 아직 제공되지 않지만 로드맵에 포함되어 있습니다.
**현재 우회 방법**: 단계에서 여러 분기를 만들고, 각 분기의 첫 단계로 **Filter** 액션을 추가하세요:
```
Step 1
│
├── Branch A: Filter (condition = true) → Actions...
│
└── Branch B: Filter (condition = false) → Actions...
```
Filter 조건을 통과한 분기만 이후 액션을 실행합니다.
단계별 안내는 [How to Use Branches](/l/ko/user-guide/workflows/capabilities/workflow-branches)를 참고하세요.
</Accordion>
<Accordion title="워크플로우 분기는 병렬로 실행되나요?">
**예**, 분기는 기본적으로 병렬로 실행됩니다.
하나의 분기만 실행되도록 하려면:
* 각 분기의 시작 부분에 **Filter** 액션을 추가합니다
* 서로 반대되는 조건을 설정합니다(예: 분기 A: status = "Open", 분기 B: status ≠ "Open")
Filter 조건을 만족하지 못한 분기는 실행이 중지되고, 다른 분기는 계속 실행됩니다.
</Accordion>
<Accordion title="분기를 다시 병합할 수 있나요?">
**예**. 병렬 분기가 완료된 후, 두 분기 모두가 연결되는 단계를 추가할 수 있습니다.
워크플로우 편집기에서:
1. 분기된 액션을 완료합니다
2. 분기 이후에 실행될 새 단계를 추가합니다
3. 각 분기의 마지막 단계에서 이 새 단계로 연결선을 드래그합니다
모든 연결된 분기가 완료된 후 병합된 단계가 실행됩니다.
</Accordion>
<Accordion title="Search Records 결과의 한도는 얼마인가요?">
**Search Records는 최대 200개 레코드를 반환합니다.**
더 많은 레코드를 처리해야 하는 경우:
* 결과를 줄이기 위해 더 구체적인 필터를 추가합니다
* 배치 처리를 위해 예약된 워크플로우를 사용합니다
* 대량 작업에는 API 사용을 고려합니다
대부분의 워크플로우에는 200개 레코드면 충분합니다. 이 한도에 자주 도달한다면 자동화 구조를 재설계하는 것을 고려하세요.
</Accordion>
<Accordion title="워크플로우 이메일에 사람들을 CC로 포함할 수 있나요?">
**아직은 불가능합니다.** Send Email 액션의 CC 및 BCC 필드는 로드맵에 포함되어 있습니다.
**현재 우회 방법**: 추가 수신자에게 이메일을 보내기 위해 여러 개의 Send Email 액션을 추가하거나, CC를 지원하는 외부 이메일 서비스를 통해 전송하도록 HTTP Request를 사용하세요.
</Accordion>
<Accordion title="이전 단계의 출력을 어떻게 사용하나요?">
모든 액션은 이후 단계에서 사용할 수 있는 출력 데이터를 생성합니다.
**이전 단계 데이터를 참조하려면**:
* 필드를 구성할 때 변수 선택기를 사용합니다
* 또는 `{{stepName.fieldName}}`을 직접 입력합니다
**예시**:
* 트리거 데이터: `{{trigger.object.email}}`
* 검색 결과: `{{searchRecords[0].name}}`
* 코드 출력: `{{code.calculatedValue}}`
액션 구성에서 임의의 필드 위에 마우스를 올리면 이전 단계에서 사용할 수 있는 변수를 확인할 수 있습니다.
</Accordion>
<Accordion title="Iterator가 실패하는 이유는 무엇인가요?">
**Iterator에는 배열 입력이 필요합니다.** 일반적인 문제는 다음과 같습니다:
1. **입력이 배열이 아님**: Search Records 또는 배열을 반환하는 다른 액션의 결과를 전달하고 있는지 확인합니다
2. **배열이 비어 있음**: `{{searchRecords.length}} > 0`을 확인하는 Filter를 Iterator 앞에 추가하세요
3. **잘못된 변수가 선택됨**: 단일 레코드가 아니라 배열 자체를 선택했는지 확인합니다
**올바른 설정**:
1. Search Records(배열 반환)
2. Filter: length > 0
3. Iterator: `{{searchRecords}}` 선택
4. Iterator 내부의 액션은 `{{iterator.currentItem.fieldName}}`을 사용합니다
</Accordion>
<Accordion title="Code 액션의 최대 실행 시간은 얼마인가요?">
코드 액션(로직 함수)에는 **5분의 기본 제한 시간** (300초)이 있습니다.
최대로 설정할 수 있는 타임아웃은 **15분**(900초)입니다.
코드 실행 시간이 이 한도를 초과하면 액션은 타임아웃 오류와 함께 실패합니다.
**타임아웃을 피하기 위한 팁**:
* Iterator를 사용해 대규모 작업을 더 작은 단위로 나눕니다
* 무거운 연산은 피하고, 많은 연산이 필요한 경우 HTTP Request를 통해 외부 서비스를 사용하세요
* 코드를 최적화하여 실행 시간을 줄이세요
* 더 긴 처리가 필요하다면 데이터를 배치로 처리하는 예약된 워크플로우 사용을 고려하세요
</Accordion>
<Accordion title="워크플로우 실행 내역은 어떻게 읽나요?">
워크플로우 실행 내역은 실행 이력을 보여주며 문제를 디버깅하는 데 도움이 됩니다.
**실행 내역에 접근하려면**:
* 워크플로우 편집기에서 → 오른쪽의 **Runs** 패널을 엽니다
* 또는 사이드바에서 **워크플로우 실행**으로 이동합니다
**실행 내역 이해하기**:
* **Status**: Running, Completed, Failed, Waiting
* **Steps**: 어떤 단계가 실행되었고 어떤 출력을 생성했는지 확인합니다
* **Errors**: 실패한 단계를 클릭하여 오류 메시지를 확인합니다
* **Data**: 각 단계에서 입력 및 출력 데이터를 확인합니다
자세한 내용은 [Workflow Runs](/l/ko/user-guide/workflows/capabilities/workflow-runs)를 참조하세요.
</Accordion>
<Accordion title="워크플로우 실행 내역이 보이지 않는 이유는 무엇인가요?">
워크플로우 실행이 레이트 리밋 때문에 즉시 실패하고 있을 수 있습니다. Twenty는 워크스페이스마다 시간당 실행 횟수를 5,000회로 엄격히 제한합니다.
이 한도를 초과하면 워크플로가 즉시 실패로 표시되며 실행 목록에 표시되지 않습니다.
**이 한도에 도달하는 일반적인 시나리오**:
* Single 수동 트리거로 5,000개가 넘는 레코드를 선택하는 경우
* 여러 워크플로가 워크스페이스 전체에서 동시에 실행되는 경우
* 고빈도 자동 트리거(예: 사용량이 많은 오브젝트에서의 Record Updated)
**해결책**:
* 여러 레코드를 한 번에 처리할 때는 Single 대신 **Bulk** 트리거를 사용하세요.
* 대용량 배치 작업 사이에 간격을 두세요
* 필터를 사용해 트리거 빈도를 줄이세요
* 부하가 큰 워크플로는 피크 시간이 아닌 시간대에 예약 실행하세요
</Accordion>
<Accordion title="워크플로우 레이트 리밋은 무엇인가요?">
Twenty는 시스템 안정성을 보장하기 위해 두 가지 레이트 리밋을 적용합니다:
| 한도 | 값 | 동작 |
| -- | - | -- |
\| **Soft limit** | 분당 100회 실행 | "Not Started" 상태로 큐에 쌓인 뒤 점진적으로 처리 |
\| **Hard limit** | 시간당 5,000회 실행 | 실행이 즉시 실패 |
**Soft limit(분당 100회)**: 워크플로우가 실패하지는 않으며, 단지 큐에서 대기하다가 시간이 지나면서 처리됩니다. 100개가 넘는 레코드를 트리거할 수 있지만, 실행 속도는 느려집니다.
**Hard limit(시간당 5,000회)**: 워크스페이스 전체에 적용됩니다. 모든 워크플로우를 합쳐 한 시간에 5,000회 실행을 초과하면, 추가 실행은 즉시 실패합니다.
**한도 내에 머무르기 위한 팁**:
* 대규모 배치 작업에는 Single 트리거 대신 Iterator를 함께 사용하는 Bulk 트리거를 사용합니다
* 관련된 자동화를 더 적은 수의 워크플로우로 통합합니다
* 부하를 분산하기 위해 예약된 워크플로우를 사용하세요
</Accordion>
<Accordion title="실패한 워크플로우 실행을 다시 시도할 수 있나요?">
**현재는 자동 재시도 기능이 없습니다.**
워크플로우 실행이 실패한 경우 다음을 수행해야 합니다:
1. **Settings → Workflows → [Your Workflow] → Runs**에서 오류를 확인합니다
2. 문제(데이터, 설정, 외부 서비스)를 수정합니다
3. 영향을 받은 레코드(들)에 대해 워크플로우를 수동으로 다시 트리거합니다
**실패를 줄이기 위한 팁**:
* 액션 실행 전에 데이터를 검증하기 위해 **Filter** 노드를 추가합니다
* 관련 레코드가 존재하는지 확인하기 위해 **Search Records**를 사용합니다
* 대량 작업 전에 소수의 레코드로 충분히 테스트합니다
자동 재시도 기능은 향후 릴리스를 위한 로드맵에 포함되어 있습니다.
</Accordion>
<Accordion title="CSV 가져오기 전에 워크플로우를 비활성화해야 하나요?">
**예, 워크플로우가 레코드 생성 또는 업데이트에 의해 트리거되는 경우에는 비활성화하는 것이 좋습니다.**
CSV로 데이터를 가져올 때, 생성되거나 업데이트되는 각 레코드가 워크플로우를 트리거할 수 있습니다. 수천 개의 레코드를 포함한 대규모 가져오기는 다음과 같은 상황을 초래할 수 있습니다:
* 시간당 5,000회 실행 한도에 도달
* 상당한 워크플로우 크레딧 소모
* 예상치 못한 이메일 또는 알림 발송
* 중복 작업 또는 레코드 생성
**대량 가져오기 전에**:
1. **Settings → Workflows**로 이동합니다
2. 가져오려는 오브젝트에 의해 트리거되는 워크플로우를 식별합니다
3. 해당 워크플로우를 일시적으로 **비활성화**합니다
4. CSV 가져오기를 실행합니다
5. 작업이 끝나면 워크플로우를 다시 **활성화**합니다
**대안**: 가져온 데이터에 워크플로우를 실행해야 한다면, 레이트 리밋 내에 머물 수 있도록 더 작은 배치로 나누어 가져오세요.
</Accordion>
<Accordion title="워크플로우 레이아웃을 정리하려면 어떻게 해야 하나요?">
워크플로우 캔버스에 노드가 흩어져 지저분해 보이는 경우, 자동 정리 기능을 사용할 수 있습니다:
1. 워크플로우 캔버스의 아무 곳이나 마우스 오른쪽 버튼으로 클릭합니다
2. **Tidy up workflow**를 클릭합니다
그러면 모든 노드가 자동으로 재배치되어 깔끔하고 정돈된 레이아웃이 됩니다.
</Accordion>
</AccordionGroup>

View File

@@ -0,0 +1,81 @@
---
title: 워크플로우
description: Twenty에서 자동화를 구축하는 방법을 알아보세요.
---
## 워크플로우의 중요성
Twenty는 사용자가 최대한 유연성을 누릴 수 있도록 설계되었습니다. 정해진 기능에 비즈니스 프로세스를 맞추는 대신, 워크플로우를 통해 귀사만의 고유한 비즈니스 사례를 지원할 수 있는 CRM 자동화를 빌드할 수 있습니다.
워크플로우는 이러한 자동화를 구축하기 위한 Twenty의 앱 내 기능입니다. 필요할 때 필요한 것을 정확히 만들 수 있는 빌딩 블록을 제공합니다.
## 워크플로우로 무엇을 할 수 있나요?
두 가지 주요 목적으로 자동화를 구축하는 것을 권장합니다:
1. **팀의 일상적인 작업을 촉진하기 위한 내부 자동화**: 수작업 입력과 반복적인 작업을 줄여 팀의 속도를 높입니다.
2. **데이터를 받아오고 Twenty에서 내보내기**: API 호출 및 웹훅을 통해 데이터베이스 및 다른 도구와 Twenty를 연결합니다.
## 첫 번째 워크플로우 만들기
### 1단계: 새 워크플로우 생성
1. 다른 오브젝트 아래에 있는 **워크플로우**로 이동합니다
2. **+ 새 레코드**를 클릭합니다
3. 워크플로우에 이름을 지정합니다
### 2단계: 트리거 추가
모든 워크플로우는 트리거로 시작됩니다. 다음 중에서 선택하세요:
* **레코드 이벤트**: 레코드가 생성, 업데이트 또는 삭제될 때
* **일정**: 지정된 시간에 실행(매일, 매주 등)
* **수동**: 사용자 동작으로 트리거됩니다
* **웹훅**: 웹훅에 의해 트리거됩니다
<img src="/images/user-guide/workflows/workflow_triggers.png" style={{width:'100%'}} />
### 3단계: 액션 추가
트리거 다음에 하나 이상의 액션을 추가하세요:
* **레코드 생성**: 모든 오브젝트에 새 레코드를 추가
* **레코드 업데이트**: 기존 레코드 데이터를 수정
* **레코드 삭제**: 오브젝트에서 레코드를 제거
* **레코드 검색**: 조건과 일치하는 레코드를 찾기
* **레코드 업서트**: 일치하는 조건에 따라 생성 또는 업데이트
* **이터레이터**: 레코드 배열을 반복 처리
* **필터**: 어떤 레코드가 진행할지 제어
* **지연**: 계속하기 전에 대기(기간 또는 예약 날짜)
* **이메일 보내기**: 연결된 계정을 통해 이메일 전송
* **코드**: 커스텀 JavaScript 실행
* **HTTP 요청**: 외부 API 호출
* **폼**: 실행 시점에 Twenty UI 내에서 사용자 입력을 받기
* **AI 에이전트**(곧 제공): 지능형 AI 작업 실행
<img src="/images/user-guide/workflows/workflow_actions.png" style={{width:'100%'}} />
### 4단계: 테스트 및 활성화
1. **테스트** 버튼을 사용해 샘플 데이터로 워크플로우를 실행합니다
2. 결과를 검토하여 예상대로 작동하는지 확인합니다
3. 준비가 되면 워크플로우를 **활성화**로 전환합니다
## 워크플로우 최적화 모범 사례
* **단계 이름 편집**: 각 단계가 무엇을 하는지 명확히 설명하도록 워크플로우 단계의 이름을 바꾸십시오. 이 방법은 유지 관리에 도움이 되고 동료에게 인계하기 쉽게 만듭니다.
* **이전 단계 데이터 활용**: 워크플로우의 모든 이전 단계에서 반환된 레코드의 필드를 사용할 수 있습니다.
* **간단하게 시작**: 기본 워크플로우로 시작하고 시스템에 익숙해지면서 점차 복잡도를 추가하십시오.
* **구축 전 계획**: 워크플로우 논리를 사전에 계획하여 구축 도중 막히는 것을 피하십시오.
## 다음 단계
* [워크플로우 트리거](/l/ko/user-guide/workflows/capabilities/workflow-triggers)
* [워크플로우 액션](/l/ko/user-guide/workflows/capabilities/workflow-actions)
* [CRM 자동화](/l/ko/user-guide/workflows/how-tos/crm-automations/closed-won-automations)
## 도움이 필요하신가요?
<Tip>
워크플로 자동화에 도움이 필요하신가요? [자동화 워크플로를 처음부터 끝까지 설계하고 구축해 줄 공인 Twenty 파트너를 찾아보세요](https://twenty.com/partners/list?categories=SOLUTIONING\&ref=docs-workflows).
</Tip>