Skip to content

Implement an Alternative Email Client

One of the key architectural benefits of this API server is its use of dependency injection and interfaces (API contracts), which makes it easy to swap out core services like the email client. By default, the server uses SendGrid, but you can replace it with any other provider (e.g., Mailgun, AWS SES, or even a local SMTP server for testing) without altering the core business logic.

This guide demonstrates the value of this architecture by walking you through the process of creating and integrating a custom email client.

The core of this flexibility is the EmailClient abstract class, defined in the email_client package. Any email service you wish to use must have a corresponding class that implements this interface.

The contract, defined in the email_client package, is simple:

lib/src/email_client.dart
abstract class EmailClient {
const EmailClient();
Future<void> sendTransactionalEmail({
required String senderEmail,
required String recipientEmail,
required String templateId,
required Map<String, dynamic> templateData,
});
}

Example: Creating a Logging-Only Email Client

Section titled “Example: Creating a Logging-Only Email Client”

Let’s create a simple “logging” email client that doesn’t actually send emails but instead prints the details to the console. This is useful for local development or testing.

  1. Create the Implementation File: You can create a new implementation anywhere, but for this example, let’s assume you create a new local file for testing purposes.

  2. Implement the EmailClient:

    import 'package:email_client/email_client.dart';
    import 'package:logging/logging.dart';
    class EmailLogging implements EmailClient {
    EmailLogging({required this.log});
    final Logger log;
    @override
    Future<void> sendTransactionalEmail({
    required String senderEmail,
    required String recipientEmail,
    required String templateId,
    required Map<String, dynamic> templateData,
    }) async {
    log.info('--- 📧 Mock Email Sent ---');
    log.info('Sender: $senderEmail');
    log.info('Recipient: $recipientEmail');
    log.info('Template ID: $templateId');
    log.info('Template Data: $templateData');
    log.info('-------------------------');
    // In a real implementation, this is where you would
    // make the API call to your email provider.
    return Future.value();
    }
    }

Swapping the implementation is the easiest part. You only need to change a single part of the dependency injection setup.

Open the dependency configuration file within the API server project: lib/src/config/app_dependencies.dart.

// ...
// Configure the HTTP client for SendGrid.
final sendGridHttpClient = HttpClient(
baseUrl: '$sendGridApiBase/v3',
tokenProvider: () async => EnvironmentConfig.sendGridApiKey,
logger: Logger('EmailSendgridClient'),
);
// Initialize the SendGrid email client with the dedicated HTTP client.
final emailClient = EmailSendGrid(
httpClient: sendGridHttpClient,
log: Logger('EmailSendgrid'),
);
emailRepository = EmailRepository(emailClient: emailClient);
// ...