Przejdź do głównej zawartości

React Native (@frontmail/react-native)

@frontmail/react-native pozwala aplikacji na iOS lub Androida wysyłać e-maile przez twoje szablony z kluczem publicznym – bez własnego backendu. Działa z Expo i z czystym React Native.

Expo:

Okno terminala
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview

Czysty React Native:

Okno terminala
npm install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview
npx pod-install

Dwa dodatkowe pakiety to opcjonalne peer dependencies:

Pakiet Potrzebny do
@react-native-async-storage/async-storage limitRate, który przetrwa restart aplikacji
react-native-webview <TurnstileWebView>

Aplikacje natywne nie wysyłają nagłówka Origin ani Referer, więc żądania z aplikacji są odrzucane (niezależnie od tego, czy masz ustawione dozwolone domeny) z błędem 403 origin_not_allowed, dopóki nie włączysz w panelu Bezpieczeństwo → Aplikacje mobilne → Zezwól na aplikacje mobilne i nie klikniesz Zapisz. Zobacz Ustawienia bezpieczeństwa.

Żądania ze stron są nadal sprawdzane z listą, więc przełącznik nie osłabia ochrony twoich formularzy na stronach. Przełącznik jest wymagany nawet przy pustej liście.

Owiń aplikację w FrontmailProvider i wysyłaj przez useSendEmail. Obiekt z opcjami powinien być stabilny – zdefiniuj go na poziomie modułu albo przez 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="Wyślij" disabled={status === 'sending'} onPress={() => send({ email, message })} />
{status === 'sent' && <Text>Dziękujemy!</Text>}
{status === 'held' && <Text>Otrzymaliśmy – wkrótce ją dostarczymy.</Text>}
{error && <Text>{error.message}</Text>}
</View>
);
}

options przyjmuje publicKey, apiUrl, retry, limitRate, storageProvider i blockList. privateKey nie jest akceptowany. Możesz też przekazać istniejącego klienta: <FrontmailProvider client={client}>.

useSendEmail(serviceId, templateId) zwraca { send, getStatus, status, error, result, reset }. status przechodzi idle → sending → sent | held | error. send(params, options?) zwraca wynik albo – przy błędzie – undefined (błąd jest w error); nigdy nie odrzuca promise’a, więc nie potrzebujesz try/catch. reset() przywraca hook do stanu idle.

Żeby wysłać e-mail w jednym z języków szablonu, przekaż locale w opcjach: send({ email, message }, { locale: 'de' }) – na przykład język urządzenia z Intl.DateTimeFormat().resolvedOptions().locale. Nieznane języki przechodzą na ten sam język bazowy, a potem na język domyślny szablonu; result.locale to użyty język.

Każde żądanie niesie nagłówek X-Frontmail-Client: @frontmail/react-native/<version>.

Jeśli szablon wymaga Turnstile, wyrenderuj <TurnstileWebView> i przekaż token do send. Nie potrzebujesz site key: domyślnie komponent używa wspólnego mobilnego klucza Frontmail. Pobiera go z GET /v1/public-config API ustawionego w providerze (z cache, więc tylko raz) i pokazuje widget Cloudflare w react-native-webview jako osadzoną stronę HTML z adresem https://mobile.frontmail.dev – nic nie jest pobierane z tego adresu.

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 });
// Token można użyć tylko raz – przy następnej próbie potrzebny jest nowy.
setToken(undefined);
turnstile.current?.reset();
};
return (
<>
{/* …pola… */}
<TurnstileWebView
ref={turnstile}
onToken={setToken}
onExpire={() => setToken(undefined)}
onError={(e) => console.warn(e.message)}
/>
<Button title="Wyślij" disabled={!token || status === 'sending'} onPress={onSubmit} />
</>
);
}

Propsy: onToken oraz opcjonalnie siteKey, baseUrl, onError, onExpire, theme (auto | light | dark), size (normal | compact | flexible), action, language, style i webViewProps (przekazywane do WebView).

WebView jest ograniczony do widgetu: ładuje tylko osadzoną stronę (baseUrl) i challenge Cloudflare, linki wewnątrz widgetu otwierają się w przeglądarce systemowej, a do twoich callbacków docierają tylko wiadomości ze strony widgetu. webViewProps nie może nadpisać source, originWhitelist, strażnika nawigacji, onMessage ani ustawień JavaScriptu i dostępu do plików.

Żądania z aplikacji nie mają nagłówka Origin, więc Frontmail weryfikuje ich tokeny wspólnym mobilnym kluczem tajnym. Nie potrzebujesz do tego własnych kluczy Turnstile – przy włączonym Zezwól na aplikacje mobilne możesz włączyć CAPTCHA w szablonach używanych przez aplikację, nawet jeśli twoja organizacja nie ma jeszcze kluczy.

Żeby zamiast tego użyć własnego widgetu Turnstile, przekaż zarówno siteKey, jak i baseUrl:

<TurnstileWebView
ref={turnstile}
siteKey="YOUR_TURNSTILE_SITE_KEY"
baseUrl="https://example.com"
onToken={setToken}
/>

SDK wysyła wtedy tokeny z tego widgetu automatycznie z turnstile_key: "org", a Frontmail weryfikuje je kluczem tajnym z Bezpieczeństwo → Ochrona przed botami (Turnstile).

Token jest ważny około 5 minut i można go użyć tylko raz: po każdej wysyłce, udanej lub nie, wywołaj ref.current.reset().

limitRate ogranicza wysyłanie na urządzeniu, np. do najwyżej jednej wiadomości na 30 sekund. W React Native nie ma location.pathname, więc zawsze ustawiaj 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),
};

Bez storageProvider SDK używa AsyncStorage, jeśli jest zainstalowany; w przeciwnym razie trzyma limit tylko w pamięci (resetuje się po restarcie aplikacji) i w trybie deweloperskim wypisuje ostrzeżenie. Użyj memoryStorageProvider(), żeby jawnie wybrać przechowywanie w pamięci.

limitRate chroni tylko przed przypadkowym wielokrotnym stuknięciem – prawdziwa ochrona jest na serwerze (zobacz Bezpieczeństwo).

getStatus() z hooka odczytuje status doręczenia ostatniej przyjętej wiadomości. Do wszystkiego innego useFrontmail() zwraca klienta z providera (send, getStatus):

import { useFrontmail } from '@frontmail/react-native';
const client = useFrontmail();
const status = await client.getStatus(messageId, { token });

Błędy sieci, 5xx i 429 są ponawiane automatycznie, a wszystkie próby jednej wysyłki używają tego samego klucza idempotencji, więc ponowienie nigdy nie wyśle e-maila dwa razy. Klucze to UUID v4 – SDK używa crypto.randomUUID, a na Hermesie, który go nie udostępnia, korzysta z własnego generatora. Zobacz Ponawianie i idempotencja.

Z wygenerowanymi typami (npx frontmail types rozszerza @frontmail/react-native) useSendEmail('svc_…', 'tpl_contact').send({...}) sprawdza typy parametrów danego szablonu.

error to FrontmailError z code, status i message. Jak obsłużyć poszczególne kody, opisuje strona Obsługa błędów.

  • Do aplikacji wkładaj tylko klucz publiczny (pk_…). Wszystko w paczce aplikacji da się wyciągnąć, więc klucz prywatny w niej jest tak dobry jak opublikowany. Zobacz Klucze publiczne i prywatne.
  • Przy włączonym Zezwól na aplikacje mobilne żądania bez nagłówka Origin są przyjmowane skądkolwiek – może je wysłać każdy skrypt, który zna twój klucz publiczny. Polegaj na limicie na IP, Turnstile w każdym szablonie używanym przez aplikację i liście blokad.

Przykładowy projekt: examples/react-native.