نموذج اتصال React يرسل البريد بلا خادم خلفي

Formgong team ·

أرسل FormData عبر fetch إلى نقطة Formgong وألصق مفتاح الوصول العلني. يصفّي Formgong الرسائل المزعجة ويوصل الطلب إلى البريد وTelegram. يصل Telegram فورًا. في الخطة المجانية البريد ملخص يومي. لا تشغّل خادمًا ولا 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", "ar");
    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", "ar");
    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", "ar");
    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", "ar");
    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 الرسائل المزعجة ويوصل الطلب.

ما الذي يختلف هنا

  • يصل Telegram فورًا حتى في الخطة المجانية. البريد مشمول أيضًا، كملخص يومي لمستلم واحد.
  • تُخزَّن الطلبات في الاتحاد الأوروبي، على Cloudflare D1 ضمن اختصاص الاتحاد الأوروبي.
  • تصفية الرسائل المزعجة لا تحتاج CAPTCHA ولا ملفات تعريف ارتباط. فخ العسل يعمل دائمًا. Turnstile اختياري.
  • مفتاح الوصول في المكوّن علني. لا تضع كلمة مرور SMTP ولا مفتاح API خاصًا في الحزمة.
  • نص الخطأ وصفحة الشكر يمكن أن يتبعا لغة الزائر. اللغات اثنتا عشرة.

لماذا ليس EmailJS ولا مسار API خاص؟

لماذا ليس EmailJS ولا مسار API خاص؟
EmailJSمسار API لديكFormgong
ماذا تضيفحزمة npm ومعرّف خدمة ومعرّف قالب ومفتاحًا علنيًا.دالة بلا خادم وعميل SMTP مثل Nodemailer.مفتاح وصول علني واحد في مكوّن هذه الصفحة.
البريدبريد فوري من قالبك. الخطة المجانية 200 طلبًا في الشهر وقالبان.بريد فوري إذا قبل مزوّدك الرسالة.ملخص يومي في الخطة المجانية. كل طلب بالبريد في Pro وBusiness.
Telegramليس جزءًا من EmailJS. الخدمة ترسل بريدًا.فقط إذا كتبت هذا الاستدعاء بنفسك.تسليم فوري، حتى في الخطة المجانية.
المفاتيح في المتصفحالمفتاح العلني يُفترض أن يبقى علنيًا. أبقِ الخاص على خادم.كلمة مرور SMTP تبقى في بيئة الاستضافة. هذه قوة هذا الطريق.مفتاح الوصول علني. لا يوجد سر SMTP.
الرسائل المزعجةتضع الحدود في لوحة EmailJS. دليل Webtips لعام 2024 لا يضيف شيئًا.أنت تبني المرشّح.فخ العسل يعمل دائمًا. بلا ملفات تعريف ارتباط. CAPTCHA غير مطلوب.
الحصة المجانية200 طلب في الشهر، ثم يُسقط الزائد.الطبقة المجانية لمستضيفك زائد حد مزوّد البريد.300 طلب في الشهر.
أين تقع البياناتيخزّن EmailJS السجل على خوادمه. المجاني يُبقي 7 أيام. صفحة الأسعار لا تقول الاتحاد الأوروبي فقط.حيث يخزّنها مستضيفك ومزوّد بريدك.في الاتحاد الأوروبي، على Cloudflare D1.

اختر EmailJS عندما يجب أن يخرج البريد من صندوقك، بقالبك، لحظة الإرسال.

اختر دالة Vercel أو Netlify عندما يجب أن يحدّث الإرسال قاعدتك أو يتحقق من أن الزائر سجّل الدخول.

اختر Formgong لنموذج اتصال. Telegram فوري في الخطة المجانية. البريد في تلك الخطة ملخص يومي. لا تخزّن سر SMTP.

أرقام EmailJS من صفحة الأسعار في 6 أكتوبر 2026. عمود مسار API هو دالة Vercel أو Netlify مع SMTP، وليس قائمة أسعار خدمة واحدة.

الإعداد في React

  1. أنشئ نموذجًا في Formgong وانسخ مفتاح الوصول. يبدو مثل fk_your_access_key.
  2. الصق المكوّن أعلاه في التطبيق واستبدل هذا المفتاح.
  3. اعرض المكوّن، افتح الموقع وأرسل لنفسك تجربة.
  4. في Formgong افتح النموذج واربط Telegram إذا أردت تنبيهًا على الهاتف.

أسئلة وأجوبة

هل أحتاج خادمًا خلفيًا لنموذج اتصال في React؟

لا. أرسل FormData عبر fetch إلى Formgong. لا تضف مسار API ولا Nodemailer ولا SMTP.

كيف أرسل بريدًا من React بلا Nodemailer أو SMTP؟

استخدم مكوّن هذه الصفحة. يقبل Formgong الـ POST ويصفّي الرسائل المزعجة. يصل Telegram فورًا. في الخطة المجانية البريد ملخص يومي. Pro وBusiness يرسلان بريدًا لكل طلب.

هل وضع المفتاح في كود العميل آمن؟

نعم. access_key علني. يستطيع فقط إرسال هذا النموذج. لا يقرأ صندوق الوارد. في Pro يمكنك حدّ النطاقات المسموحة. لا تضع كلمة مرور SMTP في الحزمة.

كيف أوقف الرسائل المزعجة في نموذج React؟

اترك botcheck فارغًا. يقيّم Formgong الرسائل المزعجة بلا ملفات تعريف ارتباط وبلا CAPTCHA إلزامي. Turnstile اختياري إذا أردت لاحقًا لغزًا.

هل يعمل هذا مع Next.js؟

نعم. في App Router أضف "use client" في أول الملف. يبقى المكوّن كما هو. متغير Next.js في هذه الصفحة يعرض الملف.

ابدأ مجانًا