# 에이전시나 프리랜서용 프로젝트 문의 폼 만들기

> 누군가 브리프를 읽기 전에 예산과 시기로 문의를 유력, 관심, 부적합으로 분류하는 스튜디오와 프리랜서용 프로젝트 의뢰 폼입니다.

2026-10-25 게시 · https://ikiform.com/ko/guides/lead-capture/project-inquiry

서비스는 체크박스로, 예산과 시작일은 선택형으로, 설명은 적합 여부를 판단할 만큼 길게 받으세요. 각 문의를 웹훅으로 작은 릴레이에 보내 예산과 시기에 따라 유력, 관심, 부적합 레인으로 분류하세요. 그런 다음 유력 레인에는 당일 답하고, 부적합 레인에는 하루 안에 정중하게 거절하세요. 두 답장 모두 평판을 쌓습니다.

## 프로젝트 문의가 다른 점

견적 요청은 정해진 작업의 가격을 묻습니다. 프로젝트 문의는 더 큰 질문을 합니다. 우리가 서로 맞을까? 방문자는 아직 프로젝트를 구상 중인 경우가 많고, 나는 디스커버리 콜을 쓸지 정하는 중입니다. 스튜디오나 프리랜서에게 부족한 것은 리드가 아니라, 성사될 리 없던 브리프를 읽는 데 쓰는 파트너의 시간입니다.

그래서 이 폼은 읽기 전에 분류할 수 있을 만큼의 구조를 받습니다. 세 가지 답이면 됩니다. 무엇이 필요한지, 대략 얼마를 쓸 수 있는지, 언제인지입니다. 이야기하고 싶은 모든 것은 긴 설명 하나에 담기고, 구조화된 답이 읽을 가치가 있다고 할 때만 전부 읽습니다.

다이어그램: 릴레이가 프로젝트 문의를 레인으로 분류하는 방식

1. 문의가 서비스, 예산, 시작일이 담긴 서명된 웹훅으로 도착합니다.
2. 릴레이가 서명을 확인하고 예산과 시기에 점수를 매깁니다.
3. 점수를 확인합니다.
4. 3개월 안에 시작하는 $30,000 이상의 예산은 유력 레인으로 갑니다.
5. 예산이 더 낮거나 시작이 늦으면 관심 레인으로 갑니다.
6. 웹사이트 외의 작업에 $10,000 미만이면 부적합 레인으로 가서 빠르고 친절하게 거절합니다.

레인은 채팅 채널입니다. 자동으로 거절되는 사람은 없습니다. 모든 레인에 여전히 사람이 답하며, 순서만 다릅니다.

## 필드

주제 하나만 묻는 가벼운 폼이라면 [문의 폼 템플릿](https://ikiform.com/forms/contact-form)에서 시작하세요. 이 폼은 분류용 질문 세 개를 추가합니다. 미리보기에서 서비스 여러 개를 체크해 목록으로 저장되는 것을 확인하세요.

폼: Project inquiry

- **이름** (`name`, text, 필수)
- **이메일** (`email`, email, 필수)
- **현재 웹사이트** (`website`, url)
- **무엇이 필요하신가요?** (`services`, multi_select, 필수)
- **예산** (`budget`, select, 필수)
- **언제 시작해야 하나요?** (`start`, radio, 필수)
- **프로젝트에 대해 알려 주세요** (`description`, textarea, 필수)

`services`는 `multi_select`이며 `["brand", "website"]` 같은 배열로 저장됩니다. `website`는 `url` 필드이므로 `example.com`은 `https://example.com/`으로 저장됩니다.

| 필드 | 이런 형태인 이유 |
| --- | --- |
| `services` | 실제 프로젝트는 서비스를 섞으므로 체크박스입니다. 웹훅에는 배열로 도착합니다 |
| `budget` | 견적 폼과 달리 여기서는 필수입니다. 범위는 최소 계약 규모에서 시작해야 합니다 |
| `start` | **Exploring for now**(아직 알아보는 중)는 정직하고 유용한 답입니다. 관심 레인으로 갑니다 |
| `description` | 최소 40자, 최대 4,000자. 적합 여부를 판단할 만큼 길고, 끝까지 쓸 수 있을 만큼 짧습니다 |

## 폼

체크박스는 하나의 `name`을 공유합니다. 브라우저는 `services=brand&services=website`를 보내고, Ikiform은 반복된 값을 하나의 목록으로 모읍니다. 도구가 기대한다면 이름 끝에 `[]`를 붙여도 됩니다.

`start-a-project.html`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required />
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <label for="website">Your current website</label>
  <input id="website" name="website" inputmode="url" placeholder="example.com" />

  <fieldset>
    <legend>What do you need?</legend>
    <label><input type="checkbox" name="services" value="brand" /> Brand identity</label>
    <label><input type="checkbox" name="services" value="website" /> Website</label>
    <label><input type="checkbox" name="services" value="app" /> Web or mobile app</label>
    <label><input type="checkbox" name="services" value="seo" /> Search and content</label>
  </fieldset>

  <label for="budget">Budget</label>
  <select id="budget" name="budget" required>
    <option value="under_10k">Under $10,000</option>
    <option value="10k_30k">$10,000 to $30,000</option>
    <option value="30k_75k">$30,000 to $75,000</option>
    <option value="over_75k">Over $75,000</option>
  </select>

  <fieldset>
    <legend>When should it start?</legend>
    <label><input type="radio" name="start" value="this_month" required /> This month</label>
    <label><input type="radio" name="start" value="next_quarter" /> In the next three months</label>
    <label><input type="radio" name="start" value="exploring" /> Exploring for now</label>
  </fieldset>

  <label for="description">Tell us about the project</label>
  <textarea id="description" name="description" rows="6" minlength="40" maxlength="4000" required></textarea>

  <input type="hidden" name="_redirect" value="https://studio.example/inquiry/thanks" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send inquiry</button>
</form>
```

## 웹훅으로 문의 분류하기

이 Express 서버는 각 문의를 받아 검증하고, 레인을 고르고, 그 레인의 Slack 채널에 짧은 요약을 올립니다. 서명은 Ikiform이 보낸 정확한 바이트를 대상으로 하므로 `express.raw`로 원시 본문을 읽고, Node의 `req.headers`를 그대로 `verifyWebhook`에 넘깁니다.

`server.ts`:

```ts
import express from "express";
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";

const BUDGET: Record<string, number> = { under_10k: 0, "10k_30k": 1, "30k_75k": 2, over_75k: 3 };
const START: Record<string, number> = { this_month: 2, next_quarter: 1, exploring: 0 };
const CHANNEL = {
  hot: process.env.SLACK_HOT_LEADS!,
  warm: process.env.SLACK_LEADS!,
  "no-fit": process.env.SLACK_NO_FIT!,
};

function lane(fields: Record<string, unknown>): keyof typeof CHANNEL {
  const budget = BUDGET[String(fields.budget)] ?? 0;
  const start = START[String(fields.start)] ?? 0;
  const services = Array.isArray(fields.services) ? fields.services : [];
  if (budget === 0 && !services.includes("website")) return "no-fit";
  if (budget >= 2 && start >= 1) return "hot";
  return "warm";
}

const app = express();

app.post("/webhooks/inquiries", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = await verifyWebhook({
      payload: req.body.toString("utf8"),
      headers: req.headers,
      secret: process.env.IKIFORM_WEBHOOK_SECRET!,
    });
  } catch (error) {
    if (error instanceof WebhookVerificationError) return res.status(400).send(error.reason);
    throw error;
  }
  if (event.type !== "submission.created") return res.sendStatus(204);

  const fields = event.data.fields;
  const which = lane(fields);
  const summary = String(fields.description ?? "").slice(0, 280);
  const response = await fetch(CHANNEL[which], {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      text: `[${which}] ${fields.name}: ${(fields.services as string[]).join(", ")}, ${fields.budget}, ${fields.start}\n> ${summary}\nmailto:${fields.email}`,
    }),
  });
  res.sendStatus(response.ok ? 204 : 502);
});

app.listen(3000);
```

- `lane()`은 내 영업 방식에 맞게 바꾸세요. 위의 기준값은 예시일 뿐 조언이 아닙니다.
- 요약은 설명의 처음 280자입니다. 전체 텍스트는 대시보드와 `event.data.fields`에 있습니다.
- Slack이 실패할 때 `502`로 응답하면 Ikiform이 같은 전달을 약 3일 동안 재시도하므로 채팅 장애로 문의를 잃지 않습니다.

**Webhooks**에 서버의 공개 `https:` URL을 추가하세요. 서버를 운영하고 싶지 않다면 [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md), [Make](https://ikiform.com/blog/form-submissions-to-make.md), [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md)의 라우터 단계로 같은 분기를 만들 수 있습니다.

## 내 서비스가 스팸처럼 들릴 때

스팸 점수에는 `seo services`, `backlinks`, `rank your website` 같은 알려진 영업 문구가 포함되며, 각각 2점씩 더합니다. 검색 관련 일을 판다면 실제 고객이 바로 그 말을 쓸 수 있습니다. 링크도 합산됩니다. `website`는 링크로 저장되고, 링크가 3개 이상이면 2점이 더해집니다.

| 실제 문의에 포함된 것 | 점수 | 결과 |
| --- | --- | --- |
| "We need SEO services"와 웹사이트 | +2 | 수신 |
| 같은 내용에 설명 속 경쟁사 링크 두 개 추가 | +2와 +2 | 수신, 그 문장에서 신호 하나 |
| 위 모두를 24시간 안에 두 번 보냄 | +2, +2, +3 합산 | 스팸, 5 초과 |

`services`에 검색과 콘텐츠 옵션이 있으면 고객이 그 문구를 입력하지 않고도 필요한 것을 말할 수 있습니다. 그래도 매주 **Spam** 필터를 확인하세요. $50,000짜리 문의 하나를 잃는 것이 스팸 열 개를 읽는 것보다 비쌉니다.

## 작동 확인하기

1. 서비스 두 개를 체크해 제출하세요. 제출에 `services`가 항목 두 개짜리 목록으로 저장됩니다.
2. **Over $75,000**과 **This month**로 제출하세요. 요약이 유력 채널에 올라갑니다.
3. **Under $10,000**과 **Brand identity**만으로 제출하세요. 부적합 채널에 올라갑니다.
4. 3,000자짜리 설명을 붙여 넣으세요. 알림 이메일은 값을 500자에서 자르고, 대시보드는 전부 보여 줍니다.
5. 서버를 멈추고 제출하세요. **Webhooks**에 전달 실패 시도가 표시되고, 서버를 다시 시작해 **Retry**를 누르면 성공합니다.

## 주의할 점

- **체크된 것이 없음.** 빈 체크박스 그룹은 아무것도 보내지 않으므로 HTML `required`로 강제할 수 없습니다. Ikiform은 POST를 `Choose at least one option`으로 거부합니다. [리드 수집 가이드](https://ikiform.com/ko/guides/lead-capture.md#form)의 `fetch` 버전으로 그 오류를 페이지에 표시하세요.
- **옵션 이름 바꾸기.** 레이블은 자유롭게 바꾸세요. 값은 릴레이와 함께 바꿀 때만 바꾸세요. 그러지 않으면 문의가 엉뚱한 레인으로 갑니다.
- **이메일 속 긴 브리프.** 알림 이메일은 최대 20개 필드와 값마다 500자까지 보여 줍니다. 설명은 거기서 잘립니다. 워크스페이스 멤버는 이메일에서 전체 제출로 가는 링크를 받습니다.
- **거절.** Ikiform은 폼을 작성한 사람에게 아무것도 보내지 않으므로, 누군가 답장하지 않으면 부적합 리드는 아무 소식도 듣지 못합니다. 다른 곳을 추천하는 두 줄짜리 거절이 문을 열어 둡니다.

## 질문

### 프로젝트 문의 폼에서는 무엇을 물어야 하나요?

연락처, 필요한 서비스, 예산 범위, 시작일, 프로젝트 설명입니다. 선택형 질문 세 개로 누군가 설명을 읽기 전에 문의를 분류할 수 있습니다.

### 체크박스 답은 어떻게 저장되나요?

이름을 공유하는 체크박스는 하나의 목록으로 모입니다. multi_select 필드를 정의하면 Ikiform은 목록에 있는 값만 받아 배열로 저장하며, 웹훅도 그 배열을 담습니다.

### 문의 폼에서 예산을 필수로 해야 하나요?

프로젝트 문의라면 대개 그렇습니다. 적합 여부를 가장 잘 보여 주는 신호이고, 최소 계약 규모에서 시작하는 범위를 쓰면 자연스러운 질문이 됩니다. 내가 가격을 정하는 견적 요청이라면 선택으로 두세요.

### SEO에 관한 실제 문의가 왜 스팸으로 분류됐나요?

seo services 같은 문구는 스팸 신호로 집계되고 링크도 점수에 더해집니다. 24시간 안의 동일한 반복과 합쳐지면 임계값 5에 닿습니다. 스팸 아님으로 표시해 이메일과 웹훅을 보내고, 고객이 그 문구를 입력하지 않아도 되도록 검색 작업을 체크박스로 제공하세요.

## 가이드 더 보기

- [리드 수집 폼](https://ikiform.com/ko/guides/lead-capture.md): reply-to가 설정된 이메일을 보내고, 팀 채팅에 알리고, CRM에 넣는 내 사이트의 리드 수집 폼입니다. 리드가 아직 있을 때 답할 수 있습니다.
- [견적 요청 폼](https://ikiform.com/ko/guides/lead-capture/quote-request.md): 자료를 업로드 대신 공유 링크로 받고, 모든 요청에 기한을 붙이고, 링크가 많은 리드가 스팸으로 분류되지 않게 하는 견적 요청 폼입니다.
- [HubSpot이나 Pipedrive로 리드 보내기](https://ikiform.com/ko/guides/lead-capture/leads-to-crm.md): 서명된 Ikiform 웹훅을 HubSpot의 연락처 upsert나 Pipedrive의 person, lead 호출로 매핑하고, 전달이 재시도되어도 연락처가 중복되지 않게 합니다.
- [상담 신청 폼](https://ikiform.com/ko/guides/lead-capture/consultation-request.md): 회계사, 변호사, 코치, 클리닉을 위한 상담 신청 폼입니다. 연락 방법과 시간을 묻고, 민감한 내용은 통화에서 다룹니다.
- [데모 요청 폼](https://ikiform.com/ko/guides/lead-capture/demo-request.md): 리드를 저장한 뒤 이름과 이메일이 미리 채워진 Cal.com이나 Calendly로 방문자를 보내는 데모 예약 폼입니다.
- [Next.js 리드 폼](https://ikiform.com/ko/guides/lead-capture/nextjs.md): 서버 액션에서 Zod로 검증하고, 첫 유입 UTM을 쿠키에 보관하고, 자격을 갖춘 리드를 캘린더로 보내는 Next.js App Router 리드 폼입니다.
