Zum Inhalt springen

React Native (@frontmail/react-native)

Mit @frontmail/react-native sendet eine iOS- oder Android-App E-Mails über Ihre Vorlagen mit dem öffentlichen Schlüssel – ganz ohne eigenes Backend. Es funktioniert mit Expo und mit Bare React Native.

Expo:

Terminal-Fenster
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview

Bare React Native:

Terminal-Fenster
npm install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview
npx pod-install

Die beiden zusätzlichen Pakete sind optionale Peer-Dependencies:

Paket Benötigt für
@react-native-async-storage/async-storage limitRate, das App-Neustarts übersteht
react-native-webview <TurnstileWebView>

Native Apps senden keinen Origin- oder Referer-Header, daher werden Anfragen aus der App (egal ob Sie erlaubte Websites eingetragen haben oder nicht) mit 403 origin_not_allowed abgelehnt, bis Sie im Dashboard Sicherheit → Mobile Apps → Mobile Apps erlauben einschalten und auf Speichern klicken. Siehe Sicherheitseinstellungen.

Anfragen von Websites werden weiterhin mit der Liste abgeglichen – der Schalter schwächt also den Schutz Ihrer Webformulare nicht. Er ist auch bei leerer Liste erforderlich.

Umschließen Sie die App mit FrontmailProvider und senden Sie mit useSendEmail. Halten Sie das Optionsobjekt stabil – definieren Sie es auf Modulebene oder mit 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="Senden" disabled={status === 'sending'} onPress={() => send({ email, message })} />
{status === 'sent' && <Text>Vielen Dank!</Text>}
{status === 'held' && <Text>Erhalten – wird in Kürze zugestellt.</Text>}
{error && <Text>{error.message}</Text>}
</View>
);
}

options akzeptiert publicKey, apiUrl, retry, limitRate, storageProvider und blockList. Ein privateKey wird nicht akzeptiert. Alternativ übergeben Sie einen vorhandenen Client: <FrontmailProvider client={client}>.

useSendEmail(serviceId, templateId) liefert { send, getStatus, status, error, result, reset }. status durchläuft idle → sending → sent | held | error. send(params, options?) liefert das Ergebnis oder bei einem Fehler undefined (der Fehler steht in error) – es wird nie abgelehnt, Sie brauchen also kein try/catch. reset() setzt den Hook auf idle zurück.

Um in einer Sprache der Vorlage zu senden, übergeben Sie locale in den Optionen: send({ email, message }, { locale: 'de' }) – zum Beispiel die Sprache des Geräts aus Intl.DateTimeFormat().resolvedOptions().locale. Unbekannte Sprachen fallen auf dieselbe Basissprache zurück, danach auf die Standardsprache der Vorlage; result.locale ist die verwendete Sprache.

Jede Anfrage trägt den Header X-Frontmail-Client: @frontmail/react-native/<version>.

Verlangt die Vorlage Turnstile, rendern Sie <TurnstileWebView> und übergeben den Token an send. Einen Site-Key brauchen Sie nicht: Standardmäßig verwendet die Komponente den gemeinsamen mobilen Schlüssel von Frontmail. Sie holt ihn von GET /v1/public-config der im Provider eingestellten API (zwischengespeichert, also nur einmal geladen) und zeigt das Cloudflare-Widget in react-native-webview als Inline-HTML-Seite mit der URL https://mobile.frontmail.dev an – von dieser Adresse wird nichts geladen.

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 });
// Tokens sind nur einmal gültig – für den nächsten Versuch einen neuen holen.
setToken(undefined);
turnstile.current?.reset();
};
return (
<>
{/* …Formularfelder… */}
<TurnstileWebView
ref={turnstile}
onToken={setToken}
onExpire={() => setToken(undefined)}
onError={(e) => console.warn(e.message)}
/>
<Button title="Senden" disabled={!token || status === 'sending'} onPress={onSubmit} />
</>
);
}

Props: onToken und optional siteKey, baseUrl, onError, onExpire, theme (auto | light | dark), size (normal | compact | flexible), action, language, style sowie webViewProps (werden an die WebView durchgereicht).

Die WebView ist auf das Widget beschränkt: Sie lädt nur die Inline-Seite (baseUrl) und die Challenge von Cloudflare, Links im Widget öffnen sich im Systembrowser, und nur Nachrichten der Widget-Seite erreichen Ihre Callbacks. webViewProps kann source, originWhitelist, den Navigationsschutz, onMessage und die Einstellungen für JavaScript und Dateizugriff nicht überschreiben.

App-Anfragen haben keinen Origin-Header, daher prüft Frontmail ihre Tokens mit seinem gemeinsamen mobilen Secret. Eigene Turnstile-Schlüssel brauchen Sie dafür nicht – mit aktiviertem Mobile Apps erlauben können Sie das CAPTCHA für die Vorlagen der App einschalten, auch wenn Ihre Organisation noch keine Schlüssel hat.

Um stattdessen Ihr eigenes Turnstile-Widget zu verwenden, übergeben Sie siteKey und baseUrl:

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

Das SDK sendet Tokens aus diesem Widget dann automatisch mit turnstile_key: "org", und Frontmail prüft sie mit dem geheimen Schlüssel aus Sicherheit → Bot-Schutz (Turnstile).

Ein Token ist etwa 5 Minuten gültig und kann einmal verwendet werden: Rufen Sie nach jedem Versand ref.current.reset() auf, ob erfolgreich oder nicht.

limitRate drosselt das Senden auf dem Gerät, z. B. auf höchstens eine Nachricht pro 30 Sekunden. In React Native gibt es kein location.pathname, setzen Sie daher immer 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),
};

Ohne storageProvider nutzt das SDK AsyncStorage, sofern installiert; andernfalls hält es das Limit nur im Speicher (es wird beim Neustart der App zurückgesetzt) und gibt in der Entwicklung eine Warnung aus. Mit memoryStorageProvider() wählen Sie den Speicher im Arbeitsspeicher ausdrücklich.

limitRate schützt nur vor versehentlich wiederholtem Tippen – der eigentliche Schutz liegt auf dem Server (siehe Sicherheit).

getStatus() aus dem Hook liest den Zustellstatus der zuletzt angenommenen Nachricht. Für alles andere liefert useFrontmail() den Client aus dem Provider (send, getStatus):

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

Netzwerkfehler, 5xx und 429 werden automatisch wiederholt, und alle Versuche einer Sendung verwenden denselben Idempotenzschlüssel – eine Wiederholung sendet die E-Mail also nie doppelt. Die Schlüssel sind UUID v4 – das SDK nutzt crypto.randomUUID und greift unter Hermes, das diese Funktion nicht bietet, auf einen eigenen Generator zurück. Siehe Wiederholungen und Idempotenz.

Mit generierten Typen (npx frontmail types erweitert @frontmail/react-native) prüft useSendEmail('svc_…', 'tpl_contact').send({...}) die Parameter dieser Vorlage auf Typfehler.

error ist ein FrontmailError mit code, status und message. Wie Sie mit den einzelnen Codes umgehen, steht unter Fehlerbehandlung.

  • Bauen Sie in eine App immer nur den öffentlichen Schlüssel (pk_…) ein. Alles in einem App-Bundle lässt sich extrahieren, ein privater Schlüssel darin ist also so gut wie veröffentlicht. Siehe Öffentliche und private Schlüssel.
  • Mit aktiviertem Mobile Apps erlauben werden Anfragen ohne Origin-Header von überall angenommen – jedes Skript, das Ihren öffentlichen Schlüssel kennt, kann sie senden. Verlassen Sie sich auf das Rate Limit pro IP, Turnstile für jede Vorlage, die die App verwendet, und die Blockliste.

Beispielprojekt: examples/react-native.