Kontaktformular in Android mit Kotlin und Jetpack Compose erstellen
Ein Kontaktbildschirm in Jetpack Compose, der Ihnen jede Nachricht über Ihr eigenes Postfach schickt – ohne Backend und ohne SMTP-Passwort in der APK. Mit dem Frontmail-Kotlin-SDK.
Aktualisiert:
Sie fügen einer Android-App in Kotlin und Jetpack Compose einen Kontaktbildschirm hinzu. Die App enthält nur einen öffentlichen Schlüssel – die E-Mail versendet Frontmail über Ihr Postfach, sodass kein SMTP-Passwort in der APK landet, wo es jeder auslesen könnte.
Sie brauchen: ein kostenloses Frontmail-Konto, ein Android-Projekt mit Compose und etwa fünfzehn Minuten.
1. Postfach verbinden und Vorlage anlegen
- E-Mail-Dienste → Dienst hinzufügen: Verbinden Sie Gmail, Outlook, SMTP oder einen Transaktions-Anbieter, aktivieren Sie Als Standard festlegen, dann Prüfen & speichern.
- Vorlagen → Aus der Galerie → Kontaktformular → Vorlage verwenden.
Pflichtparameter:
name,email,message; optional:phone,topic. - Kopieren Sie die Vorlagen-ID (
tpl_…) aus dem Tab Snippets.
2. Mobile Apps erlauben
- Sicherheit → Mobile Apps → Mobile Apps erlauben → Speichern. Apps senden keinen
Origin-Header, deshalb lehnt die API sie bis dahin mitorigin_not_allowedab. - Vorlage → Einstellungen → Schutz → CAPTCHA verlangen (Turnstile). Apps nutzen das gemeinsame Turnstile-Widget von Frontmail – keine Cloudflare-Schlüssel nötig.
3. SDK hinzufügen
// app/build.gradle.kts
dependencies {
implementation("dev.frontmail:frontmail:0.1.0")
}
Ergänzen Sie <uses-permission android:name="android.permission.INTERNET" /> in
AndroidManifest.xml. Kopieren Sie dann die Hilfsfunktionen WebView.showTurnstile(…) und
WebView.resetTurnstile() aus dem Turnstile-Abschnitt der Android-SDK-Dokumentation in Ihr
Projekt – eine einzige Datei, die Ihnen gehört.
4. Bildschirm bauen
import android.webkit.WebView
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import dev.frontmail.Frontmail
import dev.frontmail.FrontmailException
import dev.frontmail.SendOptions
import dev.frontmail.Turnstile
import dev.frontmail.TurnstileWidget
import kotlinx.coroutines.launch
val frontmail = Frontmail("pk_…") // create once, reuse (thread-safe)
@Composable
fun ContactScreen() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var message by remember { mutableStateOf("") }
var widget by remember { mutableStateOf<TurnstileWidget?>(null) }
var token by remember { mutableStateOf<String?>(null) }
var webView by remember { mutableStateOf<WebView?>(null) }
var info by remember { mutableStateOf("") }
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) { widget = Turnstile.sharedWidget() }
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
OutlinedTextField(name, { name = it }, label = { Text("Name") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(email, { email = it }, label = { Text("Email") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(message, { message = it }, label = { Text("Message") }, minLines = 4, modifier = Modifier.fillMaxWidth())
widget?.let { w ->
AndroidView(
factory = { context ->
WebView(context).apply {
showTurnstile(w, onToken = { token = it }, onExpire = { token = null })
webView = this
}
},
modifier = Modifier.fillMaxWidth().height(70.dp),
)
}
Button(
enabled = token != null,
onClick = {
scope.launch {
try {
frontmail.send(
templateId = "tpl_…",
params = mapOf("name" to name, "email" to email, "message" to message),
options = SendOptions(turnstileToken = token, turnstileKey = widget?.turnstileKey),
)
info = "Thank you! We’ll get back to you soon."
} catch (e: FrontmailException) {
info = e.message ?: "Sending failed"
} finally {
token = null // a token works only once
webView?.resetTurnstile()
}
}
},
) { Text("Send") }
Text(info)
}
}
Netzwerkfehler, Timeouts und 5xx-Antworten werden automatisch mit demselben Idempotenzschlüssel
wiederholt – bei schwachem Empfang bekommen Sie dieselbe Nachricht also nie doppelt.
5. Testen
Starten Sie die App auf einem Gerät oder Emulator, füllen Sie das Formular aus und tippen Sie auf Send. Die E-Mail kommt in Ihrem Postfach an und steht im Dashboard unter Verlauf.
origin_not_allowed– Mobile Apps erlauben ist ausgeschaltet oder wurde nicht gespeichert.captcha_required– noch kein Turnstile-Token; der Button bleibt deaktiviert, bis die Prüfung abgeschlossen ist.invalid_template_params– ein Pflichtfeld ist leer oder die E-Mail-Adresse ungültig.
Keine Credits mehr? Die Nachricht wird mit dem Status
HELDangenommen und nach dem Aufladen automatisch versendet – prüfen Sieresult.status, wenn Sie den Nutzer informieren möchten.
Nächste Schritte
- Bestätigen Sie dem Nutzer die Nachricht mit der Vorlage Automatische Antwort: Danke für Ihre Nachricht aus der Galerie.
- Übergeben Sie
SendOptions(locale = Locale.getDefault().toLanguageTag()), um die Sprachvariante der Vorlage für die Sprache des Telefons zu verwenden.


