React Native (@frontmail/react-native)
@frontmail/react-native permette a un’app iOS o Android di inviare email tramite i tuoi modelli
con la chiave pubblica – senza bisogno di un tuo backend. Funziona con Expo e con React Native
«bare».
Installazione
Sezione intitolata “Installazione”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-installI due pacchetti aggiuntivi sono peer dependency facoltative:
| Pacchetto | Serve per |
|---|---|
@react-native-async-storage/async-storage |
limitRate che sopravvive ai riavvii dell’app |
react-native-webview |
<TurnstileWebView> |
Consenti app mobile
Sezione intitolata “Consenti app mobile”Le app native non inviano l’header Origin né Referer, quindi le richieste dall’app vengono
rifiutate (che tu abbia impostato o meno dei siti consentiti) con
403 origin_not_allowed finché non attivi
Sicurezza → App mobile → Consenti app mobile nella dashboard e fai clic su Salva. Vedi
Impostazioni di sicurezza.
Le richieste dai siti web vengono comunque confrontate con la lista, quindi l’interruttore non indebolisce la protezione dei tuoi moduli web. L’interruttore è necessario anche con la lista vuota.
Provider e useSendEmail
Sezione intitolata “Provider e useSendEmail”Avvolgi l’app in FrontmailProvider e invia con useSendEmail. Mantieni stabile l’oggetto delle
opzioni – definiscilo a livello di modulo 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="Invia" disabled={status === 'sending'} onPress={() => send({ email, message })} /> {status === 'sent' && <Text>Grazie!</Text>} {status === 'held' && <Text>Ricevuto – verrà recapitato a breve.</Text>} {error && <Text>{error.message}</Text>} </View> );}options accetta publicKey, apiUrl, retry, limitRate, storageProvider e blockList.
Una privateKey non è accettata. In alternativa, passa un client esistente:
<FrontmailProvider client={client}>.
useSendEmail(serviceId, templateId) restituisce { send, getStatus, status, error, result, reset }.
status è idle → sending → sent | held | error. send(params, options?) si risolve con il
risultato oppure con undefined in caso di errore (l’errore è in error) – non viene mai
rifiutata, quindi non ti serve try/catch. reset() riporta l’hook a idle.
Per inviare in una lingua del modello, passa locale nelle opzioni:
send({ email, message }, { locale: 'de' }) – ad esempio la lingua del dispositivo da
Intl.DateTimeFormat().resolvedOptions().locale. Le lingue sconosciute ripiegano sulla stessa
lingua di base, poi sulla lingua predefinita del modello; result.locale è la lingua usata.
Ogni richiesta porta l’header X-Frontmail-Client: @frontmail/react-native/<version>.
Turnstile
Sezione intitolata “Turnstile”Se il modello richiede Turnstile, mostra <TurnstileWebView> e passa il
token a send. Non ti serve una chiave del sito: per impostazione predefinita il componente usa
la chiave mobile condivisa di Frontmail. La recupera da
GET /v1/public-config dell’API impostata nel provider (con
cache, quindi viene caricata una volta sola) e mostra il widget di Cloudflare in
react-native-webview come pagina HTML inline con l’URL https://mobile.frontmail.dev – da
quell’indirizzo non viene caricato nulla.
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 }); // I token sono monouso – prendine uno nuovo per il tentativo successivo. setToken(undefined); turnstile.current?.reset(); };
return ( <> {/* …campi… */} <TurnstileWebView ref={turnstile} onToken={setToken} onExpire={() => setToken(undefined)} onError={(e) => console.warn(e.message)} /> <Button title="Invia" disabled={!token || status === 'sending'} onPress={onSubmit} /> </> );}Props: onToken e, facoltativamente, siteKey, baseUrl, onError, onExpire, theme
(auto | light | dark), size (normal | compact | flexible), action, language,
style e webViewProps (passate alla WebView).
La WebView è vincolata al widget: carica solo la pagina inline (baseUrl) e la challenge di
Cloudflare, i link all’interno del widget si aprono nel browser di sistema e solo i messaggi della
pagina del widget arrivano alle tue callback. webViewProps non può sovrascrivere source,
originWhitelist, il controllo della navigazione, onMessage né le impostazioni di JavaScript e
di accesso ai file.
Le richieste dall’app non hanno l’header Origin, quindi Frontmail verifica i loro token con la
sua chiave segreta mobile condivisa. Per questo non ti servono chiavi Turnstile tue – con
Consenti app mobile attivo puoi attivare il CAPTCHA sui modelli dell’app anche se la tua
organizzazione non ha ancora chiavi.
La tua chiave del sito
Sezione intitolata “La tua chiave del sito”Per usare invece il tuo widget Turnstile, passa sia siteKey sia baseUrl:
<TurnstileWebView ref={turnstile} siteKey="YOUR_TURNSTILE_SITE_KEY" baseUrl="https://example.com" onToken={setToken}/>L’SDK invia allora automaticamente i token di questo widget con turnstile_key: "org", e Frontmail
li verifica con la chiave segreta impostata in Sicurezza → Protezione dai bot (Turnstile).
Un token è valido per circa 5 minuti e può essere usato una sola volta: chiama
ref.current.reset() dopo ogni invio, riuscito o no.
limitRate e archiviazione
Sezione intitolata “limitRate e archiviazione”limitRate limita gli invii sul dispositivo, ad es. al massimo un messaggio ogni 30 secondi. In
React Native non esiste location.pathname, quindi imposta sempre 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),};Senza storageProvider, l’SDK usa AsyncStorage quando è installato; altrimenti tiene il limite
solo in memoria (si azzera al riavvio dell’app) e in sviluppo stampa un avviso. Usa
memoryStorageProvider() per scegliere esplicitamente l’archiviazione in memoria.
limitRate protegge solo dai tocchi ripetuti per errore – la vera protezione è sul server (vedi
Sicurezza).
getStatus
Sezione intitolata “getStatus”getStatus() dell’hook legge lo stato di recapito dell’ultimo messaggio accettato. Per tutto il
resto, useFrontmail() restituisce il client del provider (send, getStatus):
import { useFrontmail } from '@frontmail/react-native';
const client = useFrontmail();const status = await client.getStatus(messageId, { token });Tentativi e idempotenza
Sezione intitolata “Tentativi e idempotenza”Gli errori di rete, i 5xx e i 429 vengono ripetuti automaticamente, e tutti i tentativi di un
invio riutilizzano la stessa chiave di idempotenza, quindi un nuovo tentativo non invia mai l’email
due volte. Le chiavi sono UUID v4 – l’SDK usa crypto.randomUUID e, su Hermes, che non lo fornisce,
ricorre a un proprio generatore. Vedi Tentativi e idempotenza.
Parametri tipizzati
Sezione intitolata “Parametri tipizzati”Con i tipi generati (npx frontmail types estende
@frontmail/react-native), useSendEmail('svc_…', 'tpl_contact').send({...}) controlla i tipi dei
parametri di quel modello.
error è un FrontmailError con code, status e message. Come gestire ciascun codice è
spiegato in Gestione degli errori.
Sicurezza
Sezione intitolata “Sicurezza”- In un’app metti sempre e solo la chiave pubblica (
pk_…). Tutto ciò che sta nel bundle di un’app può essere estratto, quindi una chiave privata lì dentro è come se fosse pubblicata. Vedi Chiavi pubbliche e private. - Con Consenti app mobile attivo, le richieste senza header
Originvengono accettate da qualsiasi parte – qualunque script che conosca la tua chiave pubblica può inviarle. Affidati al rate limit per IP, a Turnstile su ogni modello usato dall’app e alla lista dei bloccati.
Progetto di esempio: examples/react-native.