Preskočiť na obsah

Ako vytvoriť kontaktný formulár vo Flutteri, ktorý posiela e-maily

Kontaktný formulár vo Flutteri, ktorý vám každú správu pošle e-mailom – bez backendu a bez SMTP hesla v aplikácii. Funguje na Androide, iOS, webe aj desktope s balíčkom Frontmail pre Dart.

Aktualizované:

Video je v angličtine; nižšie nájdete celý návod v slovenčine.

Do aplikácie vo Flutteri pridáte kontaktnú obrazovku, ktorá každú správu pošle do vašej schránky. Aplikácia obsahuje iba verejný kľúč; Frontmail odošle e-mail cez vašu vlastnú schránku, takže sa do balíčka aplikácie nedostane žiadne SMTP heslo ani adresa servera.

Potrebujete: bezplatný účet Frontmail, projekt vo Flutteri 3.24+ a zhruba pätnásť minút.

1. Pripojte schránku a vytvorte šablónu

  1. E-mailové služby → Pridať službu: pripojte Gmail, Outlook, SMTP alebo transakčného poskytovateľa, zaškrtnite Nastaviť ako predvolenú, Overiť a uložiť.
  2. Šablóny → Z galérie → Kontaktný formulár → Použiť šablónu. Očakáva name, email a message (povinné) a phone, topic (nepovinné).
  3. Z karty Kód skopírujte ID šablóny (tpl_…).

2. Povoľte mobilné aplikácie

Aplikácie neposielajú hlavičku Origin ako weby, preto ich API odmieta, kým ich nepovolíte: Zabezpečenie → Mobilné aplikácie → zapnite Povoliť mobilné aplikácie → Uložiť.

Potom v šablóne otvorte Nastavenia → Ochrana a zapnite Vyžadovať CAPTCHA (Turnstile). Mobilné aplikácie používajú zdieľaný widget Turnstile od Frontmailu, takže nepotrebujete žiadne vlastné kľúče od Cloudflare.

3. Nainštalujte balíček

flutter pub add frontmail webview_flutter url_launcher

webview_flutter zobrazuje overenie Turnstile. Na Androide potrebujú release buildy aj <uses-permission android:name="android.permission.INTERNET"/> v AndroidManifest.xml.

Z časti o Turnstile v dokumentácii Flutter SDK si skopírujte widget TurnstileView do lib/turnstile_view.dart – je to zhruba šesťdesiat riadkov, ktoré patria vám a môžete ich ľubovoľne upraviť.

4. Vytvorte formulár

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),
        ],
      ),
    );
  }
}

SDK samo opakuje pokusy pri sieťových chybách a výpadkoch servera s jedným idempotency kľúčom, takže ani nestabilné mobilné pripojenie nevytvorí duplicitné e-maily.

5. Vyskúšajte to

Spustite aplikáciu, vyplňte formulár a ťuknite na Send. E-mail vám príde do schránky a zobrazí sa v dashboarde v časti História.

  • origin_not_allowed – Povoliť mobilné aplikácie je vypnuté (alebo neuložené).
  • captcha_required – chýba token Turnstile; počkajte, kým sa overenie dokončí.
  • invalid_template_params – povinné pole je prázdne alebo e-mailová adresa nie je platná.

Ak vám dôjdu kredity, správa sa prijme so stavom held a po dobití sa automaticky odošle – podľa result.status == DeliveryStatus.held to môžete oznámiť používateľovi.

Ďalšie kroky

  • Pošlite používateľovi potvrdenie pomocou šablóny Automatická odpoveď: ďakujeme za správu z galérie.
  • Ak má šablóna jazykové verzie, odovzdajte SendOptions(locale: …) a e-mail odíde v jazyku používateľa.
Všetky návody