폼을 이메일로 보내기4분 분량
폼 하나를 여러 이메일 주소로 보내기
폼이 직접 수신자를 고르게 하지 않고도, 폼 하나를 팀 전체에 이메일로 보내거나 주제별로 메시지를 적절한 사람에게 보내세요.
대시보드에서 각 사람을 폼의 수신자로 추가하세요. 워크스페이스 멤버는 체크하고, 그 밖의 사람은 확인 링크로 추가하며, 최대 10개 주소까지 가능합니다. 모두가 모든 제출의 개별 사본을 받습니다. 영업 문의는 한 사람에게, 지원 문의는 다른 사람에게 보내려면 대신 웹훅에서 필드로 라우팅하세요.
이 경우가 다른 점
예전 PHP 스크립트와 일부 폼 서비스는 숨김 to나 cc 필드로 폼이 수신자를 직접 지정하게 했습니다. 편리하지만, 문의 폼이 스팸 릴레이가 되는 원인이기도 합니다. 봇이 숨김 필드를 바꾸면 서버가 여러분의 도메인에서 원하는 누구에게나 메일을 보내기 때문입니다. Ikiform에는 수신자 필드가 없습니다. 누가 이메일을 받을지는 대시보드에서 정하고, 워크스페이스 외부 사람은 먼저 확인해야 합니다.
그래서 서로 다른 두 가지 일이 남고, 각각 다른 곳에서 해결합니다.
| 원하는 것 | 처리하는 곳 | 한도 |
|---|---|---|
| 모두가 모든 제출을 받음 | 폼의 Notifications 설정에 있는 수신자 | 폼당 주소 10개 |
| 각 제출이 적절한 사람에게 감 | 필드를 읽어 한 팀에 이메일을 보내는 웹훅 | 워크스페이스당 웹훅 엔드포인트 3개 |
모든 제출을 팀 전체에 보내기
- 폼이 Ikiform으로 한 번 POST합니다.
- Ikiform이 수신자 목록을 만듭니다. 이 폼에 체크한 멤버 또는 워크스페이스 기본값에, 확인을 마친 외부 주소를 더합니다.
- 중복은 제거되고 목록은 10개에서 멈춥니다.
- 수신자마다 개별 이메일을 받으므로 서로의 주소를 볼 수 없습니다.
- 아직 확인을 기다리는 주소와 반송 후 일시 중지된 주소는 건너뜁니다.
- 대시보드에서 폼을 연 뒤 Settings와 Notifications로 이동하세요. Email new submissions가 켜져 있는지 확인하세요.
- 이메일을 받아야 할 워크스페이스 멤버를 각각 체크하세요. 주소가 인증된 멤버만 목록에 표시됩니다.
- 워크스페이스 외부 사람은 Add someone outside the workspace에 주소를 입력하고 Send link를 누르세요. 폼당 5명까지 추가할 수 있습니다.
- 상대에게 확인을 요청하세요. 확인하기 전까지 해당 행에는 Waiting for confirmation이 표시되고 아무것도 받지 않습니다.
외부 수신자는 제출의 필드와 수신 거부 링크를 봅니다. 계정이 없으므로 대시보드 링크는 받지 않습니다. 멤버에게는 View submission 버튼이 표시됩니다. 누군가 떠나면 해당 행을 삭제하세요. 수신을 거부하면 행도 함께 사라집니다.
멤버와 외부 주소
두 종류의 수신자는 다르게 동작하므로, 누구를 어디에 추가할지 정할 때 중요합니다.
| Item | 워크스페이스 멤버 | 외부 주소 |
|---|---|---|
| 추가 방법 | 폼에서 체크 | Send link 후 상대가 확인 |
| 이메일에서 보이는 것 | 필드와 View submission 버튼 | 필드와 수신 거부 링크 |
| 대시보드를 열 수 있음 | 예 | 아니요 |
| 이메일이 멈추는 때 | 체크를 해제하거나 워크스페이스를 떠날 때 | 행을 삭제하거나 수신을 거부할 때 |
| 포함되는 한도 | 주소 10개 한도 | 주소 10개 한도와 폼당 외부 주소 5개 |
메시지에 답하는 사람에게는 워크스페이스 자리를 주어 지난 제출과 스팸을 볼 수 있게 하세요. 관리자나 공유 받은편지함처럼 알기만 하면 되는 사람에게는 외부 주소를 쓰세요.
주제별로 각 제출 라우팅하기
수신자는 방문자가 고른 값에 따라 바뀔 수 없습니다. 그러려면 팀을 지정하는 필드를 추가하고, 작은 웹훅 핸들러가 주소를 고르게 하세요. 폼은 이렇습니다.
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<label for="department">Who should read this?</label>
<select id="department" name="department" required>
<option value="sales">Sales</option>
<option value="support">Support</option>
<option value="billing">Billing</option>
</select>
<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="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Send</button>
</form>대시보드에서 department를 select 필드로 정의하면 POST에는 sales, support, billing만 담길 수 있습니다. 다른 값을 보내는 봇은 422 validation_failed를 받고 핸들러에 도달하지 못합니다.
그다음은 핸들러입니다. 서명을 검증하고, department로 팀을 고르고, 여러분의 이메일 제공업체(여기서는 Resend)를 통해 보냅니다. 이메일을 직접 보내므로 제목도 정할 수 있습니다.
import { Resend } from "resend";
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
const resend = new Resend(process.env.RESEND_API_KEY);
const TEAMS: Record<string, string[]> = {
sales: ["sales@example.com"],
support: ["support@example.com", "oncall@example.com"],
billing: ["accounts@example.com"],
};
export async function POST(request: Request) {
const payload = await request.text();
try {
const event = await verifyWebhook({
payload,
headers: request.headers,
secret: process.env.IKIFORM_WEBHOOK_SECRET!,
});
if (event.type !== "submission.created") return new Response(null, { status: 204 });
const fields = event.data.fields;
const department = String(fields.department ?? "support");
const { error } = await resend.emails.send(
{
from: "Website <forms@example.com>",
to: TEAMS[department] ?? TEAMS.support,
replyTo: typeof fields.email === "string" ? fields.email : undefined,
subject: `[${department}] ${String(fields.name ?? "New message")}`,
text: Object.entries(fields)
.map(([key, value]) => `${key}: ${String(value)}`)
.join("\n"),
},
{ idempotencyKey: event.id },
);
if (error) return new Response(error.message, { status: 502 });
return new Response(null, { status: 204 });
} catch (error) {
if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
throw error;
}
}- 주소는 폼이 아니라 코드에 있습니다. 방문자는 팀 이름을 고를 뿐 주소를 고르지 않습니다.
replyTo는 Ikiform 자체 이메일처럼 Reply가 방문자를 가리키게 합니다.idempotencyKey: event.id는 재시도된 전달이 새로 아무것도 보내지 않게 합니다. Ikiform은2xx를 받지 못한 전달을 같은 ID로 재시도합니다.- 제공업체가 실패했을 때
502를 반환하면 Ikiform이 나중에 재시도하므로, 잠깐의 장애로 메시지를 잃지 않습니다. - 알 수 없거나 비어 있는 부서는 메시지를 버리지 않고 지원팀으로 보냅니다.
대시보드의 Webhooks에 URL을 추가하고 whsec_ 시크릿을 IKIFORM_WEBHOOK_SECRET에 복사하세요. 재시도와 페이로드는 웹훅 문서에서 다룹니다. 코드 없이 하고 싶다면 부서마다 필터를 둔 Zapier나 n8n 플로로도 같은 일을 할 수 있습니다.
Ikiform 자체 이메일은 한 사람에게 예비용으로 켜 두거나, 아무도 같은 메시지를 두 번 받지 않도록 Email new submissions를 끄세요.
동작 확인하기
- 폼의 Notifications 설정에서 모든 수신자 행을 확인하세요. Waiting for confirmation이 표시된 행이 없어야 합니다.
- 페이지에서 폼을 작성해 제출하세요. 여러분의 감사 페이지나 Ikiform의 호스팅 감사 페이지로 이동합니다.
- 수신자마다 자기 이메일을 받습니다. 그중 한 명에게 To 줄을 확인해 달라고 하세요. 자기 주소만 표시되어 있습니다.
- 웹훅으로 라우팅한다면 부서마다 한 번씩 제출하고 Webhooks의 전달 로그를 확인하세요. 모두
succeeded로 표시됩니다. - 허니팟을 채워서
_gotcha=bot으로 POST하세요. 성공 응답은 받지만 제출은 스팸으로 저장되고 이메일도 웹훅도 보내지 않습니다.
주의할 점
- CC나 BCC 없음. 수신자마다 개별 메시지를 받으므로 공유 스레드가 없습니다. 누가 답할지 정하거나 주제별로 라우팅하세요.
- 한도는 수신자가 아니라 제출 수를 셉니다. 한 폼에 시간당 제출이 50건을 넘으면 모든 수신자가 대신 다이제스트를 받습니다.
- 멤버 변경. 누군가 워크스페이스를 떠나면 즉시 이메일을 받지 않게 됩니다. 팀 구성이 바뀐 뒤에는 폼이 아무에게도 이메일을 보내지 않는 상태가 되지 않도록 폼의 수신자를 확인하세요.
- 반송된 주소는 일시 중지됩니다. 해당 행에 날짜와 함께 Emails bounced가 표시되고, 그 이후에 이메일이 재개됩니다. 주소를 고치거나 삭제하세요.
- 방문자에게는 아무것도 가지 않습니다. Ikiform은 폼을 작성한 사람에게 이메일을 보내지 않습니다. 알림 이메일은 여러분과 팀에게만 가므로 자동 응답, 확인 이메일, 더블 옵트인은 없습니다. 이런 메일은 웹훅으로 트리거해 이메일 도구에서 보내세요.
질문
Ikiform은 한동안 그 주소로의 이메일을 멈추고, 폼 설정의 해당 행에 Emails bounced를 표시합니다. 다른 수신자는 영향을 받지 않습니다. 주소를 고치거나 삭제하세요.
최대 10개입니다. 체크한 워크스페이스 멤버와, 각각 먼저 링크로 확인하는 외부 주소 최대 5개를 포함합니다.
아니요, 의도적으로 그렇게 했습니다. 수신자 필드가 있으면 누구나 폼을 이용해 아무 주소로나 메일을 보낼 수 있습니다. 대신 웹훅에서 select 필드로 라우팅하고, 주소는 여러분의 코드에 두세요.
아니요. 모든 수신자는 자기에게만 보낸 개별 이메일을 받습니다.
네, 웹훅으로 가능합니다. 폼에 부서 select를 추가하고, 작은 핸들러나 자동화 도구가 주소를 골라 이메일을 보내게 하세요.
막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.