Come creare un modulo di contatto su iOS con Swift e SwiftUI
Un modulo di contatto in SwiftUI che ti invia ogni messaggio tramite la tua casella di posta: niente backend e nessuna password SMTP nell’app. Usa il pacchetto Swift di Frontmail.
Aggiornato:
Aggiungerai un modulo di contatto a un’app iOS con SwiftUI e il pacchetto Swift di Frontmail. L’app contiene solo una chiave pubblica; Frontmail invia l’email tramite la tua casella, quindi nell’app non c’è nessuna password SMTP e non devi mantenere nessun server.
Ti serve: un account Frontmail gratuito, un progetto Xcode con SwiftUI e circa un quarto d’ora.
1. Collega la tua casella e crea il modello
- Servizi email → Aggiungi servizio: collega Gmail, Outlook, SMTP o un provider transazionale, seleziona Imposta come predefinito, poi Verifica e salva.
- Modelli → Dalla galleria → Modulo di contatto → Usa modello. Parametri
obbligatori:
name,email,message; facoltativi:phone,topic. - Copia l’ID del modello (
tpl_…) dalla scheda Snippet.
2. Consenti le app mobile
- Sicurezza → App mobile → Consenti app mobile → Salva. Le app non inviano l’header
Origin, quindi fino ad allora l’API rispondeorigin_not_allowed. - Modello → Impostazioni → Protezione → Richiedi CAPTCHA (Turnstile). Le app usano il widget Turnstile condiviso di Frontmail; non ti servono chiavi Cloudflare.
3. Aggiungi il pacchetto
In Xcode scegli File → Add Package Dependencies…, inserisci
https://github.com/frontmail-dev/frontmail-swift e aggiungi la libreria Frontmail al target
della tua app. Poi copia nel progetto la TurnstileView dalla sezione Turnstile della
documentazione dell’SDK iOS: un piccolo UIViewRepresentable attorno a WKWebView, ed è tuo.
4. Crea il modulo
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
}
}
}
Come parametri va bene qualsiasi struct Encodable, quindi è il compilatore a controllare i nomi
dei campi. Gli errori di rete e le risposte 5xx vengono ritentati con un’unica chiave di
idempotenza: niente email duplicate con una connessione instabile.
5. Provalo
Avvia l’app nel simulatore o su un dispositivo, compila il modulo e tocca Send. L’email arriva nella tua casella e compare in Cronologia nella dashboard.
origin_not_allowed– Consenti app mobile è disattivato o non è stato salvato.captcha_required– non c’è ancora un token Turnstile; Send resta disattivato finché la verifica non finisce.invalid_template_params– un campo obbligatorio è vuoto oppure l’email non è valida.
Quando i crediti finiscono, il messaggio viene accettato con lo stato
.helde inviato automaticamente dopo la ricarica: non va perso nulla.
Prossimi passi
- Conferma il messaggio all’utente con il modello Risposta automatica: grazie per il messaggio della galleria.
- Invia
SendOptions(locale: Locale.preferredLanguages.first)per usare una variante linguistica del modello nella lingua dell’utente.


