Comment créer un formulaire de contact dans React Native (et Expo) qui envoie des e-mails
Un formulaire de contact React Native qui vous envoie chaque message par e-mail, avec état d’envoi, protection contre les bots et sans backend. Fonctionne avec Expo et React Native nu.
Mis à jour:
Vous allez ajouter un formulaire de contact à une application React Native ou Expo avec le paquet
@frontmail/react-native : un provider, un hook useSendEmail qui suit l’état de l’envoi et un
composant Turnstile prêt à l’emploi contre les bots. L’e-mail part via votre propre boîte mail.
Il vous faut : un compte Frontmail gratuit, un projet Expo ou React Native et une dizaine de minutes.
1. Connectez votre boîte mail et créez le modèle
- Services d’envoi → Ajouter un service : connectez Gmail, Outlook, SMTP ou un fournisseur transactionnel, cochez Définir par défaut, puis Vérifier et enregistrer.
- Modèles → Depuis la galerie → Formulaire de contact → Utiliser ce modèle.
Paramètres obligatoires :
name,email,message; facultatifs :phone,topic. - Copiez l’ID du modèle (
tpl_…) depuis l’onglet Extraits de code.
2. Autorisez les applications mobiles
- Sécurité → Applications mobiles → Autoriser les applications mobiles →
Enregistrer. Les applications n’envoient pas d’en-tête
Origin: sans ce réglage, l’API répondorigin_not_allowed. - Modèle → Paramètres → Protection → Exiger un CAPTCHA (Turnstile). L’application utilise le widget Turnstile partagé de Frontmail ; vous n’avez pas besoin de clés Cloudflare.
3. Installez
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview
Dans un projet React Native nu, utilisez npm i avec les mêmes paquets, puis lancez
npx pod-install.
4. Créez le formulaire
import { useRef, useState } from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { FrontmailProvider, TurnstileWebView, useSendEmail, type TurnstileWebViewHandle } from '@frontmail/react-native';
const options = { publicKey: 'pk_…' }; // keep the object stable
export default function App() {
return (
<FrontmailProvider options={options}>
<ContactForm />
</FrontmailProvider>
);
}
function ContactForm() {
const { send, status, error } = useSendEmail(null, 'tpl_…'); // null = default service
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [token, setToken] = useState<string>();
const turnstile = useRef<TurnstileWebViewHandle>(null);
const onSend = async () => {
await send({ name, email, message }, { turnstileToken: token });
setToken(undefined);
turnstile.current?.reset(); // a token works only once
};
if (status === 'sent' || status === 'held') return <Text>Thank you! We’ll get back to you soon.</Text>;
return (
<View style={{ gap: 12, padding: 16 }}>
<TextInput placeholder="Name" value={name} onChangeText={setName} />
<TextInput placeholder="Email" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
<TextInput placeholder="Message" value={message} onChangeText={setMessage} multiline />
<TurnstileWebView ref={turnstile} onToken={setToken} />
<Button title="Send" disabled={!token || status === 'sending'} onPress={onSend} />
{error && <Text>{error.message}</Text>}
</View>
);
}
send ne lève jamais d’exception : la promesse se résout avec le résultat ou undefined, et
status passe de idle à sending, puis à sent, held ou error. Les nouvelles tentatives
après une erreur réseau réutilisent une seule clé d’idempotence : un utilisateur dans le train ne
vous enverra jamais deux fois le même message.
5. Testez
Démarrez l’application (npx expo start), remplissez le formulaire et touchez Send. L’e-mail
arrive dans votre boîte de réception et dans l’Historique du tableau de bord.
origin_not_allowed– Autoriser les applications mobiles est désactivé ou non enregistré.captcha_required– la vérification Turnstile n’est pas encore terminée ; le bouton reste désactivé tant qu’il n’y a pas de jeton.invalid_template_params– un champ obligatoire est vide ou l’e-mail n’est pas valide.
heldsignifie que le message a été accepté alors que vos crédits étaient épuisés. Il part automatiquement dès que vous rechargez – rien n’est abandonné.
Étapes suivantes
- Ajoutez
limitRate: { id: 'contact', throttle: 30_000 }aux options du provider pour autoriser un message toutes les 30 secondes par appareil. - Envoyez une confirmation à l’utilisateur avec le modèle Réponse automatique : merci pour votre message de la galerie.


