email_integrity 0.1.0 copy "email_integrity: ^0.1.0" to clipboard
email_integrity: ^0.1.0 copied to clipboard

A production-ready Flutter/Dart package for email validation, syntax checks, normalization, disposable & fake email detection, custom domain rules, and DNS/MX validation.

example/lib/main.dart

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

void main() {
  runApp(const EmailIntegrityExampleApp());
}

class EmailIntegrityExampleApp extends StatelessWidget {
  const EmailIntegrityExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Email Integrity Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6750A4),
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      home: const MainHomeScreen(),
    );
  }
}

class MainHomeScreen extends StatelessWidget {
  const MainHomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(Icons.shield_outlined, color: Colors.deepPurple),
              SizedBox(width: 8),
              Text('Email Integrity', style: TextStyle(fontWeight: FontWeight.bold)),
            ],
          ),
          centerTitle: true,
          bottom: const TabBar(
            tabs: [
              Tab(icon: Icon(Icons.analytics_outlined), text: 'Inspector'),
              Tab(icon: Icon(Icons.category_outlined), text: 'Classifier'),
              Tab(icon: Icon(Icons.dynamic_form_outlined), text: 'Form Demo'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            EmailInspectorTab(),
            EmailClassifierTab(),
            FormValidationTab(),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// TAB 1: EMAIL INSPECTOR & VALIDATOR
// ============================================================================
class EmailInspectorTab extends StatefulWidget {
  const EmailInspectorTab({super.key});

  @override
  State<EmailInspectorTab> createState() => _EmailInspectorTabState();
}

class _EmailInspectorTabState extends State<EmailInspectorTab> {
  final _emailController = TextEditingController(text: 'user@gmail.com');
  final _allowedDomainsController = TextEditingController();
  final _blockedDomainsController = TextEditingController();

  EmailValidationMode _mode = EmailValidationMode.standard;
  DomainPolicy _policy = DomainPolicy.allowAny;

  bool _checkDisposable = true;
  bool _checkFakeDomains = true;
  bool _checkDns = false;
  bool _checkMx = false;
  bool _applyProviderNormalization = false;

  bool _isLoading = false;
  EmailValidationResult? _result;

  final List<String> _quickTestEmails = [
    'user@gmail.com',
    'user@proton.me',
    'employee@acme.com',
    'user@startup.app',
    'test@mailinator.com',
    'test@example.com',
    'user@münchen.de',
    'user@@company.com',
  ];

  Future<void> _runValidation() async {
    setState(() => _isLoading = true);

    final allowed = _allowedDomainsController.text
        .split(',')
        .map((e) => e.trim())
        .where((e) => e.isNotEmpty)
        .toSet();

    final blocked = _blockedDomainsController.text
        .split(',')
        .map((e) => e.trim())
        .where((e) => e.isNotEmpty)
        .toSet();

    final options = EmailValidationOptions(
      mode: _mode,
      domainPolicy: _policy,
      checkDisposable: _checkDisposable,
      checkFakeDomains: _checkFakeDomains,
      checkDns: _checkDns,
      checkMx: _checkMx,
      applyProviderNormalization: _applyProviderNormalization,
      allowedDomains: allowed,
      blockedDomains: blocked,
    );

    final res = await EmailIntegrity.validate(
      _emailController.text,
      options: options,
    );

    setState(() {
      _isLoading = false;
      _result = res;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16.0),
      children: [
        // Quick Presets
        const Text(
          'Quick Test Presets',
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
        ),
        const SizedBox(height: 8),
        Wrap(
          spacing: 6,
          runSpacing: 4,
          children: _quickTestEmails.map((email) {
            return ActionChip(
              label: Text(email, style: const TextStyle(fontSize: 12)),
              onPressed: () {
                _emailController.text = email;
                _runValidation();
              },
            );
          }).toList(),
        ),
        const SizedBox(height: 16),

        // Email Input Field
        TextField(
          controller: _emailController,
          decoration: InputDecoration(
            labelText: 'Target Email Address',
            hintText: 'Enter email to validate...',
            border: const OutlineInputBorder(),
            prefixIcon: const Icon(Icons.alternate_email),
            suffixIcon: IconButton(
              icon: const Icon(Icons.clear),
              onPressed: () => _emailController.clear(),
            ),
          ),
          keyboardType: TextInputType.emailAddress,
        ),
        const SizedBox(height: 16),

        // Options Accordion / Card
        Card(
          elevation: 0,
          shape: RoundedRectangleBorder(
            side: BorderSide(color: Colors.grey.shade300),
            borderRadius: BorderRadius.circular(12),
          ),
          child: ExpansionTile(
            title: const Text(
              'Validation Options & Policies',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
            ),
            subtitle: Text('Mode: ${_mode.name} | Policy: ${_policy.name}',
                overflow: TextOverflow.ellipsis),
            initiallyExpanded: false,
            children: [
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  children: [
                    DropdownButtonFormField<EmailValidationMode>(
                      initialValue: _mode,
                      isExpanded: true,
                      decoration: const InputDecoration(
                        labelText: 'Validation Mode',
                        border: OutlineInputBorder(),
                      ),
                      items: EmailValidationMode.values
                          .map((m) => DropdownMenuItem(
                                value: m,
                                child: Text(
                                  'Mode: ${m.name.toUpperCase()}',
                                  overflow: TextOverflow.ellipsis,
                                ),
                              ))
                          .toList(),
                      onChanged: (v) {
                        if (v != null) setState(() => _mode = v);
                      },
                    ),
                    const SizedBox(height: 12),
                    DropdownButtonFormField<DomainPolicy>(
                      initialValue: _policy,
                      isExpanded: true,
                      decoration: const InputDecoration(
                        labelText: 'Domain Filtering Policy',
                        border: OutlineInputBorder(),
                      ),
                      items: DomainPolicy.values
                          .map((p) => DropdownMenuItem(
                                value: p,
                                child: Text(
                                  'Policy: ${p.name}',
                                  overflow: TextOverflow.ellipsis,
                                ),
                              ))
                          .toList(),
                      onChanged: (v) {
                        if (v != null) setState(() => _policy = v);
                      },
                    ),
                    const SizedBox(height: 12),
                    SwitchListTile(
                      title: const Text('Check Disposable Domains'),
                      subtitle: const Text('Detect temporary mail services'),
                      value: _checkDisposable,
                      onChanged: (v) => setState(() => _checkDisposable = v),
                    ),
                    SwitchListTile(
                      title: const Text('Check Fake / Test Domains'),
                      subtitle: const Text('Detect example.com, localhost, etc.'),
                      value: _checkFakeDomains,
                      onChanged: (v) => setState(() => _checkFakeDomains = v),
                    ),
                    SwitchListTile(
                      title: const Text('DNS Lookup (DoH)'),
                      subtitle: const Text('Verify domain exists in DNS'),
                      value: _checkDns,
                      onChanged: (v) => setState(() => _checkDns = v),
                    ),
                    SwitchListTile(
                      title: const Text('MX Record Lookup (DoH)'),
                      subtitle: const Text('Verify active mail servers exist'),
                      value: _checkMx,
                      onChanged: (v) => setState(() => _checkMx = v),
                    ),
                    SwitchListTile(
                      title: const Text('Apply Provider Normalization'),
                      subtitle: const Text('Strip Gmail dots/tags for canonical ID'),
                      value: _applyProviderNormalization,
                      onChanged: (v) => setState(() => _applyProviderNormalization = v),
                    ),
                    const SizedBox(height: 8),
                    TextField(
                      controller: _allowedDomainsController,
                      decoration: const InputDecoration(
                        labelText: 'Allowed Custom Domains (comma separated)',
                        hintText: 'acme.com, school.edu',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    const SizedBox(height: 12),
                    TextField(
                      controller: _blockedDomainsController,
                      decoration: const InputDecoration(
                        labelText: 'Blocked Domains (comma separated)',
                        hintText: 'competitor.com, badactor.org',
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 16),

        // Validate Button
        FilledButton.icon(
          onPressed: _isLoading ? null : _runValidation,
          style: FilledButton.styleFrom(
            padding: const EdgeInsets.symmetric(vertical: 14),
          ),
          icon: _isLoading
              ? const SizedBox(
                  width: 18,
                  height: 18,
                  child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
                )
              : const Icon(Icons.verified_user),
          label: Text(
            _isLoading ? 'Analyzing Email...' : 'Validate Email Address',
            style: const TextStyle(fontSize: 16),
          ),
        ),
        const SizedBox(height: 24),

        // Result Card
        if (_result != null) _buildResultCard(_result!),
      ],
    );
  }

  Widget _buildResultCard(EmailValidationResult res) {
    final isValid = res.isValid;
    final color = isValid ? Colors.green.shade700 : Colors.red.shade700;
    final bgColor = isValid ? Colors.green.shade50 : Colors.red.shade50;

    return Card(
      elevation: 2,
      color: bgColor,
      shape: RoundedRectangleBorder(
        side: BorderSide(color: color, width: 1.5),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Status Header & Provider Badge
            Row(
              children: [
                Icon(isValid ? Icons.check_circle : Icons.cancel, color: color, size: 32),
                const SizedBox(width: 10),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        isValid ? 'Email Accepted' : 'Email Rejected',
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                          color: color,
                        ),
                      ),
                      Text(
                        res.reason.name,
                        style: TextStyle(color: Colors.grey.shade800, fontSize: 13),
                        overflow: TextOverflow.ellipsis,
                      ),
                    ],
                  ),
                ),
                _buildProviderBadge(res.providerType),
              ],
            ),
            const Divider(height: 28),

            // Message & Normalized Email
            _infoTile('Message', res.message, isBold: true),
            _infoTile('Normalized', res.normalizedEmail ?? 'N/A'),
            if (res.domain != null) _infoTile('Domain', res.domain!),
            if (res.validationDuration != null)
              _infoTile('Duration', '${res.validationDuration!.inMilliseconds} ms'),

            const SizedBox(height: 12),
            const Text(
              'Detailed Checks Breakdown:',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
            ),
            const SizedBox(height: 8),

            // Detailed Flag Grid
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                _statusChip('Syntax', res.syntaxValid),
                _statusChip('Domain Format', res.domainValid),
                _statusChip('Disposable', !res.disposable, trueLabel: 'Not Disposable', falseLabel: 'Disposable'),
                _statusChip('Fake/Test', !res.fake, trueLabel: 'Not Fake', falseLabel: 'Fake/Test'),
                _statusChip('Custom Policy', res.customDomainAllowed, trueLabel: 'Allowed', falseLabel: 'Not Allowed'),
                _statusChip('Blocklist', !res.blocked, trueLabel: 'Unblocked', falseLabel: 'Blocked'),
                if (res.dnsValid != null) _statusChip('DNS Lookup', res.dnsValid!),
                if (res.mxValid != null) _statusChip('MX Lookup', res.mxValid!),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildProviderBadge(EmailProviderType provider) {
    Color chipColor;
    IconData icon;

    switch (provider) {
      case EmailProviderType.public:
        chipColor = Colors.blue.shade700;
        icon = Icons.public;
        break;
      case EmailProviderType.privacy:
        chipColor = Colors.purple.shade700;
        icon = Icons.security;
        break;
      case EmailProviderType.disposable:
        chipColor = Colors.orange.shade800;
        icon = Icons.delete_outline;
        break;
      case EmailProviderType.business:
        chipColor = Colors.teal.shade700;
        icon = Icons.business;
        break;
      case EmailProviderType.unknown:
        chipColor = Colors.grey.shade700;
        icon = Icons.help_outline;
        break;
    }

    return Chip(
      avatar: Icon(icon, size: 16, color: Colors.white),
      label: Text(
        provider.name.toUpperCase(),
        style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 11),
      ),
      backgroundColor: chipColor,
      padding: EdgeInsets.zero,
      visualDensity: VisualDensity.compact,
    );
  }

  Widget _statusChip(
    String label,
    bool passed, {
    String? trueLabel,
    String? falseLabel,
  }) {
    final text = passed ? (trueLabel ?? 'Passed') : (falseLabel ?? 'Failed');
    final color = passed ? Colors.green.shade700 : Colors.red.shade700;

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: color.withValues(alpha: 0.4)),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(passed ? Icons.check : Icons.close, size: 14, color: color),
          const SizedBox(width: 4),
          Flexible(
            child: Text(
              '$label: $text',
              style: TextStyle(color: color, fontWeight: FontWeight.w600, fontSize: 12),
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ],
      ),
    );
  }

  Widget _infoTile(String label, String value, {bool isBold = false}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 3.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 90,
            child: Text(label, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
          ),
          Expanded(
            child: Text(
              value,
              style: TextStyle(
                fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
                fontSize: 13,
                color: Colors.black87,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// TAB 2: INSTANT EMAIL CLASSIFIER & NORMALIZER
// ============================================================================
class EmailClassifierTab extends StatefulWidget {
  const EmailClassifierTab({super.key});

  @override
  State<EmailClassifierTab> createState() => _EmailClassifierTabState();
}

class _EmailClassifierTabState extends State<EmailClassifierTab> {
  final _controller = TextEditingController(text: 'John.Doe+tag@Gmail.COM');

  @override
  Widget build(BuildContext context) {
    final input = _controller.text;
    final normalizedStandard = EmailIntegrity.normalize(input) ?? 'Invalid';
    final normalizedGmail = EmailIntegrity.normalize(input, applyProviderNormalization: true) ?? 'Invalid';
    final providerType = EmailIntegrity.classifyProvider(input);
    final isDisposable = EmailIntegrity.isDisposableEmail(input);
    final isStandardPublic = EmailIntegrity.isStandardPublicDomain(
      input.contains('@') ? input.split('@').last : input,
    );

    return ListView(
      padding: const EdgeInsets.all(16.0),
      children: [
        const Text(
          'Instant Email Normalizer & Classifier',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 4),
        const Text(
          'Classify provider types and test safe normalization rules in real-time.',
          style: TextStyle(color: Colors.grey),
        ),
        const SizedBox(height: 16),
        TextField(
          controller: _controller,
          decoration: const InputDecoration(
            labelText: 'Email Address or Domain',
            border: OutlineInputBorder(),
            prefixIcon: Icon(Icons.search),
          ),
          onChanged: (_) => setState(() {}),
        ),
        const SizedBox(height: 20),
        Card(
          elevation: 1,
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: [
                _classifierRow('Provider Type', providerType.name.toUpperCase(), Icons.category),
                const Divider(),
                _classifierRow('Standard Normalization', normalizedStandard, Icons.cleaning_services),
                const Divider(),
                _classifierRow('Provider Normalization (Gmail)', normalizedGmail, Icons.alternate_email),
                const Divider(),
                _classifierRow('Is Disposable?', isDisposable ? 'Yes (Temporary)' : 'No', Icons.delete_outline),
                const Divider(),
                _classifierRow('Is Standard Public Provider?', isStandardPublic ? 'Yes' : 'No', Icons.public),
              ],
            ),
          ),
        ),
      ],
    );
  }

  Widget _classifierRow(String label, String value, IconData icon) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        children: [
          Icon(icon, size: 20, color: Colors.deepPurple),
          const SizedBox(width: 12),
          Text(label, style: const TextStyle(fontWeight: FontWeight.w600)),
          const SizedBox(width: 8),
          Expanded(
            child: Text(
              value,
              textAlign: TextAlign.end,
              style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.deepPurple),
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// TAB 3: FLUTTER FORM INTEGRATION DEMO
// ============================================================================
class FormValidationTab extends StatefulWidget {
  const FormValidationTab({super.key});

  @override
  State<FormValidationTab> createState() => _FormValidationTabState();
}

class _FormValidationTabState extends State<FormValidationTab> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();

  bool _allowEmpty = false;
  final String _requiredMessage = 'Email address is required.';
  final String _invalidMessage = 'Please enter a valid email address.';

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16.0),
      children: [
        const Text(
          'Flutter TextFormField Validator Demo',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 4),
        const Text(
          'Demonstrates synchronous, offline-safe FormFieldValidator integration.',
          style: TextStyle(color: Colors.grey),
        ),
        const SizedBox(height: 16),
        Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: 'Email Address',
                  hintText: 'Enter your email',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.email_outlined),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: EmailIntegrity.formValidator(
                  allowEmpty: _allowEmpty,
                  requiredMessage: _requiredMessage,
                  invalidMessage: _invalidMessage,
                ),
              ),
              const SizedBox(height: 16),
              Card(
                elevation: 0,
                shape: RoundedRectangleBorder(
                  side: BorderSide(color: Colors.grey.shade300),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Column(
                  children: [
                    SwitchListTile(
                      title: const Text('Allow Empty Input'),
                      subtitle: const Text('When enabled, empty fields pass form validation'),
                      value: _allowEmpty,
                      onChanged: (v) => setState(() => _allowEmpty = v),
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 20),
              SizedBox(
                width: double.infinity,
                child: FilledButton.icon(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(
                          content: Text('Form Validation Passed Successfully!'),
                          backgroundColor: Colors.green,
                        ),
                      );
                    }
                  },
                  icon: const Icon(Icons.check_circle_outline),
                  label: const Text('Submit Form'),
                  style: FilledButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 14),
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}
0
likes
150
points
57
downloads

Documentation

API reference

Publisher

verified publisherappifyhaven.website

Weekly Downloads

A production-ready Flutter/Dart package for email validation, syntax checks, normalization, disposable & fake email detection, custom domain rules, and DNS/MX validation.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, http

More

Packages that depend on email_integrity