Formulir lead capture5 menit baca
Buat formulir permintaan konsultasi untuk bisnis jasa
Formulir permintaan konsultasi untuk akuntan, pengacara, coach, dan klinik yang menanyakan cara dan waktu menghubungi, dan menyimpan detail sensitif untuk panggilannya.
Minta nama, email, cara menghubungi mereka, dan kira-kira kapan, plus topik umum, dan simpan setiap detail situasi mereka untuk panggilan. Catat zona waktu dari browser agar pagi yang diminta berarti pagi di tempat mereka. Terapkan aturan yang tidak bisa diungkapkan schema formulir, seperti nomor telepon hanya jika mereka memilih panggilan telepon, di kode Anda sendiri.
Apa yang berbeda dari permintaan konsultasi
Permintaan konsultasi datang dari orang yang punya masalah: surat pajak, sengketa sewa, bisnis yang ingin mereka mulai. Mereka ingin menjelaskannya, dan formulir mengundang mereka melakukannya. Itulah jebakannya. Apa pun yang mereka ketik disimpan bersama kiriman, ditampilkan di email notifikasi, dan dikirim lewat webhook ke mana pun Anda mengarahkannya. Nomor rekening dan detail kasus tidak pantas ada di tempat-tempat itu.
Jadi tugas formulir lebih sempit daripada kelihatannya: siapa mereka, bagaimana dan kapan menghubungi mereka, dan kira-kira tentang apa. Sisanya menunggu panggilan, tempat Anda mengendalikan apa yang dicatat dan di mana disimpan.
- Formulir menanyakan siapa mereka: nama dan email.
- Formulir menanyakan cara menghubungi mereka: email, telepon, atau video.
- Formulir menanyakan kapan: hari yang diinginkan, rentang waktu, dan zona waktu mereka.
- Formulir menanyakan topiknya, sebagai satu topik umum.
- Panggilan membahas situasi mereka dengan kata-kata mereka sendiri.
- Dokumen dikirim lewat saluran yang Anda pilih, bukan formulir.
- Nomor rekening, ID, dan catatan tidak pernah diketik di formulir.
- Panggilan diakhiri dengan jawaban Anda tentang kecocokan dan biaya.
Field-nya
Pratinjau
Menjalankan validator Ikiform di browser Anda. Tidak ada yang dikirim.
| Field | Mengapa bentuknya begini |
|---|---|
contact_method | radio, agar kontak pertama terjadi sesuai cara yang mereka minta |
phone | Opsional di schema, diwajibkan oleh kode Anda saat mereka memilih panggilan telepon |
preferred_date | Field date menyimpan YYYY-MM-DD. min di schema-nya tetap, jadi halaman mengatur besok sebagai min di browser |
timezone | Diisi dari browser dengan Intl.DateTimeFormat().resolvedOptions().timeZone, seperti Europe/Berlin |
note | Dibatasi 500 karakter. Email notifikasi menampilkan hingga 500 karakter per nilai, jadi seluruh catatan muat |
Aturan bersyarat di kode Anda sendiri
Ikiform memvalidasi setiap field secara terpisah. Aturan yang bergantung pada field lain, seperti telepon hanya untuk panggilan telepon, berjalan di kode Anda. Versi server bisa benar-benar menegakkannya; versi browser memandu pengunjung dan tidak mengirim apa pun yang tidak perlu.
Script mengatur required pada input telepon saat Phone call dipilih, mengatur besok sebagai hari paling awal di kalender pengunjung sendiri, dan mengisi zona waktu tersembunyi.
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST" id="consult">
<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 />
<fieldset>
<legend>How should we reach you?</legend>
<label><input type="radio" name="contact_method" value="email" checked /> Email</label>
<label><input type="radio" name="contact_method" value="phone" /> Phone call</label>
<label><input type="radio" name="contact_method" value="video" /> Video call</label>
</fieldset>
<label for="phone">Phone</label>
<input id="phone" name="phone" type="tel" autocomplete="tel" />
<label for="topic">What is it about?</label>
<select id="topic" name="topic" required>
<option value="new_business">Starting a business</option>
<option value="tax">Tax and bookkeeping</option>
<option value="other">Something else</option>
</select>
<label for="preferred_date">Preferred day</label>
<input id="preferred_date" name="preferred_date" type="date" />
<fieldset>
<legend>Best time</legend>
<label><input type="radio" name="time_window" value="morning" /> Morning</label>
<label><input type="radio" name="time_window" value="afternoon" /> Afternoon</label>
<label><input type="radio" name="time_window" value="evening" /> Evening</label>
</fieldset>
<input type="hidden" name="timezone" />
<label for="note">Anything we should know first?</label>
<textarea id="note" name="note" rows="3" maxlength="500"></textarea>
<p>A sentence or two. Please don't include account numbers or personal records.</p>
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Request a consultation</button>
</form>
<script>
const form = document.getElementById("consult");
const phone = form.elements.phone;
const tomorrow = new Date(Date.now() + 86400000);
const local = new Date(tomorrow.getTime() - tomorrow.getTimezoneOffset() * 60000);
form.elements.timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
form.elements.preferred_date.min = local.toISOString().slice(0, 10);
form.addEventListener("change", () => {
phone.required = form.elements.contact_method.value === "phone";
});
</script>Server action menolak panggilan telepon tanpa nomor sebelum mengirim, menghapus nomor yang sempat diketik lalu ditinggalkan, dan me-redirect ke halaman terima kasih. Render formulir dengan useActionState dan tampilkan state.error.
"use server";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
const KEYS = ["name", "email", "contact_method", "phone", "topic", "preferred_date", "time_window", "timezone", "note", "_gotcha"];
export async function requestConsultation(_previous: Record<string, string>, formData: FormData) {
const values = Object.fromEntries(KEYS.map((key) => [key, String(formData.get(key) ?? "").trim()]));
if (values.contact_method === "phone" && !values.phone) {
return { ...values, error: "Add a phone number, or choose email or video." };
}
if (values.contact_method !== "phone") values.phone = "";
const origin = (await headers()).get("origin");
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
...(origin ? { Origin: origin } : {}),
},
body: JSON.stringify(values),
});
const result = await response.json().catch(() => null);
if (!result?.ok) {
const fields: Record<string, string[]> = result?.error?.fields ?? {};
const [field, messages] = Object.entries(fields)[0] ?? [];
return { ...values, error: field ? `${field}: ${messages?.[0]}` : (result?.error?.message ?? "Couldn't send. Try again.") };
}
redirect("/consultation/thanks");
}Input telepon hanya ada saat panggilan telepon dipilih, dan nilai kosong dibuang sebelum kiriman, jadi field opsional tiba dalam keadaan tidak ada alih-alih kosong.
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";
const request = reactive({
name: "",
email: "",
contact_method: "email",
phone: "",
topic: "new_business",
preferred_date: "",
time_window: "",
note: "",
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});
const status = ref<"idle" | "sending" | "sent">("idle");
const error = ref("");
const wantsCall = computed(() => request.contact_method === "phone");
async function send() {
status.value = "sending";
error.value = "";
const body = Object.fromEntries(Object.entries(request).filter(([, value]) => value !== ""));
if (!wantsCall.value) delete body.phone;
try {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { Accept: "application/json", "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const result = await response.json();
if (result.ok) {
status.value = "sent";
return;
}
const first = Object.entries(result.error.fields ?? {})[0] as [string, string[]] | undefined;
error.value = first ? `${first[0]}: ${first[1][0]}` : result.error.message;
} catch {
error.value = "Couldn't reach us. Try again, or call the office.";
}
status.value = "idle";
}
</script>
<template>
<p v-if="status === 'sent'" role="status">Thanks, {{ request.name }}. We'll contact you by {{ request.contact_method }} within one working day.</p>
<form v-else @submit.prevent="send">
<label for="name">Name</label>
<input id="name" v-model="request.name" autocomplete="name" required />
<label for="email">Email</label>
<input id="email" v-model="request.email" type="email" autocomplete="email" required />
<fieldset>
<legend>How should we reach you?</legend>
<label><input v-model="request.contact_method" type="radio" value="email" /> Email</label>
<label><input v-model="request.contact_method" type="radio" value="phone" /> Phone call</label>
<label><input v-model="request.contact_method" type="radio" value="video" /> Video call</label>
</fieldset>
<template v-if="wantsCall">
<label for="phone">Phone</label>
<input id="phone" v-model="request.phone" type="tel" autocomplete="tel" required />
</template>
<label for="topic">What is it about?</label>
<select id="topic" v-model="request.topic">
<option value="new_business">Starting a business</option>
<option value="tax">Tax and bookkeeping</option>
<option value="other">Something else</option>
</select>
<label for="preferred_date">Preferred day</label>
<input id="preferred_date" v-model="request.preferred_date" type="date" />
<label for="note">Anything we should know first?</label>
<textarea id="note" v-model="request.note" rows="3" maxlength="500" />
<p v-if="error" role="alert">{{ error }}</p>
<button :disabled="status === 'sending'">{{ status === "sending" ? "Sending" : "Request a consultation" }}</button>
</form>
</template>Versi sisi server mengirim dengan secret key, jadi Ikiform melewati Allowed domains dan pemeriksaan manusianya. Setiap kiriman kemudian berasal dari IP server Anda, dan batas Standard 5 per menit per pengunjung menghitung semua pengunjung Anda bersama-sama. Pilih rate limit Relaxed atau Custom untuk formulir yang Anda kirim lewat server, dan simpan IKIFORM_SECRET_KEY di environment variable khusus server. Versi server meneruskan header Origin pengunjung, karena fetch di sisi server tidak mengirimnya dan Ikiform mendeteksi uji localhost gratis dari header itu.
Jauhkan detail sensitif dari formulir
- Sebutkan di teks bantuan, seperti formulir di atas. Orang mengikuti instruksi yang jelas lebih sering dari yang Anda kira.
- Jangan tambahkan field untuk nomor identitas, nomor rekening, atau detail medis. Jika profesi Anda punya aturan tentang data klien, periksa dulu sebelum mengumpulkan apa pun selain detail kontak, dan kumpulkan sisanya lewat saluran yang memenuhi aturan itu.
- Nama field juga penting. Ikiform menahan email notifikasi untuk ditinjau saat nama field terlihat seperti kredensial, termasuk nama yang mengandung
password,ssn,cvv, atauseed. Field bernamaseed_rounddi formulir konsultan startup memicu hal ini. Beri namafunding_stage. - Arahkan webhook hanya ke tempat yang seluruh anggotanya boleh membaca nama klien, seperti channel privat, bukan channel seluruh perusahaan.
Balasan pertama
Email notifikasi tiba dengan Reply mengarah ke pengunjung, jadi balasan pertama bisa langsung dari inbox Anda. Buat balasan itu melakukan satu tugas: memastikan waktu. Dua slot konkret di dalam rentang waktu mereka lebih mudah disetujui daripada pertanyaan tentang kapan mereka bisa.
- Konversikan rentang waktu mereka ke waktu Anda sebelum menulis.
morningdiAustralia/Sydneyadalah malam sebelumnya di London. - Tawarkan dua slot dalam zona waktu mereka, dan sebutkan zonanya di email, agar tidak ada yang datang satu jam lebih awal.
- Sebutkan cara Anda akan menghubungi: nomor yang mereka berikan, atau tautan video yang Anda kirim bersama undangan.
- Jika topiknya di luar bidang Anda, katakan di balasan pertama dan sarankan orang yang menanganinya. Balasan itu tetap layak dikirim.
Pastikan berfungsi
- Pilih Phone call, biarkan telepon kosong, lalu kirim. Browser atau server Anda menghentikannya dengan pesan.
- Pilih Email, kirim, lalu buka kirimannya. Tidak ada nilai
phonedan ada zona waktu Anda, sepertiAmerica/Chicago. - Coba pilih hari ini sebagai hari yang diinginkan. Pemilih tanggal tidak menawarkannya.
- Periksa email notifikasi. Catatan tampil lengkap, dan Reply menulis ke pengunjung.
- Daftar ID field Anda dan pastikan tidak ada yang mengandung
password,ssn,cvv, atauseed.
Hal yang sering menjebak
- Tanggal tanpa zona waktu.
preferred_dateadalah hari kalender, bukan momen. Baca bersamatimezonedantime_windowsebelum Anda menelepon. - Radio tanpa pilihan. Grup radio yang tidak dicentang tidak mengirim apa pun. Itu tidak masalah untuk
time_window, yang opsional, dan itulah alasancontact_methoddimulai dengan Email tercentang. - Tidak ada konfirmasi booking. Ikiform tidak mengirim email ke orang yang mengisi formulir. Balasan pertama Anda yang memastikan waktunya. Jika Anda ingin orang memilih slot sendiri, serahkan ke penjadwal seperti di Formulir permintaan demo.
- Format telepon. Field telepon menerima 6 sampai 15 digit sesuai yang diketik. Aktifkan
e164jika Anda menelepon dari sistem yang butuh+14155550123, dan nomor tanpa kode negara kemudian ditolak.
Pertanyaan
Nama, email, cara mereka ingin dihubungi, hari dan waktu yang diinginkan, dan topik umum. Simpan situasi lengkap mereka, dokumen, dan nomor apa pun untuk konsultasinya sendiri.
Tidak di definisi field, yang memvalidasi setiap field secara terpisah. Atur required pada input di browser saat telepon dipilih, dan periksa di kode server Anda sebelum mengirim.
Isi field timezone tersembunyi dari Intl.DateTimeFormat().resolvedOptions().timeZone di browser. Field itu menyimpan nama IANA seperti Europe/London di samping hari dan rentang waktu yang diminta.
Tidak. Ikiform mengirim email ke Anda dan tim, bukan ke pengirim formulir, jadi tidak ada autoresponder atau email konfirmasi. Kirim webhook ke alat email Anda dan kirim konfirmasinya dari sana.
Mentok di salah satu langkah, atau ingin panduan untuk situasi Anda? Email hello@ikiform.com.