Zum Inhalt springen

Kontaktformular in Flutter erstellen, das E-Mails versendet

Ein Flutter-Kontaktformular, das Ihnen jede Nachricht per E-Mail schickt – ohne Backend und ohne SMTP-Passwort in der App. Läuft auf Android, iOS, Web und Desktop mit dem Frontmail-Dart-Paket.

Aktualisiert:

Das Video ist auf Englisch; die Schritte unten sind das vollständige Tutorial.

Sie fügen einer Flutter-App einen Kontaktbildschirm hinzu, der jede Nachricht in Ihr Postfach schickt. Die App enthält nur einen öffentlichen Schlüssel; Frontmail versendet die E-Mail über Ihr eigenes Postfach, sodass weder ein SMTP-Passwort noch ein Server im App-Bundle landet.

Sie brauchen: ein kostenloses Frontmail-Konto, ein Flutter-Projekt ab 3.24 und etwa fünfzehn Minuten.

1. Postfach verbinden und Vorlage anlegen

  1. E-Mail-Dienste → Dienst hinzufügen: Verbinden Sie Gmail, Outlook, SMTP oder einen Transaktions-Anbieter, aktivieren Sie Als Standard festlegen, dann Prüfen & speichern.
  2. Vorlagen → Aus der Galerie → Kontaktformular → Vorlage verwenden. Sie erwartet name, email und message (Pflicht) sowie phone und topic (optional).
  3. Kopieren Sie die Vorlagen-ID (tpl_…) aus dem Tab Snippets.

2. Mobile Apps erlauben

Apps senden keinen Origin-Header wie Websites, daher lehnt die API sie ab, bis Sie das erlauben: Sicherheit → Mobile Apps → Mobile Apps erlauben einschalten → Speichern.

Öffnen Sie dann in der Vorlage Einstellungen → Schutz und aktivieren Sie CAPTCHA verlangen (Turnstile). Mobile Apps nutzen das gemeinsame Turnstile-Widget von Frontmail – eigene Cloudflare-Schlüssel brauchen Sie also nicht.

3. Paket installieren

flutter pub add frontmail webview_flutter url_launcher

webview_flutter zeigt die Turnstile-Prüfung an. Unter Android brauchen Release-Builds außerdem <uses-permission android:name="android.permission.INTERNET"/> in AndroidManifest.xml.

Kopieren Sie das Widget TurnstileView aus dem Turnstile-Abschnitt der Flutter-SDK-Dokumentation nach lib/turnstile_view.dart – rund sechzig Zeilen, die Ihnen gehören und die Sie frei gestalten können.

4. Formular bauen

import 'package:flutter/material.dart';
import 'package:frontmail/frontmail.dart';

import 'turnstile_view.dart';

final frontmail = Frontmail(publicKey: 'pk_…'); // create once, share

class ContactScreen extends StatefulWidget {
  const ContactScreen({super.key});

  @override
  State<ContactScreen> createState() => _ContactScreenState();
}

class _ContactScreenState extends State<ContactScreen> {
  final _name = TextEditingController();
  final _email = TextEditingController();
  final _message = TextEditingController();
  TurnstileSite? _site;
  String? _token;
  int _resetId = 0;
  bool _sending = false;
  String _info = '';

  @override
  void initState() {
    super.initState();
    Turnstile.site(client: frontmail).then((site) => setState(() => _site = site));
  }

  Future<void> _send() async {
    final site = _site, token = _token;
    if (site == null || token == null) return;
    setState(() {
      _sending = true;
      _token = null; // a token works only once
      _resetId++;
    });
    try {
      await frontmail.send(
        templateId: 'tpl_…',
        params: {'name': _name.text, 'email': _email.text, 'message': _message.text},
        options: SendOptions(turnstileToken: token, turnstileKey: site.turnstileKey),
      );
      setState(() => _info = 'Thank you! We’ll get back to you soon.');
    } on FrontmailException catch (e) {
      setState(() => _info = e.message);
    } finally {
      setState(() => _sending = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final site = _site;
    return Scaffold(
      appBar: AppBar(title: const Text('Contact us')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          TextField(controller: _name, decoration: const InputDecoration(labelText: 'Name')),
          TextField(controller: _email, decoration: const InputDecoration(labelText: 'Email'), keyboardType: TextInputType.emailAddress),
          TextField(controller: _message, decoration: const InputDecoration(labelText: 'Message'), maxLines: 5),
          const SizedBox(height: 16),
          if (site != null)
            TurnstileView(
              site: site,
              resetId: _resetId,
              onMessage: (m) => setState(() => _token = m is TurnstileToken ? m.token : null),
            ),
          FilledButton(onPressed: _token == null || _sending ? null : _send, child: const Text('Send')),
          Text(_info),
        ],
      ),
    );
  }
}

Das SDK wiederholt Netzwerkfehler und kurze Serverstörungen selbstständig mit einem einzigen Idempotenzschlüssel – eine wacklige Mobilfunkverbindung erzeugt also nie doppelte E-Mails.

5. Testen

Starten Sie die App, füllen Sie das Formular aus und tippen Sie auf Send. Die E-Mail kommt in Ihrem Postfach an und erscheint im Dashboard unter Verlauf.

  • origin_not_allowed – Mobile Apps erlauben ist ausgeschaltet (oder nicht gespeichert).
  • captcha_required – das Turnstile-Token fehlt; warten Sie, bis die Prüfung abgeschlossen ist.
  • invalid_template_params – ein Pflichtfeld ist leer oder die E-Mail-Adresse ungültig.

Wenn Ihre Credits aufgebraucht sind, wird die Nachricht mit dem Status held angenommen und nach dem Aufladen automatisch versendet – über result.status == DeliveryStatus.held können Sie den Nutzer darüber informieren.

Nächste Schritte

  • Schicken Sie dem Nutzer eine Bestätigung mit der Vorlage Automatische Antwort: Danke für Ihre Nachricht aus der Galerie.
  • Übergeben Sie SendOptions(locale: …), um die E-Mail in der Sprache des Nutzers zu versenden, wenn die Vorlage Sprachvarianten hat.
Alle Tutorials