# 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.

Publié le 2026-10-25 · https://ikiform.com/fr/guides/send-form-to-email/emailjs-alternative

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.

Schéma : Qui peut envoyer via une clé d'e-mail côté navigateur, et via un endpoint de formulaire

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 :

|   | EmailJS | Ikiform |
| --- | --- | --- |
| Ce que contient le navigateur | Id de service, id de modèle, clé publique | Un id de formulaire |
| Qui décide du destinataire | Le modèle, qui peut lire une variable | Uniquement les réglages de votre tableau de bord |
| Filtrage du spam | Captcha par modèle, si vous l'ajoutez | Honeypot, score de spam et limites de débit sur chaque formulaire, plus une vérification humaine facultative |
| Vos messages ensuite | Dans votre boîte | Dans le tableau de bord, l'export CSV et l'API, en plus de votre boîte |
| E-mail au visiteur | Oui, avec un modèle de réponse automatique | Non |
| Votre propre design d'e-mail | Oui, des modèles dans leur éditeur | Non, 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`:

```tsx
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`:

```tsx
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](https://ikiform.com/fr/docs/guides/react.md) 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](https://ikiform.com/fr/docs/webhooks.md). 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](https://ikiform.com/fr/guides/send-form-to-email/multiple-recipients.md#route) 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ème | Que faire |
| --- | --- |
| Répondre écrit toujours à Ikiform | Le champ s'appelle toujours `user_email`. Renommez-le `email`, ou l'un de `email`, `email_address`, `your_email`, `reply_to`, `contact_email`. |
| `422 validation_failed` | L'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_required` | Le 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 spam | Le 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

### Ikiform est-il moins cher qu'EmailJS ?

Cela dépend du volume et de l'offre, et les deux évoluent : comparez les pages de tarifs actuelles. Ikiform n'a pas d'offre gratuite ; il facture à la soumission, avec Pro ou des packs payés une seule fois.

### Est-il sûr d'exposer la clé publique EmailJS ?

EmailJS indique qu'une clé copiée ne peut envoyer que vos modèles, pas des e-mails personnalisés. Mais l'appelant remplit quand même les variables du modèle, et si le destinataire est une variable, il le choisit aussi. Restreignez la clé à vos domaines et ajoutez un captcha si vous continuez à l'utiliser.

### Ikiform a-t-il besoin d'une clé d'API dans le navigateur ?

Non. Un formulaire côté navigateur envoie vers l'URL de votre formulaire sans clé. Les clés secrètes ne servent qu'à envoyer depuis votre propre serveur, et elles ont leur place dans des variables d'environnement côté serveur.

### Ikiform peut-il envoyer un e-mail à la personne qui a rempli le formulaire ?

Non. Ikiform envoie des e-mails à vous et à votre équipe, pas à l'expéditeur : il n'y a donc ni réponse automatique ni e-mail de confirmation. Envoyez un webhook à votre outil d'e-mailing et envoyez la confirmation depuis celui-ci.

### Puis-je garder mes noms de champs EmailJS ?

Oui, sauf le champ e-mail : nommez-le email pour que les réponses aux notifications aillent au visiteur. Ikiform stocke tous les noms de champs que vous envoyez.

## Autres guides

- [Recevoir un formulaire par e-mail](https://ikiform.com/fr/guides/send-form-to-email.md): Recevez chaque soumission dans votre boîte sans serveur mail : pourquoi mailto : et mail() de PHP échouent, et une solution qui filtre le spam et permet de répondre.
- [Next.js](https://ikiform.com/fr/guides/send-form-to-email/nextjs.md): Deux façons d'envoyer un formulaire Next.js par e-mail avec une Server Action : l'envoyer vous-même avec Resend ou Nodemailer, ou le transmettre à un backend.
- [Répondre depuis votre boîte](https://ikiform.com/fr/guides/send-form-to-email/reply-to.md): Faites que Répondre sur une notification écrive à la personne qui a rempli le formulaire : noms de champs valides, quand utiliser _replyto, et pourquoi les réponses s'égarent.
- [Sans PHP](https://ikiform.com/fr/guides/send-form-to-email/without-php.md): Remplacez un script contact.php qui appelle mail() par un formulaire qui envoie vers un endpoint hébergé, en gardant vos noms de champs, votre page de remerciement et les réponses.
- [Vers Gmail](https://ikiform.com/fr/guides/send-form-to-email/gmail.md): Recevez les soumissions dans Gmail sans liens mailto:, mots de passe d'application ni Apps Script, puis libellez-les, évitez les spams et répondez depuis Gmail.
- [Plusieurs destinataires](https://ikiform.com/fr/guides/send-form-to-email/multiple-recipients.md): Envoyez un formulaire à toute votre équipe, ou aiguillez chaque message vers la bonne personne selon le sujet, sans laisser le formulaire choisir qui reçoit l'e-mail.
