bloomBUILD•SHIP•BLOOM
.md
Infrastructure & Tasks

Jobs, Mail & Storage

Bloom Server provides integrated infrastructure modules for asynchronous background job execution (bloom_jobs), transactional multi-backend email sending (bloom_mail), and swappable local/S3 cloud file storage (bloom_storage).

Background Jobs with bloom_jobs

bloom_jobs provides an asynchronous task queue and worker architecture modeled on djangors-tasks:

  • BloomTaskRegistry — Named task handler registry.
  • BloomTaskQueue — In-memory FIFO queue with atomic claimNext() synchronization locks.
  • BloomRecurringRegistry — Periodic scheduled interval triggers.
  • BloomJobWorker — Polling worker loop with single-pass and continuous execution modes.
lib/jobs.dart
dart
// Background Jobs with bloom_jobs
import 'package:bloom_framework/bloom.dart';
import 'package:bloom_jobs/bloom_jobs.dart';

void configureJobs() async {
  final registry = BloomTaskRegistry();
  final queue = BloomTaskQueue();
  final recurring = BloomRecurringRegistry();

  // 1. Register a task handler
  registry.register('send_welcome_email', (Map<String, dynamic> payload) async {
    final email = payload['email'] as String;
    final name = payload['name'] as String;
    // Deliver welcome email via bloom_mail
  });

  // 2. Schedule recurring task every 30 minutes
  recurring.register(
    taskName: 'send_welcome_email',
    interval: const Duration(minutes: 30),
    payload: {'email': 'admin@example.com', 'name': 'Admin'},
  );

  // 3. Enqueue immediate or delayed tasks
  await queue.enqueue('send_welcome_email', {'email': 'alice@example.com', 'name': 'Alice'});

  // 4. Start background worker
  final worker = BloomJobWorker(
    queue: queue,
    registry: registry,
    recurringRegistry: recurring,
  ).withPollInterval(const Duration(seconds: 1));

  // worker.run();
}

Transactional Email with bloom_mail

bloom_mail provides a standardized BloomMailMessage model and swappable delivery backends:

BloomSmtpBackend

Real SMTP delivery with explicit STARTTLS or implicit SSL/TLS encryption.

BloomConsoleBackend

Formatted stdout logger for local rapid prototyping without credential leaks.

BloomFileBackend

Writes timestamped .eml files to disk for offline inspection.

BloomInMemoryBackend

Records sent messages in memory for unit test assertions.

lib/mail.dart
dart
// Transactional Email with bloom_mail
import 'package:bloom_mail/bloom_mail.dart';

Future<void> sendNotification(BloomMailBackend mailer) async {
  final message = BloomMailMessage(
    to: ['alice@example.com'],
    from: 'noreply@bloom.dev',
    subject: 'Welcome to Bloom Server',
    body: 'Your account is now ready.',
    htmlBody: '<h1>Welcome!</h1><p>Your account is now ready.</p>',
  );

  // Deliver via Smtp, Console (dev), File, or InMemory (tests)
  await mailer.send(message);
}

File Storage & S3 with bloom_storage

bloom_storage decouples your application from storage vendors. Switch between local disk and AWS S3, Cloudflare R2, MinIO, or Supabase Storage without modifying application code:

lib/storage.dart
dart
// File Storage with bloom_storage
import 'package:bloom_storage/bloom_storage.dart';

void configureStorage() async {
  // Option A: Local Disk for Development with Path Traversal Protection
  final localBackend = LocalDiskBackend(
    baseDirectory: './storage/uploads',
    publicUrlPrefix: 'http://localhost:8080/files',
  );
  BloomStorage.register(localBackend);

  // Option B: AWS S3, Cloudflare R2, MinIO, or Supabase Storage
  final s3Backend = S3Backend(
    config: BloomS3Config(
      accessKeyId: 'ACCESS_KEY',
      secretAccessKey: 'SECRET_KEY',
      bucket: 'my-media-bucket',
      region: 'auto',
      endpoint: 'https://<account-id>.r2.cloudflarestorage.com',
    ),
  );

  // Upload file
  final url = await BloomStorage.upload('avatars/usr_1.png', bytes, contentType: 'image/png');

  // Generate 30-minute presigned private download URL (SigV4)
  final signedUrl = await BloomStorage.getSignedUrl('invoices/inv_01.pdf', expiry: Duration(minutes: 30));
}