React Native (@frontmail/react-native)
@frontmail/react-native permet à une application iOS ou Android d’envoyer des e-mails à partir de
vos modèles avec la clé publique – sans backend à vous. Il fonctionne avec Expo comme avec React
Native « bare ».
Installation
Section intitulée « Installation »Expo :
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webviewReact Native bare :
npm install @frontmail/react-native @react-native-async-storage/async-storage react-native-webviewnpx pod-installLes deux paquets supplémentaires sont des dépendances pairs facultatives :
| Paquet | Nécessaire pour |
|---|---|
@react-native-async-storage/async-storage |
un limitRate qui survit aux redémarrages de l’application |
react-native-webview |
<TurnstileWebView> |
Autoriser les applications mobiles
Section intitulée « Autoriser les applications mobiles »Les applications natives n’envoient pas d’en-tête Origin ni Referer : les requêtes de
l’application sont donc refusées (que vous ayez défini des sites autorisés
ou non) avec 403 origin_not_allowed tant que vous
n’avez pas activé Sécurité → Applications mobiles → Autoriser les applications mobiles dans le
tableau de bord et cliqué sur Enregistrer. Voir
Paramètres de sécurité.
Les requêtes provenant de sites web restent comparées à la liste : l’option n’affaiblit donc pas la protection de vos formulaires web. L’option est nécessaire même avec une liste vide.
Provider et useSendEmail
Section intitulée « Provider et useSendEmail »Enveloppez l’application dans FrontmailProvider et envoyez avec useSendEmail. Gardez l’objet
d’options stable – définissez-le au niveau du module ou avec useMemo :
import { useState } from 'react';import { Button, Text, TextInput, View } from 'react-native';import { FrontmailProvider, useSendEmail } from '@frontmail/react-native';
const frontmailOptions = { publicKey: 'pk_4f2a…', limitRate: { id: 'contact', throttle: 30_000 } };
export default function App() { return ( <FrontmailProvider options={frontmailOptions}> <ContactScreen /> </FrontmailProvider> );}
function ContactScreen() { const { send, status, error } = useSendEmail('svc_01J9…', 'tpl_contact'); const [email, setEmail] = useState(''); const [message, setMessage] = useState('');
return ( <View> <TextInput value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput value={message} onChangeText={setMessage} multiline /> <Button title="Envoyer" disabled={status === 'sending'} onPress={() => send({ email, message })} /> {status === 'sent' && <Text>Merci !</Text>} {status === 'held' && <Text>Bien reçu – il sera transmis sous peu.</Text>} {error && <Text>{error.message}</Text>} </View> );}options accepte publicKey, apiUrl, retry, limitRate, storageProvider et blockList.
Une privateKey n’est pas acceptée. Vous pouvez aussi passer un client existant :
<FrontmailProvider client={client}>.
useSendEmail(serviceId, templateId) renvoie { send, getStatus, status, error, result, reset }.
status passe de idle → sending → sent | held | error. send(params, options?) se résout
avec le résultat, ou avec undefined en cas d’erreur (l’erreur est dans error) – la promesse
n’est jamais rejetée, vous n’avez donc pas besoin de try/catch. reset() remet le hook à idle.
Pour envoyer dans une langue du modèle, passez locale dans les
options : send({ email, message }, { locale: 'de' }) – par exemple la langue de l’appareil tirée de
Intl.DateTimeFormat().resolvedOptions().locale. Les langues inconnues se rabattent sur la même
langue de base, puis sur la langue par défaut du modèle ; result.locale est la langue utilisée.
Chaque requête porte l’en-tête X-Frontmail-Client: @frontmail/react-native/<version>.
Turnstile
Section intitulée « Turnstile »Si le modèle exige Turnstile, affichez <TurnstileWebView> et passez le
jeton à send. Vous n’avez pas besoin de clé de site : par défaut, le composant utilise la clé
mobile partagée de Frontmail. Il récupère la clé depuis
GET /v1/public-config de l’API définie dans le provider
(mise en cache, elle n’est donc chargée qu’une fois) et affiche le widget Cloudflare dans
react-native-webview, sous la forme d’une page HTML intégrée à l’URL
https://mobile.frontmail.dev – rien n’est chargé depuis cette adresse.
import { useRef, useState } from 'react';import { Button } from 'react-native';import { TurnstileWebView, useSendEmail } from '@frontmail/react-native';import type { TurnstileWebViewHandle } from '@frontmail/react-native';
function ContactScreen() { const { send, status } = useSendEmail('svc_01J9…', 'tpl_contact'); const turnstile = useRef<TurnstileWebViewHandle>(null); const [token, setToken] = useState<string>();
const onSubmit = async () => { await send({ email, message }, { turnstileToken: token }); // Un jeton ne sert qu’une fois – demandez-en un nouveau pour la tentative suivante. setToken(undefined); turnstile.current?.reset(); };
return ( <> {/* …champs du formulaire… */} <TurnstileWebView ref={turnstile} onToken={setToken} onExpire={() => setToken(undefined)} onError={(e) => console.warn(e.message)} /> <Button title="Envoyer" disabled={!token || status === 'sending'} onPress={onSubmit} /> </> );}Props : onToken et, en option, siteKey, baseUrl, onError, onExpire, theme
(auto | light | dark), size (normal | compact | flexible), action, language,
style et webViewProps (transmises à la WebView).
La WebView est verrouillée sur le widget : elle ne charge que la page intégrée (baseUrl) et le
challenge Cloudflare, les liens du widget s’ouvrent dans le navigateur du système, et seuls les
messages de la page du widget parviennent à vos callbacks. webViewProps ne peut pas remplacer
source, originWhitelist, le garde de navigation, onMessage ni les réglages JavaScript / d’accès
aux fichiers.
Les requêtes de l’application n’ont pas d’en-tête Origin : Frontmail vérifie donc leurs jetons avec
sa clé secrète mobile partagée. Vous n’avez pas besoin de vos propres clés Turnstile pour cela – avec
Autoriser les applications mobiles activé, vous pouvez activer le CAPTCHA sur les modèles de
l’application même si votre organisation n’a pas encore de clés.
Votre propre clé de site
Section intitulée « Votre propre clé de site »Pour utiliser plutôt votre propre widget Turnstile, passez à la fois siteKey et baseUrl :
<TurnstileWebView ref={turnstile} siteKey="YOUR_TURNSTILE_SITE_KEY" baseUrl="https://example.com" onToken={setToken}/>Le SDK envoie alors automatiquement les jetons de ce widget avec turnstile_key: "org", et
Frontmail les vérifie avec la clé secrète de Sécurité → Protection contre les bots (Turnstile).
Un jeton est valable environ 5 minutes et ne peut être utilisé qu’une fois : appelez
ref.current.reset() après chaque envoi, réussi ou non.
limitRate et stockage
Section intitulée « limitRate et stockage »limitRate limite l’envoi sur l’appareil, p. ex. au plus un message toutes les 30 secondes. Il n’y a
pas de location.pathname en React Native : définissez donc toujours limitRate.id :
import AsyncStorage from '@react-native-async-storage/async-storage';import { asyncStorageProvider } from '@frontmail/react-native';
const frontmailOptions = { publicKey: 'pk_4f2a…', limitRate: { id: 'contact', throttle: 30_000 }, storageProvider: asyncStorageProvider(AsyncStorage),};Sans storageProvider, le SDK utilise AsyncStorage s’il est installé ; sinon, il ne garde la limite
qu’en mémoire (elle est réinitialisée au redémarrage de l’application) et affiche un avertissement en
développement. Utilisez memoryStorageProvider() pour choisir explicitement le stockage en mémoire.
limitRate ne protège que contre les appuis répétés accidentels – la vraie protection est côté
serveur (voir Sécurité).
getStatus
Section intitulée « getStatus »getStatus() du hook lit le statut de livraison du dernier message accepté. Pour tout le reste,
useFrontmail() renvoie le client du provider (send, getStatus) :
import { useFrontmail } from '@frontmail/react-native';
const client = useFrontmail();const status = await client.getStatus(messageId, { token });Nouvelles tentatives et idempotence
Section intitulée « Nouvelles tentatives et idempotence »Les erreurs réseau, les 5xx et les 429 sont relancés automatiquement, et toutes les tentatives
d’un même envoi réutilisent la même clé d’idempotence : une nouvelle tentative n’envoie donc jamais
l’e-mail deux fois. Les clés sont des UUID v4 – le SDK utilise crypto.randomUUID et se rabat sur
son propre générateur sous Hermes, qui ne le fournit pas. Voir
Nouvelles tentatives et idempotence.
Paramètres typés
Section intitulée « Paramètres typés »Avec les types générés (npx frontmail types étend
@frontmail/react-native), useSendEmail('svc_…', 'tpl_contact').send({...}) vérifie le type des
paramètres de ce modèle.
error est une FrontmailError avec code, status et message. La façon de traiter chaque code
est décrite dans Gestion des erreurs.
Sécurité
Section intitulée « Sécurité »- Ne placez jamais que la clé publique (
pk_…) dans une application. Tout ce qui se trouve dans un bundle d’application peut être extrait : une clé privée y serait comme publiée. Voir Clés publiques et privées. - Avec Autoriser les applications mobiles activé, les requêtes sans en-tête
Originsont acceptées de n’importe où – tout script qui connaît votre clé publique peut les envoyer. Appuyez-vous sur le rate limit par IP, sur Turnstile pour chaque modèle utilisé par l’application et sur la liste de blocage.
Projet d’exemple : examples/react-native.