리드 수집 폼

리드 수집 폼4분 분량

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

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

Preet SutharIkiform 창업자

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

프로젝트 문의가 다른 점

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

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

릴레이가 프로젝트 문의를 레인으로 분류하는 방식문의서비스, 예산, 시작일릴레이확인, 점수 매기기예산과 시작일?유력 레인$30k 이상, 곧 시작관심 레인이틀 안에 답장부적합 레인정중한 거절웹훅높음낮거나 늦음릴레이가 프로젝트 문의를 레인으로 분류하는 방식문의서비스, 예산, 시작일릴레이확인, 점수 매기기예산과 시작일?유력 레인$30k 이상, 곧 시작관심 레인이틀 안에 답장부적합 레인정중한 거절웹훅높음낮거나 늦음
  1. 문의가 서비스, 예산, 시작일이 담긴 서명된 웹훅으로 도착합니다.
  2. 릴레이가 서명을 확인하고 예산과 시기에 점수를 매깁니다.
  3. 점수를 확인합니다.
  4. 3개월 안에 시작하는 $30,000 이상의 예산은 유력 레인으로 갑니다.
  5. 예산이 더 낮거나 시작이 늦으면 관심 레인으로 갑니다.
  6. 웹사이트 외의 작업에 $10,000 미만이면 부적합 레인으로 가서 빠르고 친절하게 거절합니다.
레인은 채팅 채널입니다. 자동으로 거절되는 사람은 없습니다. 모든 레인에 여전히 사람이 답하며, 순서만 다릅니다.

필드

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

미리보기

Ikiform의 검증기를 브라우저에서 실행합니다. 아무것도 전송되지 않습니다.

무엇이 필요하신가요?
언제 시작해야 하나요?

목표, 대상, 그리고 성공의 모습.

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

폼

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

start-a-project.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
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, Make, n8n의 라우터 단계로 같은 분기를 만들 수 있습니다.

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

스팸 점수에는 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으로 거부합니다. 리드 수집 가이드의 fetch 버전으로 그 오류를 페이지에 표시하세요.
  • 옵션 이름 바꾸기. 레이블은 자유롭게 바꾸세요. 값은 릴레이와 함께 바꿀 때만 바꾸세요. 그러지 않으면 문의가 엉뚱한 레인으로 갑니다.
  • 이메일 속 긴 브리프. 알림 이메일은 최대 20개 필드와 값마다 500자까지 보여 줍니다. 설명은 거기서 잘립니다. 워크스페이스 멤버는 이메일에서 전체 제출로 가는 링크를 받습니다.
  • 거절. Ikiform은 폼을 작성한 사람에게 아무것도 보내지 않으므로, 누군가 답장하지 않으면 부적합 리드는 아무 소식도 듣지 못합니다. 다른 곳을 추천하는 두 줄짜리 거절이 문을 열어 둡니다.

질문

막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.