Recevoir un formulaire par e-mail

Recevoir un formulaire par e-mail7 min de lecture

Une alternative à EmailJS qui garde les clés hors du navigateur

Quittez EmailJS pour un formulaire de contact : pourquoi les clés côté navigateur sont un risque, ce qui change dans le code, et ce que vous perdez, comme la réponse automatique.

Preet SutharFondateur, Ikiform

EmailJS envoie les e-mails depuis le navigateur : votre id de service, votre id de modèle et votre clé publique sont donc livrés dans le JavaScript de votre page, à la portée de tous. Un backend de formulaires n'a besoin d'aucune clé dans le navigateur : le formulaire envoie vers une URL, et le backend filtre le spam, stocke le message et vous envoie un e-mail. Vous perdez la réponse automatique d'EmailJS au visiteur, que vous enverriez plutôt depuis votre propre outil d'e-mailing.

Ce qui change ici

EmailJS est populaire pour une bonne raison : il envoie de vrais e-mails depuis un site statique sans serveur. Pour cela, le navigateur appelle directement EmailJS avec trois valeurs : un id de service, un id de modèle et une clé publique. Elles sont dans votre bundle, lisibles dans le code source de la page ou l'onglet réseau.

La FAQ d'EmailJS indique que quelqu'un qui les copie ne peut envoyer que vos modèles avec votre contenu, pas des e-mails arbitraires. C'est vrai pour le texte fixe du modèle. Mais c'est l'appelant qui remplit les variables du modèle : les clés copiées peuvent donc envoyer votre modèle avec n'importe quel nom, message et liens, aussi souvent que votre quota le permet. Et si le To d'un modèle est une variable, comme pour une réponse automatique à l'adresse du visiteur, l'appelant choisit aussi le destinataire.

Qui peut envoyer via une clé d'e-mail côté navigateur, et via un endpoint de formulairesdk e-mail navigateurendpoint de formulairevotre pageservice, modèle etclé publiquele script den'importe quicopie les troisvaleursvotre modèleavec son texte,vers son Tovotre pageun id deformulaire, pas desecretIkiformscore de spam,limite de débit,domainesvosdestinatairesseulementdéfinis dans letableau de bordcode sourceenvoienvoie-mailQui peut envoyer via une clé d'e-mail côté navigateur, et via un endpoint de formulairesdk e-mail navigateurendpoint de formulairevotre pageservice, modèle et clé publiquele script de n'importe quicopie les trois valeursvotre modèleavec son texte, vers son Tovotre pageun id de formulaire, pas desecretIkiformscore de spam, limite de débit,domainesvos destinatairesseulementdéfinis dans le tableau de bordcode sourceenvoienvoie-mail
  1. Avec un SDK d'e-mail côté navigateur, la page contient tout ce qu'il faut pour envoyer. Un script ailleurs peut copier les id et la clé et envoyer votre modèle avec son propre texte, jusqu'à votre quota.
  2. Si le destinataire du modèle vient d'une variable, les clés copiées choisissent aussi qui le reçoit.
  3. Avec un endpoint de formulaire, la page ne contient qu'un id de formulaire. Les envois sont filtrés contre le spam et soumis à une limite de débit, et les e-mails ne vont qu'aux destinataires définis dans le tableau de bord.
Un id de formulaire n'est pas non plus un secret. La différence est ce qu'un inconnu peut en faire : envoyer votre formulaire, pas envoyer vos e-mails.

EmailJS propose des réglages qui réduisent ce risque, comme restreindre les domaines autorisés à utiliser votre clé et ajouter un captcha à un modèle. Vérifiez lesquels votre offre inclut et activez-les si vous restez. La comparaison ci-dessous porte sur la conception, pas sur un verdict concernant le service :

ItemEmailJSIkiform
Ce que contient le navigateurId de service, id de modèle, clé publiqueUn id de formulaire
Qui décide du destinataireLe modèle, qui peut lire une variableUniquement les réglages de votre tableau de bord
Filtrage du spamCaptcha par modèle, si vous l'ajoutezHoneypot, score de spam et limites de débit sur chaque formulaire, plus une vérification humaine facultative
Vos messages ensuiteDans votre boîteDans le tableau de bord, l'export CSV et l'API, en plus de votre boîte
E-mail au visiteurOui, avec un modèle de réponse automatiqueNon
Votre propre design d'e-mailOui, des modèles dans leur éditeurNon, une mise en page fixe avec l'objet New submission: <form name>

Si la réponse automatique ou un e-mail à votre marque est l'essentiel, EmailJS, ou votre propre serveur avec une API d'e-mail, convient mieux. Si vous voulez le message dans votre boîte, en toute sécurité, et conservé quelque part, un backend de formulaires convient.

Migrer le formulaire

Un formulaire EmailJS typique en React, d'après leur tutoriel de formulaire de contact :

ContactForm.tsx, avant
import emailjs from "@emailjs/browser";
import { type FormEvent, useRef, useState } from "react";

export function ContactForm() {
  const form = useRef<HTMLFormElement>(null);
  const [status, setStatus] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!form.current) return;
    try {
      await emailjs.sendForm("service_abc123", "template_xyz789", form.current, {
        publicKey: "aBcDeFgHiJkLmNoPq",
      });
      setStatus("Message sent.");
    } catch {
      setStatus("Couldn't send your message.");
    }
  }

  return (
    <form ref={form} onSubmit={onSubmit}>
      <input name="user_name" required />
      <input name="user_email" type="email" required />
      <textarea name="message" required />
      <button>Send</button>
      <p role="status">{status}</p>
    </form>
  );
}

Le même formulaire qui envoie à Ikiform avec submit() de @ikiform/sdk :

ContactForm.tsx, après
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";

const FORM_ID = "frm_xxxxxxxxxxxxxxxx";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent">("idle");
  const [fields, setFields] = useState<Record<string, string[]>>({});
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    setFields({});
    setError("");
    const result = await submit(FORM_ID, new FormData(event.currentTarget));
    if (result.ok) {
      setStatus("sent");
      return;
    }
    setFields(result.error.fields ?? {});
    setError(result.error.code === "network_error" ? "You're offline. Try again." : result.error.message);
    setStatus("idle");
  }

  if (status === "sent") return <p role="status">Message sent. We'll reply by email.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="user_name">Name</label>
      <input id="user_name" name="user_name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error && !Object.keys(fields).length ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
    </form>
  );
}
  • @emailjs/browser et les trois id disparaissent. Désinstallez le package, et supprimez le modèle ou le service EmailJS une fois le nouveau formulaire en place, pour que les copies en cache de votre page cessent d'envoyer.
  • user_email devient email. EmailJS ne se soucie pas des noms, mais Ikiform utilise le champ appelé email comme adresse de réponse. Les autres noms peuvent rester, car les notifications listent les champs tels qu'ils arrivent.
  • submit() ne lève jamais d'exception. Il renvoie l'enveloppe d'erreur, pour que les erreurs de champ issues de la définition du formulaire s'affichent sous leur input.
  • Le honeypot est nouveau. EmailJS n'en avait pas ; cet input masqué arrête les bots les plus simples sans configuration.
  • Ajoutez votre domaine dans Allowed domains, pour que les envois depuis d'autres sites soient marqués comme spam.
Vous utilisez la vérification humaine ? Réglez le Spam check du formulaire sur Ikiform, puis appelez challenge(FORM_ID) avant submit et passez le jeton en turnstileToken. Le guide React le montre.

Si vous utilisiez la réponse automatique

Ikiform n'envoie pas d'e-mail à la personne qui remplit le formulaire. Les e-mails de notification vont uniquement à vous et à votre équipe : il n'y a donc ni réponse automatique, ni e-mail de confirmation, ni double opt-in. Envoyez-les depuis votre outil d'e-mailing, déclenché par un webhook. Un service d'envoi que vous pilotez depuis un serveur, comme Resend ou Postmark, peut l'envoyer à chaque webhook submission.created, avec l'adresse du visiteur tirée du payload de confiance plutôt que du navigateur. Le guide sur les destinataires multiples contient un gestionnaire que vous pouvez adapter.

Demandez-vous si vous en avez besoin. Le message de remerciement sur la page indique au visiteur que cela a fonctionné, et une vraie réponse de votre part une heure plus tard vaut mieux qu'un modèle.

Vérifiez que tout fonctionne

  1. Dans Allowed domains du formulaire, ajoutez le domaine de votre site, plus localhost pendant le développement. Laissez vide pour accepter les envois de partout.
  2. Cherchez emailjs et votre ancien id de service dans votre site compilé. Rien ne doit correspondre.
  3. Remplissez le formulaire sur votre page et envoyez-le. Vous arrivez sur votre page de remerciement, ou sur celle hébergée par Ikiform.
  4. Vérifiez la boîte de réception de chaque destinataire des notifications. Chacun reçoit un e-mail avec les champs, et Reply répond à l'expéditeur quand le formulaire a un champ email.
  5. Envoyez avec le honeypot rempli, _gotcha=bot. L'envoi reçoit quand même une réponse de succès, mais la soumission est stockée comme spam et n'envoie ni e-mail ni webhook.

Si quelque chose ne va pas

ProblèmeQue faire
Répondre écrit toujours à IkiformLe champ s'appelle toujours user_email. Renommez-le email, ou l'un de email, email_address, your_email, reply_to, contact_email.
422 validation_failedL'envoi ne correspond pas à la définition des champs du formulaire. error.fields nomme chaque champ et la raison. Faites correspondre le name de l'input à l'id du champ.
403 challenge_requiredLe spam check du formulaire est réglé sur Ikiform et un envoi fetch n'avait pas de jeton. Obtenez-en un avec challenge(formId) de @ikiform/sdk avant chaque envoi, ou désactivez la vérification.
403 origin_not_allowed, ou tous les envois finissent en spamLe domaine de la page n'est pas dans les Allowed domains du formulaire. Ajoutez-le, plus localhost pour les tests en local. Les sous-domaines d'un domaine listé sont autorisés.

Questions

Une étape vous bloque, ou vous voulez un guide pour votre cas ? Écrivez à hello@ikiform.com.