React Native (@frontmail/react-native)
@frontmail/react-native permite que una app de iOS o Android envíe correos a través de tus
plantillas con la clave pública, sin necesidad de un backend propio. Funciona con Expo y con React
Native sin Expo (bare).
Instalación
Sección titulada «Instalación»Expo:
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webviewReact Native sin Expo:
npm install @frontmail/react-native @react-native-async-storage/async-storage react-native-webviewnpx pod-installLos dos paquetes adicionales son dependencias peer opcionales:
| Paquete | Necesario para |
|---|---|
@react-native-async-storage/async-storage |
Un limitRate que sobreviva a los reinicios de la app |
react-native-webview |
<TurnstileWebView> |
Permitir apps móviles
Sección titulada «Permitir apps móviles»Las apps nativas no envían cabecera Origin ni Referer, así que las solicitudes desde la app se
rechazan (tengas o no configurados sitios web permitidos) con
403 origin_not_allowed hasta que actives
Seguridad → Apps móviles → Permitir apps móviles en el panel y hagas clic en Guardar.
Consulta Ajustes de seguridad.
Las solicitudes desde sitios web se siguen comprobando contra la lista, así que el interruptor no debilita la protección de tus formularios web. El interruptor es necesario incluso con la lista vacía.
Provider y useSendEmail
Sección titulada «Provider y useSendEmail»Envuelve la app en FrontmailProvider y envía con useSendEmail. Mantén estable el objeto de
opciones: defínelo a nivel de módulo o con 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="Enviar" disabled={status === 'sending'} onPress={() => send({ email, message })} /> {status === 'sent' && <Text>¡Gracias!</Text>} {status === 'held' && <Text>Recibido: lo entregaremos en breve.</Text>} {error && <Text>{error.message}</Text>} </View> );}options acepta publicKey, apiUrl, retry, limitRate, storageProvider y blockList. No
se acepta privateKey. También puedes pasar un cliente existente:
<FrontmailProvider client={client}>.
useSendEmail(serviceId, templateId) devuelve { send, getStatus, status, error, result, reset }.
status pasa de idle → sending → sent | held | error. send(params, options?) se
resuelve con el resultado, o con undefined si hay un error (el error queda en error); nunca se
rechaza, así que no necesitas try/catch. reset() devuelve el hook a idle.
Para enviar en un idioma de la plantilla, pasa locale en las
opciones: send({ email, message }, { locale: 'de' }), por ejemplo con el idioma del dispositivo de
Intl.DateTimeFormat().resolvedOptions().locale. Los idiomas desconocidos recurren al mismo idioma
base y, si no, al idioma predeterminado de la plantilla; result.locale es el idioma usado.
Todas las solicitudes llevan la cabecera X-Frontmail-Client: @frontmail/react-native/<version>.
Turnstile
Sección titulada «Turnstile»Si la plantilla exige Turnstile, renderiza <TurnstileWebView> y pasa
el token a send. No necesitas site key: por defecto, el componente usa la clave móvil
compartida de Frontmail. La obtiene de GET /v1/public-config
de la API configurada en el provider (en caché, así que solo se carga una vez) y muestra el widget
de Cloudflare en react-native-webview como una página HTML incrustada con la URL
https://mobile.frontmail.dev; desde esa dirección no se carga nada.
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 }); // Los tokens son de un solo uso: obtén uno nuevo para el siguiente intento. setToken(undefined); turnstile.current?.reset(); };
return ( <> {/* …campos… */} <TurnstileWebView ref={turnstile} onToken={setToken} onExpire={() => setToken(undefined)} onError={(e) => console.warn(e.message)} /> <Button title="Enviar" disabled={!token || status === 'sending'} onPress={onSubmit} /> </> );}Props: onToken y, opcionalmente, siteKey, baseUrl, onError, onExpire, theme
(auto | light | dark), size (normal | compact | flexible), action, language,
style y webViewProps (se pasan al WebView).
El WebView está limitado al widget: solo carga la página incrustada (baseUrl) y el desafío de
Cloudflare, los enlaces dentro del widget se abren en el navegador del sistema y solo los mensajes
de la página del widget llegan a tus callbacks. webViewProps no puede sobrescribir source,
originWhitelist, la protección de navegación, onMessage ni los ajustes de JavaScript y de acceso
a archivos.
Las solicitudes de la app no llevan cabecera Origin, así que Frontmail verifica sus tokens con su
secreto móvil compartido. No necesitas tus propias claves de Turnstile para esto: con Permitir
apps móviles activado, puedes activar la CAPTCHA en las plantillas de la app aunque tu
organización todavía no tenga claves.
Tu propia site key
Sección titulada «Tu propia site key»Para usar en su lugar tu propio widget de Turnstile, pasa tanto siteKey como baseUrl:
<TurnstileWebView ref={turnstile} siteKey="YOUR_TURNSTILE_SITE_KEY" baseUrl="https://example.com" onToken={setToken}/>El SDK envía entonces automáticamente los tokens de este widget con turnstile_key: "org", y
Frontmail los verifica con la secret key de Seguridad → Protección contra bots (Turnstile).
Un token es válido durante unos 5 minutos y se puede usar una sola vez: llama a
ref.current.reset() después de cada envío, tenga éxito o no.
limitRate y almacenamiento
Sección titulada «limitRate y almacenamiento»limitRate limita los envíos en el dispositivo, p. ej. como máximo un mensaje cada 30 segundos. En
React Native no existe location.pathname, así que establece siempre 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),};Sin storageProvider, el SDK usa AsyncStorage cuando está instalado; si no, guarda el límite solo
en memoria (se reinicia al reiniciar la app) y muestra una advertencia en desarrollo. Usa
memoryStorageProvider() para elegir explícitamente el almacenamiento en memoria.
limitRate solo protege contra toques repetidos por accidente; la protección real está en el
servidor (consulta Seguridad).
getStatus
Sección titulada «getStatus»getStatus() del hook lee el estado de entrega del último mensaje aceptado. Para todo lo demás,
useFrontmail() devuelve el cliente del provider (send, getStatus):
import { useFrontmail } from '@frontmail/react-native';
const client = useFrontmail();const status = await client.getStatus(messageId, { token });Reintentos e idempotencia
Sección titulada «Reintentos e idempotencia»Los errores de red, los 5xx y los 429 se reintentan automáticamente, y todos los intentos de un
mismo envío reutilizan la misma clave de idempotencia, así que un reintento nunca envía el correo
dos veces. Las claves son UUID v4: el SDK usa crypto.randomUUID y, en Hermes, que no lo ofrece,
recurre a su propio generador. Consulta Reintentos e idempotencia.
Parámetros tipados
Sección titulada «Parámetros tipados»Con los tipos generados (npx frontmail types amplía
@frontmail/react-native), useSendEmail('svc_…', 'tpl_contact').send({...}) comprueba los tipos
de los parámetros de esa plantilla.
Errores
Sección titulada «Errores»error es un FrontmailError con code, status y message. Cómo tratar cada código se explica
en Gestión de errores.
Seguridad
Sección titulada «Seguridad»- En una app pon siempre solo la clave pública (
pk_…). Todo lo que hay en el bundle de una app se puede extraer, así que una clave privada ahí es como si estuviera publicada. Consulta Claves públicas y privadas. - Con Permitir apps móviles activado, las solicitudes sin cabecera
Originse aceptan desde cualquier lugar: cualquier script que conozca tu clave pública puede enviarlas. Apóyate en el límite de solicitudes por IP, en Turnstile en todas las plantillas que use la app y en la lista de bloqueo.
Proyecto de ejemplo: examples/react-native.