Send email from Flutter: call your backend, not the email API

Updated

There is no Flutter or Dart package for Inboxili. A mobile app should never contain the API key, so the app calls your backend, which uses an SDK.

Mobile apps are easy to unpack. A secret compiled into an Android or iOS build can be extracted, so the Inboxili API key must stay on your server.

The pattern

  1. The app calls your backend: "send me a login code", "I booked this slot".
  2. The backend checks who is asking, applies limits, and sends the email with an SDK.
  3. The backend answers the app with a result that does not reveal secrets.

For most mobile features the server already knows the event happened, so it can send the email without the app asking at all. Prefer that where you can: a booking handled by your server triggers its own confirmation.

The Dart side

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

class EmailRequestException implements Exception {
  EmailRequestException(this.message);
  final String message;
  @override
  String toString() => message;
}

/// Asks your backend to email a login code. The backend holds the Inboxili key.
Future<void> requestLoginCode(String email, {String? authToken}) async {
  final uri = Uri.parse('https://api.yourdomain.com/auth/login-code');
  try {
    final response = await http
        .post(
          uri,
          headers: {
            'Content-Type': 'application/json',
            if (authToken != null) 'Authorization': 'Bearer $authToken',
          },
          body: jsonEncode({'email': email}),
        )
        .timeout(const Duration(seconds: 15));

    if (response.statusCode == 429) {
      throw EmailRequestException('Too many requests. Please wait a minute.');
    }
    if (response.statusCode != 200 && response.statusCode != 202) {
      throw EmailRequestException('Could not send the code. Please try again.');
    }
  } on TimeoutException {
    throw EmailRequestException('The request timed out. Check your connection.');
  } on http.ClientException {
    throw EmailRequestException('No connection. Please try again.');
  }
}

Use it from a button handler and show the exception message. Disable the button while the request runs, to avoid duplicate codes.

The server side

The /auth/login-code endpoint is where Inboxili comes in. Build it with the Node.js SDK, Python package or PHP SDK, following the OTP workflow: generate the code, store its hash and expiry, send the email, and rate-limit by address and IP.

Things to get right

  • Rate limits belong on the server. The app can be bypassed.
  • Deep links. For magic links and invitations, use universal links or app links so the email opens the app, with a web fallback.
  • Push or email. Use email for things the user must be able to find later, such as receipts. Use push for ephemeral prompts.
  • Do not store the API key in --dart-define. That still compiles it into the app.

Frequently asked questions

Can a mobile app call the Inboxili API directly?
Technically yes, but the API key would ship inside the app, where anyone can extract it from the binary. Treat it as public and call your own server instead.
Should the app send the email, or the backend?
The backend. The app reports an event, such as a booking created, and the server decides who gets which email.

Official SDKs

Build with Inboxili

Create a workspace, verify a domain, and make your first API call.

Related