טופס יצירת קשר ב-React ששולח מייל בלי בקאנד

Formgong team ·

שולחים FormData עם fetch לנקודת הקצה של Formgong ומדביקים מפתח גישה ציבורי. Formgong מסנן ספאם ומעביר את הפנייה למייל ולטלגרם. טלגרם מגיע מיד. בתוכנית החינמית המייל הוא סיכום יומי. לא מריצים שרת, SMTP או מפתחות סודיים. מדביקים את הרכיב למטה.

יוצרים טופס, מעתיקים את מפתח הגישה ומחליפים את fk_your_access_key ברכיב. המפתח ציבורי. המקום שלו הוא הקובץ הזה.

Vite או Create React App
import { useState } from "react";

// הדביקו את המפתח: החליפו את fk_your_access_key במפתח של טופס Formgong.
const ACCESS_KEY = "fk_your_access_key";

export default function ContactForm() {
  const [status, setStatus] = useState("idle");
  const [notice, setNotice] = useState("");

  async function onSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    if (!(form instanceof HTMLFormElement) || !form.reportValidity()) return;
    const body = new FormData(form);
    body.set("access_key", ACCESS_KEY);
    body.set("_lang", "he");
    setStatus("sending");
    setNotice("");
    try {
      const response = await fetch("https://formgong.com/submit", {
        method: "POST",
        headers: { Accept: "application/json" },
        body,
      });
      const result = await response.json();
      if (!response.ok || !result.success) {
        setStatus("error");
        setNotice(result.message || "השליחה נכשלה. נא לנסות שוב.");
        return;
      }
      form.reset();
      setStatus("success");
      setNotice(result.message || "נשלח. תודה!");
    } catch {
      setStatus("error");
      setNotice("אין חיבור. נא לנסות שוב.");
    }
  }

  const sending = status === "sending";
  return (
    <form onSubmit={onSubmit}>
      <label>{"שם"} <input name="name" autoComplete="name" placeholder={"השם שלך"} required /></label>
      <label>{"אימייל"} <input name="email" type="email" autoComplete="email" placeholder={"you@example.com"} required /></label>
      <label>{"הודעה"} <textarea name="message" placeholder={"איך נוכל לעזור?"} required /></label>
      <div aria-hidden="true" style={{ position: "absolute", insetInlineStart: 0, top: 0, width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }}>
        <input name="botcheck" tabIndex={-1} autoComplete="off" />
      </div>
      <button type="submit" disabled={sending}>{sending ? "שולחים…" : "שליחה"}</button>
      <p role={status === "error" ? "alert" : "status"}>{notice}</p>
    </form>
  );
}

הגדרות React נוספות

Vite או Create React App

הרכיב למעלה הוא ההגדרה הזו. שומרים אותו כ-src/ContactForm.jsx ומציגים אותו מ-App. בלי חבילה נוספת.

Vite או Create React App
// src/App.jsx — Vite or Create React App
import ContactForm from "./ContactForm";

export default function App() {
  return <ContactForm />;
}
Next.js App Router

מוסיפים את הנחיית הלקוח בראש הקובץ. העמוד שמציג את הטופס יכול להישאר רכיב שרת.

Next.js App Router
"use client";

import { useState } from "react";

// הדביקו את המפתח: החליפו את fk_your_access_key במפתח של טופס Formgong.
const ACCESS_KEY = "fk_your_access_key";

export default function ContactForm() {
  const [status, setStatus] = useState("idle");
  const [notice, setNotice] = useState("");

  async function onSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    if (!(form instanceof HTMLFormElement) || !form.reportValidity()) return;
    const body = new FormData(form);
    body.set("access_key", ACCESS_KEY);
    body.set("_lang", "he");
    setStatus("sending");
    setNotice("");
    try {
      const response = await fetch("https://formgong.com/submit", {
        method: "POST",
        headers: { Accept: "application/json" },
        body,
      });
      const result = await response.json();
      if (!response.ok || !result.success) {
        setStatus("error");
        setNotice(result.message || "השליחה נכשלה. נא לנסות שוב.");
        return;
      }
      form.reset();
      setStatus("success");
      setNotice(result.message || "נשלח. תודה!");
    } catch {
      setStatus("error");
      setNotice("אין חיבור. נא לנסות שוב.");
    }
  }

  const sending = status === "sending";
  return (
    <form onSubmit={onSubmit}>
      <label>{"שם"} <input name="name" autoComplete="name" placeholder={"השם שלך"} required /></label>
      <label>{"אימייל"} <input name="email" type="email" autoComplete="email" placeholder={"you@example.com"} required /></label>
      <label>{"הודעה"} <textarea name="message" placeholder={"איך נוכל לעזור?"} required /></label>
      <div aria-hidden="true" style={{ position: "absolute", insetInlineStart: 0, top: 0, width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }}>
        <input name="botcheck" tabIndex={-1} autoComplete="off" />
      </div>
      <button type="submit" disabled={sending}>{sending ? "שולחים…" : "שליחה"}</button>
      <p role={status === "error" ? "alert" : "status"}>{notice}</p>
    </form>
  );
}

// app/contact/page.jsx — this file can stay a Server Component.
import ContactForm from "./ContactForm";

export default function Page() {
  return <ContactForm />;
}
React Hook Form

מתקינים react-hook-form. משאירים שם את האימות. השליחה עדיין FormData אל Formgong.

React Hook Form
import { useState } from "react";
import { useForm } from "react-hook-form";

// הדביקו את המפתח: החליפו את fk_your_access_key במפתח של טופס Formgong.
const ACCESS_KEY = "fk_your_access_key";

export default function ContactForm() {
  const { register, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm();
  const [notice, setNotice] = useState("");
  const [failed, setFailed] = useState(false);

  async function onSubmit(values) {
    const body = new FormData();
    body.set("access_key", ACCESS_KEY);
    body.set("_lang", "he");
    body.set("name", values.name);
    body.set("email", values.email);
    body.set("message", values.message);
    body.set("botcheck", values.botcheck || "");
    setFailed(false);
    setNotice("");
    try {
      const response = await fetch("https://formgong.com/submit", {
        method: "POST",
        headers: { Accept: "application/json" },
        body,
      });
      const result = await response.json();
      if (!response.ok || !result.success) {
        setFailed(true);
        setNotice(result.message || "השליחה נכשלה. נא לנסות שוב.");
        return;
      }
      reset();
      setNotice(result.message || "נשלח. תודה!");
    } catch {
      setFailed(true);
      setNotice("אין חיבור. נא לנסות שוב.");
    }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>{"שם" <input {...register("name", { required: "שדה חובה" })} aria-invalid={errors.name ? true : undefined} autoComplete="name" /></label>
      {errors.name && <span>{errors.name.message}</span>}
      <label>{"אימייל" <input type="email" {...register("email", { required: "שדה חובה" })} aria-invalid={errors.email ? true : undefined} autoComplete="email" /></label>
      {errors.email && <span>{errors.email.message}</span>}
      <label>{"הודעה" <textarea {...register("message", { required: "שדה חובה" })} aria-invalid={errors.message ? true : undefined} /></label>
      {errors.message && <span>{errors.message.message}</span>}
      <div aria-hidden="true" style={{ position: "absolute", insetInlineStart: 0, top: 0, width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }}>
        <input {...register("botcheck")} tabIndex={-1} autoComplete="off" />
      </div>
      <button type="submit" disabled={isSubmitting}>{isSubmitting ? "שולחים…" : "שליחה"}</button>
      {failed ? <p role="alert">{notice}</p> : <p role="status">{notice}</p>}
    </form>
  );
}
TypeScript

אותו רכיב, עם טיפוס למצב ועם טיפוס לתשובת JSON. שומרים כ-ContactForm.tsx.

TypeScript
import { useState, type FormEvent } from "react";

// הדביקו את המפתח: החליפו את fk_your_access_key במפתח של טופס Formgong.
const ACCESS_KEY = "fk_your_access_key";

type Status = "idle" | "sending" | "success" | "error";
type SubmitResult = { success?: boolean; message?: string };

export default function ContactForm() {
  const [status, setStatus] = useState<Status>("idle");
  const [notice, setNotice] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const form = event.currentTarget;
    if (!form.reportValidity()) return;
    const body = new FormData(form);
    body.set("access_key", ACCESS_KEY);
    body.set("_lang", "he");
    setStatus("sending");
    setNotice("");
    try {
      const response = await fetch("https://formgong.com/submit", {
        method: "POST",
        headers: { Accept: "application/json" },
        body,
      });
      const result = (await response.json()) as SubmitResult;
      if (!response.ok || !result.success) {
        setStatus("error");
        setNotice(result.message || "השליחה נכשלה. נא לנסות שוב.");
        return;
      }
      form.reset();
      setStatus("success");
      setNotice(result.message || "נשלח. תודה!");
    } catch {
      setStatus("error");
      setNotice("אין חיבור. נא לנסות שוב.");
    }
  }

  const sending = status === "sending";
  return (
    <form onSubmit={onSubmit}>
      <label>{"שם"} <input name="name" autoComplete="name" placeholder={"השם שלך"} required /></label>
      <label>{"אימייל"} <input name="email" type="email" autoComplete="email" placeholder={"you@example.com"} required /></label>
      <label>{"הודעה"} <textarea name="message" placeholder={"איך נוכל לעזור?"} required /></label>
      <div aria-hidden="true" style={{ position: "absolute", insetInlineStart: 0, top: 0, width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }}>
        <input name="botcheck" tabIndex={-1} autoComplete="off" />
      </div>
      <button type="submit" disabled={sending}>{sending ? "שולחים…" : "שליחה"}</button>
      <p role={status === "error" ? "alert" : "status"}>{notice}</p>
    </form>
  );
}

רכיב React מוכן ב-GitHub: github.com/formgong/react-contact-form. רישיון MIT: לחצו על „Use this template” או העתיקו את הקבצים.

חבילת React: npm install @formgong/react (npm: @formgong/react)

הוספה לפרויקט קיים: npx formgong init (npm: formgong)

איך טפסים עובדים ב-React

React מצייר את הטופס. הוא לא שולח דואר. Vite ו-Create React App מוסרים קבצים סטטיים, ולכן הדפדפן צריך כתובת שמקבלת את ה-POST.

הכתובת היא Formgong. הרכיב שולח FormData עם fetch אל /submit. איתו הולכים access_key ציבורי, _lang ושדה botcheck ריק. Formgong מסנן ספאם ומעביר את הפנייה.

מה שונה כאן

  • טלגרם מגיע מיד גם בתוכנית החינמית. מייל כלול גם הוא, כסיכום יומי לנמען אחד.
  • הפניות נשמרות באיחוד האירופי, ב-Cloudflare D1 תחת סמכות האיחוד האירופי.
  • סינון הספאם לא דורש CAPTCHA ולא עוגיות. מלכודת הדבש תמיד פעילה. Turnstile רשות.
  • מפתח הגישה ברכיב ציבורי. לא שמים סיסמת SMTP או מפתח API פרטי בחבילה.
  • טקסט השגיאה ועמוד התודה יכולים לעקוב אחרי שפת המבקר. יש 12 שפות.

למה לא EmailJS ולא נתיב API משלכם?

למה לא EmailJS ולא נתיב API משלכם?
EmailJSנתיב ה-API שלכםFormgong
מה מוסיפיםחבילת npm, service id, template id ומפתח ציבורי.פונקציית serverless ולקוח SMTP כמו Nodemailer.מפתח גישה ציבורי אחד ברכיב שבדף הזה.
מיילמייל מיידי מהתבנית שלכם. התוכנית החינמית היא 200 בקשות בחודש ו-2 תבניות.מייל מיידי אם הספק מקבל את ההודעה.סיכום יומי בתוכנית החינמית. כל פנייה במייל יש ב-Pro וב-Business.
טלגרםזה לא חלק מ-EmailJS. השירות שולח מייל.רק אם כותבים את הקריאה בעצמכם.מסירה מיידית, כבר בתוכנית החינמית.
מפתחות בדפדפןהמפתח הציבורי אמור להיות ציבורי. את הפרטי שומרים בשרת.סיסמת ה-SMTP נשארת בסביבת המארח. זו החוזקה של הדרך הזו.מפתח הגישה ציבורי. אין סוד SMTP.
ספאםאת המגבלות קובעים בלוח EmailJS. המדריך של Webtips מ-2024 לא מוסיף דבר.את המסנן בונים בעצמכם.מלכודת דבש תמיד פעילה. בלי עוגיות. CAPTCHA לא נדרש.
מכסה חינמית200 בקשות בחודש, ואחר כך העודף נזרק.השכבה החינמית של המארח, ועוד תקרת ספק הדואר.300 פניות בחודש.
איפה הנתוניםEmailJS שומר היסטוריה בשרתים שלו. החינמי שומר 7 ימים. דף המחיר לא אומר שזה רק האיחוד האירופי.איפה שהמארח וספק הדואר שומרים אותם.באיחוד האירופי, על Cloudflare D1.

בחרו EmailJS כשהדואר צריך לצאת מהתיבה שלכם, עם התבנית שלכם, ברגע השליחה.

בחרו פונקציית Vercel או Netlify כשהשליחה צריכה לעדכן את המסד שלכם או לבדוק שהמבקר מחובר.

בחרו Formgong לטופס יצירת קשר. טלגרם מיידי בתוכנית החינמית. המייל בתוכנית הזו הוא סיכום יומי. לא שומרים סוד SMTP.

המספרים של EmailJS לקוחים מדף המחיר ב-6 באוקטובר 2026. עמודת נתיב ה-API היא פונקציית Vercel או Netlify ועוד SMTP, לא מחירון של שירות אחד.

הגדרה ב-React

  1. צרו טופס ב-Formgong והעתיקו את מפתח הגישה. הוא נראה כמו fk_your_access_key.
  2. הדביקו את הרכיב למעלה באפליקציה והחליפו את המפתח.
  3. הציגו את הרכיב, פתחו את האתר ושלחו לעצמכם ניסיון.
  4. ב-Formgong פתחו את הטופס וחברו טלגרם אם אתם רוצים התראה לטלפון.

שאלות ותשובות

האם טופס יצירת קשר ב-React צריך בקאנד?

לא. שלחו FormData עם fetch אל Formgong. לא מוסיפים נתיב API, Nodemailer או SMTP.

איך שולחים מייל מ-React בלי Nodemailer או SMTP?

השתמשו ברכיב שבדף הזה. Formgong מקבל את ה-POST ומסנן ספאם. טלגרם מקבל את הפנייה מיד. בתוכנית החינמית המייל הוא סיכום יומי. Pro ו-Business שולחים מייל על כל פנייה.

האם בטוח לשים את המפתח בקוד הלקוח?

כן. access_key ציבורי. הוא יכול רק לשלוח את הטופס הזה. הוא לא קורא את תיבת הדואר. ב-Pro אפשר להגביל דומיינים מורשים. אל תשימו סיסמת SMTP בחבילה.

איך עוצרים ספאם בטופס React?

השאירו את botcheck ריק. Formgong מדרג ספאם בלי עוגיות ובלי CAPTCHA חובה. Turnstile רשות אם אחר כך תרצו חידה.

האם זה עובד עם Next.js?

כן. ב-App Router הוסיפו "use client" בראש הקובץ. הרכיב נשאר אותו דבר. גרסת Next.js בדף הזה מציגה את הקובץ.

התחילו חינם