리드 수집 폼4분 분량
에이전시나 프리랜서용 프로젝트 문의 폼 만들기
누군가 브리프를 읽기 전에 예산과 시기로 문의를 유력, 관심, 부적합으로 분류하는 스튜디오와 프리랜서용 프로젝트 의뢰 폼입니다.
서비스는 체크박스로, 예산과 시작일은 선택형으로, 설명은 적합 여부를 판단할 만큼 길게 받으세요. 각 문의를 웹훅으로 작은 릴레이에 보내 예산과 시기에 따라 유력, 관심, 부적합 레인으로 분류하세요. 그런 다음 유력 레인에는 당일 답하고, 부적합 레인에는 하루 안에 정중하게 거절하세요. 두 답장 모두 평판을 쌓습니다.
프로젝트 문의가 다른 점
견적 요청은 정해진 작업의 가격을 묻습니다. 프로젝트 문의는 더 큰 질문을 합니다. 우리가 서로 맞을까? 방문자는 아직 프로젝트를 구상 중인 경우가 많고, 나는 디스커버리 콜을 쓸지 정하는 중입니다. 스튜디오나 프리랜서에게 부족한 것은 리드가 아니라, 성사될 리 없던 브리프를 읽는 데 쓰는 파트너의 시간입니다.
그래서 이 폼은 읽기 전에 분류할 수 있을 만큼의 구조를 받습니다. 세 가지 답이면 됩니다. 무엇이 필요한지, 대략 얼마를 쓸 수 있는지, 언제인지입니다. 이야기하고 싶은 모든 것은 긴 설명 하나에 담기고, 구조화된 답이 읽을 가치가 있다고 할 때만 전부 읽습니다.
- 문의가 서비스, 예산, 시작일이 담긴 서명된 웹훅으로 도착합니다.
- 릴레이가 서명을 확인하고 예산과 시기에 점수를 매깁니다.
- 점수를 확인합니다.
- 3개월 안에 시작하는 $30,000 이상의 예산은 유력 레인으로 갑니다.
- 예산이 더 낮거나 시작이 늦으면 관심 레인으로 갑니다.
- 웹사이트 외의 작업에 $10,000 미만이면 부적합 레인으로 가서 빠르고 친절하게 거절합니다.
필드
주제 하나만 묻는 가벼운 폼이라면 문의 폼 템플릿에서 시작하세요. 이 폼은 분류용 질문 세 개를 추가합니다. 미리보기에서 서비스 여러 개를 체크해 목록으로 저장되는 것을 확인하세요.
미리보기
Ikiform의 검증기를 브라우저에서 실행합니다. 아무것도 전송되지 않습니다.
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은 반복된 값을 하나의 목록으로 모읍니다. 도구가 기대한다면 이름 끝에 []를 붙여도 됩니다.
<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에 넘깁니다.
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짜리 문의 하나를 잃는 것이 스팸 열 개를 읽는 것보다 비쌉니다.
작동 확인하기
- 서비스 두 개를 체크해 제출하세요. 제출에
services가 항목 두 개짜리 목록으로 저장됩니다. - Over $75,000과 This month로 제출하세요. 요약이 유력 채널에 올라갑니다.
- Under $10,000과 Brand identity만으로 제출하세요. 부적합 채널에 올라갑니다.
- 3,000자짜리 설명을 붙여 넣으세요. 알림 이메일은 값을 500자에서 자르고, 대시보드는 전부 보여 줍니다.
- 서버를 멈추고 제출하세요. Webhooks에 전달 실패 시도가 표시되고, 서버를 다시 시작해 Retry를 누르면 성공합니다.
주의할 점
- 체크된 것이 없음. 빈 체크박스 그룹은 아무것도 보내지 않으므로 HTML
required로 강제할 수 없습니다. Ikiform은 POST를Choose at least one option으로 거부합니다. 리드 수집 가이드의fetch버전으로 그 오류를 페이지에 표시하세요. - 옵션 이름 바꾸기. 레이블은 자유롭게 바꾸세요. 값은 릴레이와 함께 바꿀 때만 바꾸세요. 그러지 않으면 문의가 엉뚱한 레인으로 갑니다.
- 이메일 속 긴 브리프. 알림 이메일은 최대 20개 필드와 값마다 500자까지 보여 줍니다. 설명은 거기서 잘립니다. 워크스페이스 멤버는 이메일에서 전체 제출로 가는 링크를 받습니다.
- 거절. Ikiform은 폼을 작성한 사람에게 아무것도 보내지 않으므로, 누군가 답장하지 않으면 부적합 리드는 아무 소식도 듣지 못합니다. 다른 곳을 추천하는 두 줄짜리 거절이 문을 열어 둡니다.
질문
연락처, 필요한 서비스, 예산 범위, 시작일, 프로젝트 설명입니다. 선택형 질문 세 개로 누군가 설명을 읽기 전에 문의를 분류할 수 있습니다.
이름을 공유하는 체크박스는 하나의 목록으로 모입니다. multi_select 필드를 정의하면 Ikiform은 목록에 있는 값만 받아 배열로 저장하며, 웹훅도 그 배열을 담습니다.
프로젝트 문의라면 대개 그렇습니다. 적합 여부를 가장 잘 보여 주는 신호이고, 최소 계약 규모에서 시작하는 범위를 쓰면 자연스러운 질문이 됩니다. 내가 가격을 정하는 견적 요청이라면 선택으로 두세요.
seo services 같은 문구는 스팸 신호로 집계되고 링크도 점수에 더해집니다. 24시간 안의 동일한 반복과 합쳐지면 임계값 5에 닿습니다. 스팸 아님으로 표시해 이메일과 웹훅을 보내고, 고객이 그 문구를 입력하지 않아도 되도록 검색 작업을 체크박스로 제공하세요.
막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.