Vai al contenuto

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:

Il video è in inglese; i passaggi qui sotto sono il tutorial completo.

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

  1. Servizi email → Aggiungi servizio: collega Gmail, Outlook, SMTP o un provider transazionale, seleziona Imposta come predefinito, poi Verifica e salva.
  2. Modelli → Dalla galleria → Modulo di contatto → Usa modello. Parametri obbligatori: name, email, message; facoltativi: phone, topic.
  3. 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 risponde origin_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 .held e 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.
Tutti i tutorial