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

Transactional email sending for Bloom server applications with swappable delivery backends.

bloom_mail #

Transactional email sending for Bloom server applications, ported from djangors-mail.

Provides a provider-agnostic BloomMailMessage model and swappable BloomMailBackend delivery adapters that integrate directly with bloom_framework's dependency injection container.


Features #

  • Provider-Agnostic Message Model: Full support for to, from, subject, plain-text body, optional htmlBody, cc, and bcc.
  • Pluggable Delivery Backends:
    • BloomSmtpBackend: Real SMTP delivery with explicit STARTTLS or implicit SSL/TLS.
    • BloomConsoleBackend: Formatted stdout logging for local development.
    • BloomFileBackend: Writes individual .eml files to disk for inspection.
    • BloomInMemoryBackend: Records sent messages in memory for test assertions.
  • Dependency Injection Ready: Seamlessly register with BloomContainer (globalContainer.provideSingleton<BloomMailBackend>(...)) so business logic depends solely on the abstract backend interface.
  • Credential Safety: SMTP credentials (host, username, password) are never logged or exposed in dev backends.
  • Environment Driven: Built-in support for loading configuration via BloomEnv.

Installation #

Add bloom_mail to your pubspec.yaml:

dependencies:
  bloom_framework:
    path: ../bloom_framework
  bloom_mail:
    path: ../bloom_mail

Usage #

1. Registering the Backend at Boot #

In your server bootstrap or service initialization (e.g. boot.dart / app.dart), configure and register the appropriate backend into the BloomContainer:

import 'package:bloom_framework/bloom_framework.dart';
import 'package:bloom_mail/bloom_mail.dart';

void configureMailBackend() {
  final isProduction = BloomEnv.getOrNull('APP_ENV') == 'production';

  if (isProduction) {
    // Configure real SMTP backend from environment variables
    final smtpConfig = BloomSmtpConfig.fromEnv(
      hostKey: 'SMTP_HOST',
      portKey: 'SMTP_PORT',
      userKey: 'SMTP_USER',
      passKey: 'SMTP_PASSWORD',
      useTlsKey: 'SMTP_USE_TLS',
    );

    globalContainer.provideSingleton<BloomMailBackend>(
      () => BloomSmtpBackend(smtpConfig),
    );
  } else {
    // Local dev: log to stdout without leaking credentials
    globalContainer.provideSingleton<BloomMailBackend>(
      () => const BloomConsoleBackend(),
    );
  }
}

2. Sending Emails in Services / Handlers #

Application code injects the abstract BloomMailBackend interface without depending on any concrete delivery implementation:

import 'package:bloom_framework/bloom_framework.dart';
import 'package:bloom_mail/bloom_mail.dart';

class AuthService {
  final BloomMailBackend _mailBackend;

  AuthService({BloomMailBackend? mailBackend})
      : _mailBackend = mailBackend ?? inject<BloomMailBackend>();

  Future<void> sendPasswordReset(String recipientEmail, String resetToken) async {
    final message = BloomMailMessage(
      to: [recipientEmail],
      from: 'noreply@bloom.dev',
      subject: 'Reset your Bloom password',
      body: 'Use this link to reset your password: https://app.bloom.dev/reset?token=$resetToken',
      htmlBody: '''
        <h1>Password Reset</h1>
        <p>Click <a href="https://app.bloom.dev/reset?token=$resetToken">here</a> to reset your password.</p>
      ''',
    );

    await _mailBackend.send(message);
  }
}

Testing with BloomInMemoryBackend #

In your unit and integration tests, override or inject BloomInMemoryBackend to verify outgoing emails without sending network requests:

import 'package:bloom_framework/bloom_framework.dart';
import 'package:bloom_mail/bloom_mail.dart';
import 'package:test/test.dart';

void main() {
  late BloomInMemoryBackend mailBackend;
  late AuthService authService;

  setUp(() {
    mailBackend = BloomInMemoryBackend();
    
    // Override DI container or pass explicitly to service
    globalContainer.override<BloomMailBackend>(mailBackend);
    authService = AuthService();
  });

  tearDown(() {
    globalContainer.removeOverride<BloomMailBackend>();
  });

  test('sends password reset email with valid link', () async {
    await authService.sendPasswordReset('alice@example.com', 'xyz-token-123');

    // Assert on captured messages
    expect(mailBackend.sentMessages, hasLength(1));

    final sent = mailBackend.sentMessages.first;
    expect(sent.to, equals(['alice@example.com']));
    expect(sent.from, equals('noreply@bloom.dev'));
    expect(sent.subject, equals('Reset your Bloom password'));
    expect(sent.body, contains('https://app.bloom.dev/reset?token=xyz-token-123'));
    expect(sent.htmlBody, contains('href="https://app.bloom.dev/reset?token=xyz-token-123"'));
  });
}

Fluent BloomSmtpConfig Builder #

You can also construct SMTP configuration programmatically using fluent chaining:

final config = BloomSmtpConfig('smtp.example.com')
    .withPort(587)
    .credentials('smtp_user', 'secret_password')
    .withUseTls(true);

final backend = BloomSmtpBackend(config);

Dev Backends #

Console Backend #

Prints formatted email contents to stdout for local rapid prototyping:

final backend = BloomConsoleBackend();

File Backend #

Writes outgoing emails as timestamped .eml files into a directory for offline inspection:

final backend = BloomFileBackend('.bloom/emails');
0
likes
0
points
364
downloads

Publisher

unverified uploader

Weekly Downloads

Transactional email sending for Bloom server applications with swappable delivery backends.

Homepage
Repository (GitHub)
View/report issues

Topics

#bloom #email #smtp

License

unknown (license)

Dependencies

bloom_framework, mailer

More

Packages that depend on bloom_mail