Anfrageformular in Android mit Kotlin erstellen – Angebotsanfrage per E-Mail
Ein Anfrageformular in Jetpack Compose mit Auswahl von Leistung und Budget sowie Termin, das Ihnen jede Anfrage per E-Mail schickt und dem Kunden automatisch eine Bestätigung sendet.
Aktualisiert:
In diesem Tutorial bauen Sie einen Bildschirm für Angebotsanfragen in Jetpack Compose. Der Kunde wählt eine Leistung und ein Budget, gibt einen Termin und eine Beschreibung ein; Sie erhalten eine strukturierte E-Mail, der Kunde eine automatische Bestätigung.
Sie brauchen: ein kostenloses Frontmail-Konto, ein Android-Projekt mit Compose und etwa zwanzig Minuten.
1. Postfach verbinden und Vorlagen anlegen
- E-Mail-Dienste → Dienst hinzufügen: Verbinden Sie Ihr Postfach oder Ihren Anbieter, Als Standard festlegen, Prüfen & speichern.
- Vorlagen → Aus der Galerie → Angebotsanfrage (Kategorie Geschäftlich) → Vorlage verwenden.
- Aus der Galerie → Automatische Antwort: Danke für Ihre Nachricht → Vorlage
verwenden; Standardwerte für
company_nameundresponse_timefestlegen und die Vorlage dann in der Anfragevorlage unter Einstellungen → Zustellung → Vorlage für automatische Antwort auswählen.
Die Anfragevorlage braucht name, email, service und details; phone, company, budget
und deadline (YYYY-MM-DD) sind optional. service und budget akzeptieren nur die Werte auf
dem Tab Parameter – verwenden Sie in der App dieselben Zeichenketten. Das Codebeispiel nutzt die
Werte der englischen Vorlage; eine auf Deutsch angelegte Vorlage hat Website, Onlineshop, Mobile
App, Beratung, Sonstiges und Unter 5.000 €, 5.000–20.000 €, 20.000–50.000 €, Über 50.000 €.
2. Mobile Apps erlauben und CAPTCHA aktivieren
- Sicherheit → Mobile Apps → Mobile Apps erlauben → Speichern.
- Anfragevorlage → Einstellungen → Schutz → CAPTCHA verlangen (Turnstile) (das gemeinsame mobile Widget von Frontmail, keine Schlüssel nötig).
3. SDK hinzufügen
// app/build.gradle.kts
dependencies {
implementation("dev.frontmail:frontmail:0.1.0")
}
Ergänzen Sie die Berechtigung INTERNET und kopieren Sie die Hilfsfunktionen showTurnstile /
resetTurnstile aus dem Turnstile-Abschnitt der Android-SDK-Dokumentation.
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
import java.time.LocalDate
val frontmail = Frontmail("pk_…")
val services = listOf("Website", "E-shop", "Mobile app", "Consulting", "Other")
val budgets = listOf("Under $5k", "$5k–20k", "$20k–50k", "Over $50k")
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Choices(options: List<String>, selected: String?, onSelect: (String?) -> Unit) {
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
options.forEach { option ->
FilterChip(
selected = option == selected,
onClick = { onSelect(if (option == selected) null else option) },
label = { Text(option) },
)
}
}
}
@Composable
fun QuoteScreen() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var service by remember { mutableStateOf<String?>(services.first()) }
var budget by remember { mutableStateOf<String?>(null) }
var deadline by remember { mutableStateOf("") }
var details 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())
Text("What do you need?")
Choices(services, service) { service = it }
Text("Budget (optional)")
Choices(budgets, budget) { budget = it }
OutlinedTextField(deadline, { deadline = it }, label = { Text("Deadline (YYYY-MM-DD, optional)") }, modifier = Modifier.fillMaxWidth())
OutlinedTextField(details, { details = it }, label = { Text("Project details") }, 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 && service != null,
onClick = {
scope.launch {
try {
frontmail.send(
templateId = "tpl_…",
params = mapOf(
"name" to name,
"email" to email,
"service" to service,
"budget" to budget,
"deadline" to runCatching { LocalDate.parse(deadline).toString() }.getOrNull(),
"details" to details,
),
options = SendOptions(turnstileToken = token, turnstileKey = widget?.turnstileKey),
)
info = "Thanks! We’ve emailed you a confirmation."
} catch (e: FrontmailException) {
info = e.message ?: "Sending failed"
} finally {
token = null // a token works only once
webView?.resetTurnstile()
}
}
},
) { Text("Request a quote") }
Text(info)
}
}
null-Werte (kein Budget, kein gültiger Termin) fehlen in der E-Mail einfach. Wenn Sie möchten,
ersetzen Sie das Textfeld durch einen Material-3-DatePicker – senden Sie das Datum nur als
YYYY-MM-DD.
5. Testen
Senden Sie eine Anfrage mit Ihrer eigenen E-Mail-Adresse: Sie erhalten die Anfrage und kurz darauf die Bestätigung. Beide erscheinen mit ihrem Zustellverlauf unter Verlauf.
Eine Anfrage mit Bestätigung kostet zwei Credits. Sind Ihre Credits aufgebraucht, werden Anfragen als
HELDangenommen und nach dem Aufladen automatisch versendet.
Nächste Schritte
- Aktivieren Sie Kontakte sammeln in den Einstellungen der Vorlage, um eine Liste aller Interessenten zu führen.
- Hängen Sie Dateien an (zum Beispiel ein Briefing als PDF) mit
SendOptions(attachments = …)– siehe SDK-Dokumentation.


