Skip to main content

Quick Start

Spin up Stem in minutes with nothing but Dart installed. This walkthrough stays fully in-memory so you can focus on the core pipeline: enqueueing, retries, delays, priorities, and chaining work together.

1. Create a Demo Project

dart create stem_quickstart
cd stem_quickstart

# Add Stem as a dependency and activate the CLI.
dart pub add stem
dart pub global activate stem

Add the Dart pub cache to your PATH so the stem CLI is reachable:

export PATH="$HOME/.pub-cache/bin:$PATH"
stem --version

2. Register Tasks with Options

Replace the generated bin/stem_quickstart.dart with the script built from the snippets below. The full, runnable version lives at packages/stem/example/docs_snippets/lib/quick_start.dart in the repository.

Define task handlers

Each task declares its name and retry/timeout options.

class ResizeImageTask extends TaskHandler<void> {

String get name => 'media.resize';


TaskOptions get options => const TaskOptions(
maxRetries: 5,
softTimeLimit: Duration(seconds: 10),
hardTimeLimit: Duration(seconds: 20),
priority: 7,
rateLimit: '20/m',
visibilityTimeout: Duration(seconds: 60),
);


Future<void> call(TaskContext context, Map<String, Object?> args) async {
final file = args['file'] as String? ?? 'unknown.png';
context.heartbeat();
print('[media.resize] resizing $file (attempt ${context.attempt})');
await Future<void>.delayed(const Duration(milliseconds: 200));
}
}

Bootstrap worker + Stem

Use StemApp to wire tasks, the in-memory broker/backend, and the worker:

  // In-memory adapters make the quick start self-contained.
final app = await StemApp.inMemory(
tasks: [ResizeImageTask(), EmailReceiptTask()],
workerConfig: const StemWorkerConfig(
queue: 'default',
consumerName: 'quickstart-worker',
concurrency: 4,
),
);

unawaited(app.start());

final stem = app.stem;

Enqueue tasks

Publish an immediate task plus a delayed task with custom metadata:

  final resizeId = await stem.enqueue(
'media.resize',
args: {'file': 'report.png'},
);

final emailId = await stem.enqueue(
'billing.email-receipt',
args: {'to': 'alice@example.com'},
options: const TaskOptions(priority: 10),
notBefore: DateTime.now().add(const Duration(seconds: 5)),
meta: {'orderId': 4242},
);

print('Enqueued tasks: resize=$resizeId email=$emailId');

Run the script:

dart run bin/stem_quickstart.dart

Stem handles retries, time limits, rate limiting, and priority ordering even with the in-memory adapters—great for tests and local demos.

3. Compose Work with Canvas

Stem’s canvas API lets you chain, group, or create chords of tasks. Add this helper to the bottom of the file above to try a chain:

Future<void> runCanvasExample(Canvas canvas) async {
final chainResult = await canvas.chain([
task(
'media.resize',
args: {'file': 'canvas.png'},
options: const TaskOptions(priority: 5),
),
task(
'billing.email-receipt',
args: {'to': 'ops@example.com'},
options: const TaskOptions(queue: 'emails'),
),
]);

print('Canvas chain complete. Final task id = ${chainResult.finalTaskId}');
}

Then call it from main once the worker has started:

  final canvas = app.canvas;
await runCanvasExample(canvas);

Finally, inspect the result state before shutting down:

  await Future<void>.delayed(const Duration(seconds: 6));
final resizeStatus = await app.backend.get(resizeId);
print('Resize status: ${resizeStatus?.state} (${resizeStatus?.attempt})');

await app.close();

Each step records progress in the result backend, and failures trigger retries or DLQ placement according to TaskOptions.

4. Peek at Retries and DLQ

Force a failure to see retry behaviour:

class EmailReceiptTask extends TaskHandler<void> {

String get name => 'billing.email-receipt';


TaskOptions get options => const TaskOptions(
queue: 'emails',
maxRetries: 3,
priority: 9,
);


Future<void> call(TaskContext context, Map<String, Object?> args) async {
final to = args['to'] as String? ?? 'customer@example.com';
if (context.attempt < 2) {
throw StateError('Simulated failure for $to');
}
print('[billing.email-receipt] delivered on attempt ${context.attempt}');
}
}

The retry pipeline and DLQ logic are built into the worker. When the task exceeds maxRetries, the envelope moves to the DLQ; you’ll learn how to inspect and replay those entries in the next guide.

5. Where to Next

  • Connect Stem to Redis/Postgres, try broadcast routing, and run Beat in Connect to Infrastructure.
  • Explore worker control commands, DLQ tooling, and OpenTelemetry export in Observe & Operate.
  • Keep the script—you’ll reuse the tasks and app bootstrap in later steps.