Comment créer un formulaire de contact Android avec Kotlin et Jetpack Compose
Un écran de contact Jetpack Compose qui vous envoie chaque message par e-mail via votre propre boîte mail – sans backend, sans mot de passe SMTP dans l’APK. Avec le SDK Kotlin de Frontmail.
Mis à jour:
Vous allez ajouter un écran de contact à une application Android écrite en Kotlin avec Jetpack Compose. L’application ne contient qu’une clé publique – l’e-mail est envoyé par Frontmail via votre boîte mail, donc aucun mot de passe SMTP ne se retrouve dans l’APK, où n’importe qui pourrait l’extraire.
Il vous faut : un compte Frontmail gratuit, un projet Android avec Compose et environ quinze minutes.
1. Connectez votre boîte mail et créez le modèle
- Services d’envoi → Ajouter un service : connectez Gmail, Outlook, SMTP ou un fournisseur transactionnel, cochez Définir par défaut, puis Vérifier et enregistrer.
- Modèles → Depuis la galerie → Formulaire de contact → Utiliser ce modèle.
Paramètres obligatoires :
name,email,message; facultatifs :phone,topic. - Copiez l’ID du modèle (
tpl_…) depuis l’onglet Extraits de code.
2. Autorisez les applications mobiles
- Sécurité → Applications mobiles → Autoriser les applications mobiles →
Enregistrer. Les applications n’envoient pas d’en-tête
Origin: tant que ce n’est pas fait, l’API les rejette avecorigin_not_allowed. - Modèle → Paramètres → Protection → Exiger un CAPTCHA (Turnstile). Les applications utilisent le widget Turnstile partagé de Frontmail – aucune clé Cloudflare n’est nécessaire.
3. Ajoutez le SDK
// app/build.gradle.kts
dependencies {
implementation("dev.frontmail:frontmail:0.1.0")
}
Ajoutez <uses-permission android:name="android.permission.INTERNET" /> à AndroidManifest.xml.
Copiez ensuite les helpers WebView.showTurnstile(…) et WebView.resetTurnstile() depuis la
section Turnstile de la documentation du SDK Android dans votre projet – un seul fichier, qui vous
appartient.
4. Créez l’écran
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)
}
}
Les erreurs réseau, les délais dépassés et les réponses 5xx sont relancés automatiquement avec la
même clé d’idempotence : un signal faible ne vous enverra jamais deux fois le même message.
5. Testez
Lancez l’application sur un appareil ou un émulateur, remplissez le formulaire et touchez Send. L’e-mail arrive dans votre boîte de réception et apparaît dans l’Historique du tableau de bord.
origin_not_allowed– Autoriser les applications mobiles est désactivé ou n’a pas été enregistré.captcha_required– pas encore de jeton Turnstile ; le bouton reste désactivé jusqu’à la fin de la vérification.invalid_template_params– un champ obligatoire est vide ou l’e-mail n’est pas valide.
Plus de crédits ? Le message est accepté avec le statut
HELDet envoyé automatiquement dès que vous rechargez – consultezresult.statussi vous souhaitez en informer l’utilisateur.
Étapes suivantes
- Ajoutez le modèle Réponse automatique : merci pour votre message depuis la galerie pour confirmer le message à l’utilisateur.
- Passez
SendOptions(locale = Locale.getDefault().toLanguageTag())pour utiliser la variante linguistique du modèle correspondant à la langue du téléphone.


