Aller au contenu

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

Expo :

Fenêtre de terminal
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview

React Native bare :

Fenêtre de terminal
npm install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview
npx pod-install

Les 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>

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.

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

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.

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 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() 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 });

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.

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.

  • 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 Origin sont 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.