İki yöntem kısaca
- Yöntem 1: form → Apps Script. Form verileri doğrudan tablonuzdaki küçük bir betiğe gönderir. Başka bir şey gerekmez. Satırlar gelir, ama bildirim yoktur, kendi yazdığınız dışında spam filtresi yoktur ve form JavaScript ister.
- Yöntem 2: form → form backend'i → Apps Script. Form bir backend'e gider. Backend spamı ayıklar, size bildirim gönderir ve her gerçek başvuruyu webhook ile betiğinize iletir.
Yalnızca bir liste lazımsa yöntem 1 ile başlayın. Başvurulara hızlı yanıt vermeniz gerekiyorsa yöntem 2'yi seçin.
Yöntem 1: formu Apps Script'e gönderin
Menü adları Google'ın İngilizce arayüzündeki gibi yazıldı. Türkçe arayüzde çevrilmiş hâlleri aynı yerlerde durur.
- Bir tablo oluşturun, sayfaya
Başvurularadını verin ve 1. satıra şunları yazın: Tarih, Ad, E-posta, Mesaj. - Extensions → Apps Script menüsünü açın, örnek kodu silin ve aşağıdaki betiği yapıştırın.
- Deploy → New deployment'a tıklayın ve Web app'i seçin. Execute as için Me, Who has access için Anyone seçin.
- İzinleri onaylayın ve web uygulamasının adresini kopyalayın. Adres
/execile biter. - Bu adresi bir sonraki kutudaki form koduna yazın ve sayfayı yayınlayın.
Betik, bal küpü alanı dolu gelen başvuruları atlar. Ayrıca kilit alır, böylece aynı anda gelen iki başvuru birbirinin üzerine yazılmaz. clean() adımı, =IMPORTXML(…) gibi bir değerin tablonuzda formül olarak çalışmasını engeller.
// Tablonuzda: Extensions > Apps Script. "Başvurular" sayfasının 1. satırında sütun adları var.
function doPost(e) {
const p = e.parameter;
if (p.botcheck) return json({ ok: true }); // bal küpü alanı: botlar doldurur, insanlar görmez
const lock = LockService.getScriptLock();
lock.waitLock(20000); // aynı anda tek yazma, satırlar birbirinin üzerine yazılmasın
try {
SpreadsheetApp.getActive().getSheetByName("Başvurular").appendRow([
new Date(), clean(p.name), clean(p.email), clean(p.message),
]);
} finally {
lock.releaseLock();
}
return json({ ok: true });
}
// = + - veya @ ile başlayan bir değer formül olarak çalışırdı. Tırnak işareti onu metin olarak tutar.
function clean(value) {
const text = String(value || "");
return /^[=+\-@]/.test(text) ? "'" + text : text;
}
function json(data) {
return ContentService.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}Web uygulamasına veri gönderen HTML form
<form id="lead-form">
<label>Ad <input type="text" name="name" required autocomplete="name"></label>
<label>E-posta <input type="email" name="email" required autocomplete="email"></label>
<label>Mesaj <textarea name="message" required></textarea></label>
<div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
<input type="text" name="botcheck" tabindex="-1" autocomplete="off">
</div>
<button type="submit">Gönder</button>
<p id="lead-status" role="status"></p>
</form>
<script>
const form = document.getElementById("lead-form");
const status = document.getElementById("lead-status");
form.addEventListener("submit", async (event) => {
event.preventDefault();
status.textContent = "Gönderiliyor…";
try {
// Form biçimindeki gövde isteği basit tutar, tarayıcı preflight göndermez.
const response = await fetch("https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec", {
method: "POST",
body: new URLSearchParams(new FormData(form)),
});
if (!response.ok) throw new Error(String(response.status));
form.reset();
status.textContent = "Teşekkürler, mesajınız ulaştı.";
} catch {
status.textContent = "Gönderilemedi. Lütfen tekrar deneyin ya da bize e-posta gönderin.";
}
});
</script>Yöntem 1'in yapmadıkları
- Bildirim yok. Satırlar sessizce eklenir. Yeni başvuruyu görmek için tabloyu açmanız gerekir.
- Zayıf spam koruması. Adres herkese açıktır, bulan herkes veri gönderebilir. Bal küpü alanı yalnızca basit botları durdurur.
- JavaScript gerekir. Form
fetchkullanır, çünkü normal gönderim ziyaretçiyi bir Google sayfasında bırakır. - Değişiklik yeni sürüm demektir. Betiği düzenledikten sonra Manage deployments'ı açın, dağıtımı düzenleyin ve yeni sürüm seçin. Adres aynı kalır.
- Kotalar. Google, Apps Script'i kullanıcı başına sınırlar: örneğin aynı anda 30 çalıştırma ve çalıştırma başına 6 dakika (05.10.2026 itibarıyla). Küçük bir site bunu fark etmez.
Yöntem 2: form backend'i webhook'u ile tablonuza
Burada form, verileri Formgong'a gönderen sıradan bir HTML formudur. JavaScript olmadan çalışır ve bir teşekkür sayfası gösterir. Formgong spamı ayıklar, başvuruyu e-postaya veya Telegram'a gönderir, sonra JSON'u betiğinize iletir. Ücretsiz plan form başına bir webhook içerir.
- Tabloyu ve şu sütunlarla
Başvurularsayfasını oluşturun: ID, Tarih, Ad, E-posta, Mesaj. - Aşağıdaki webhook betiğini yapıştırın,
SECRET'ı değiştirin ve yöntem 1'deki gibi web uygulaması olarak yayınlayın. - Formgong'da form ayarlarını açın ve bir webhook ekleyin. Web uygulaması adresinin sonuna
?key=ve gizli anahtarınızı ekleyin. - Paneldeki webhook testine tıklayın, ardından sitenizden gerçek bir başvuru gönderin.
Formgong webhook'u için Apps Script
// Formgong'daki webhook URL'si: web uygulaması adresi + "?key=" + aynı SECRET.
// Apps Script istek başlıklarını okuyamaz, bu yüzden adresteki anahtar imza kontrolünün yerini alır.
const SECRET = "change-me";
function doPost(e) {
if (e.parameter.key !== SECRET) return reply();
const body = JSON.parse(e.postData.contents);
if (body.event !== "submission.created") return reply(); // panelden yapılan test "webhook.test" olayını gönderir
const lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
const sheet = SpreadsheetApp.getActive().getSheetByName("Başvurular");
const id = body.submission.id;
// Yeniden deneme aynı id ile gelir: satır zaten varsa atla.
if (!sheet.getRange("A:A").createTextFinder(id).matchEntireCell(true).findNext()) {
const f = body.submission.fields;
sheet.appendRow([id, body.submission.created_at, clean(f.name), clean(f.email), clean(f.message)]);
}
} finally {
lock.releaseLock();
}
return reply();
}
function clean(value) {
const text = String(value || "");
return /^[=+\-@]/.test(text) ? "'" + text : text;
}
// ContentService değil HtmlService: ContentService yönlendirmeyle yanıt verir,
// yönlendirmeyi izlemeyen bir webhook göndericisi bunu hata sayar.
function reply() {
return HtmlService.createHtmlOutput("ok");
}Yinelenen satırlara yol açan yönlendirme tuzağı
Google'a göre ContentService ile yanıt veren bir betik, script.googleusercontent.com üzerindeki tek kullanımlık bir adrese yönlendirme döndürür. Tarayıcı bu yönlendirmeyi izler, yani yöntem 1'de sorun yoktur. Yönlendirmeleri izlemeyen bir webhook göndericisi ise satır yazılmış olsa bile hata görür.
Formgong güvenlik nedeniyle webhook yönlendirmelerini izlemez. Yönlendirmeyi başarısız teslim sayar ve beşe kadar deneme yapar. Her deneme betiğinizi çalıştırır, yani aynı başvuru beş kez gelebilir. Webhook betiği bunu iki şekilde önler:
HtmlServiceile yanıt verir. Geliştiricilerin aktardığına göre bu çıktı yönlendirme olmadan doğrudan döner.- Başvuru ID'sini A sütununa yazar ve zaten var olan ID'leri atlar. Yeniden deneme olsa bile tek satır olur.
Kurulumdan sonra paneldeki webhook günlüğünü açın. Gerçek bir başvuru ilk denemede 200 durumunu göstermelidir.
Kodsuz yol: Make, n8n veya Zapier
Apps Script'e dokunmak istemiyorsanız webhook'u bir otomasyon aracına yönlendirin ve orada bir Google Sheets adımı ekleyin. Make ve kendi sunucunuzda çalışan n8n ücretsiz başlangıç sunar. Zapier de olur, ama webhook tetikleyicisi ücretli bir Zapier planı ister.
JSON gövdesi ve imzanın kendi sunucunuzda nasıl doğrulanacağı webhook belgelerinde anlatılıyor.
Sık sorulan sorular
HTML form kod yazmadan Google Sheets'e yazabilir mi?
Doğrudan hayır. Ya küçük bir Apps Script ya da arada bir araç gerekir, örneğin Make veya n8n ile bir form backend'i. Bu yazıdaki betik kopyalanacak yaklaşık 20 satırdır.
Gönderimden sonra neden bir Google sayfası açılıyor?
Form doğrudan web uygulamasına gönderiyor ve tarayıcı onun yanıtını gösteriyor. Formu yöntem 1'deki gibi fetch ile gönderin ya da sizi kendi teşekkür sayfanıza yönlendiren bir form backend'i kullanın.
Webhook neden yinelenen satırlar oluşturuyor?
Gönderici bir hata gördü ve yeniden denedi. Apps Script'te ContentService bir yönlendirmeyle yanıt verir ve bazı göndericiler bunu hata sayar. HtmlService ile yanıt verin ve tabloda zaten olan başvuru ID'lerini atlayın.
Web uygulamasını Anyone erişimine açmak güvenli mi?
Adresi bilen herkes veri gönderebilir, ama bu adres üzerinden tablonuzu kimse okuyamaz. Betiği kısa tutun, webhook'lar için gizli anahtarı kontrol edin ve yanıtta asla tablo verisi döndürmeyin.
Kaynaklar ve belgeler
Bu rehberin resmî kaynakları: Apps Script: web apps, Apps Script: Content service redirects, Apps Script: quotas, Apps Script: Lock service, Stack Overflow: HtmlService avoids the redirect, Zapier pricing. Formgong webhook'ları, Verilerin saklanması.
Yazıyı Markdown olarak okuİlgili rehberler
- Lovable formunu gerçek başvurular alacak şekilde bağlayın
- Statik sitenize sunucu kurmadan HTML iletişim formu ekleyin
- Web sitesi başvurularını Telegram'da karşılayın
- Form başarılı görünüyor ama e-posta gelmiyor: nerede duruyor?
- CRM olmadan web sitesi leadlerini Telegram'da yönetin
- Lovable, Bolt, v0 ve Cursor formlarına gerçek backend bağlayın
- Web formu için Telegram botu: yazmalı mı?
- GDPR uyumlu form servisi: seçmeden önce 7 kontrol
- Mailto formu mu, form backend mi? Ne seçmeli
- Webflow form limiti: 50 gönderimden sonra ne?
- Turnstile, reCAPTCHA ve hCaptcha karşılaştırması
- Contact Form 7 ve Elementor formları Telegram'a
- Squarespace formu e-posta göndermiyor mu?
- Shopify iletişim formu mesajları nereye gider?
- Google Form'dan Telegram'a: ücretsiz yöntem
- Wix formu e-posta göndermiyor mu? Çözümler
- AB / KVKK odaklı Formspree alternatifleri karşılaştırması
- Mesaj göndermeyen iletişim formları: 793 site test edildi