Comment créer un formulaire de contact iOS avec Swift et SwiftUI
Un formulaire de contact SwiftUI qui vous envoie chaque message par e-mail via votre propre boîte mail – sans backend ni mot de passe SMTP dans l’application. Avec le paquet Swift de Frontmail.
Mis à jour:
Vous allez ajouter un formulaire de contact à une application iOS avec SwiftUI et le paquet Swift de Frontmail. L’application ne contient qu’une clé publique ; Frontmail envoie l’e-mail via votre boîte mail : pas de mot de passe SMTP dans l’application, pas de serveur à maintenir.
Il vous faut : un compte Frontmail gratuit, un projet Xcode avec SwiftUI 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: d’ici là, l’API répondorigin_not_allowed. - Modèle → Paramètres → Protection → Exiger un CAPTCHA (Turnstile). Les applications utilisent le widget Turnstile partagé de Frontmail ; vous n’avez pas besoin de clés Cloudflare.
3. Ajoutez le paquet
Dans Xcode, choisissez File → Add Package Dependencies…, saisissez
https://github.com/frontmail-dev/frontmail-swift et ajoutez la bibliothèque Frontmail à la
cible de votre application. Copiez ensuite la TurnstileView depuis la section Turnstile de la
documentation du SDK iOS dans votre projet – un petit UIViewRepresentable autour de WKWebView,
qui vous appartient.
4. Créez le formulaire
import SwiftUI
import Frontmail
let frontmail = try! Frontmail(publicKey: "pk_…") // create once, share
struct ContactMessage: Encodable {
let name: String
let email: String
let message: String
}
struct ContactView: View {
@State private var name = ""
@State private var email = ""
@State private var message = ""
@State private var widget: Turnstile.Widget?
@State private var token: String?
@State private var resetID = 0
@State private var info = ""
var body: some View {
Form {
TextField("Name", text: $name)
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
TextField("Message", text: $message, axis: .vertical)
.lineLimit(4...8)
if let widget {
TurnstileView(widget: widget, size: .flexible, resetID: resetID) { event in
if case let .token(t) = event { token = t } else { token = nil }
}
.frame(height: Turnstile.Size.flexible.recommendedHeight)
}
Button("Send") { Task { await send() } }
.disabled(token == nil)
Text(info)
}
.task { widget = try? await Turnstile.widget(client: frontmail) }
}
private func send() async {
guard let widget, let token else { return }
defer { self.token = nil; resetID += 1 } // a token works only once
do {
_ = try await frontmail.send(
templateID: "tpl_…",
params: ContactMessage(name: name, email: email, message: message),
options: SendOptions(turnstileToken: token, turnstileKey: widget.turnstileKey)
)
info = "Thank you! We’ll get back to you soon."
} catch let error as FrontmailError {
info = error.message
} catch {
info = error.localizedDescription
}
}
}
N’importe quelle struct Encodable peut servir de paramètres : le compilateur vérifie les noms de
champs à votre place. Les erreurs réseau et les réponses 5xx sont relancées avec une seule clé
d’idempotence – pas d’e-mails en double sur une connexion instable.
5. Testez
Lancez l’application dans le simulateur ou sur un appareil, 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 non enregistré.captcha_required– pas encore de jeton Turnstile ; Send 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.
Quand vos crédits sont épuisés, le message est accepté avec le statut
.heldet envoyé automatiquement après la recharge – rien ne se perd.
Étapes suivantes
- Confirmez le message à l’utilisateur avec le modèle Réponse automatique : merci pour votre message de la galerie.
- Envoyez
SendOptions(locale: Locale.preferredLanguages.first)pour utiliser une variante linguistique du modèle dans la langue de l’utilisateur.


