If you've worked with .NET applications, you've probably written code like this:
await ChargePayment();
await ReserveInventory();
await GenerateInvoice();
await SendEmail();
It looks simple.
- But what happens if the application crashes after ReserveInventory()?
- What happens if the payment API is temporarily unavailable?
- What happens if the workflow needs to wait for a manager's approval for two days?
- What happens if the server restarts while the workflow is running?
You can build solutions for all of these problems yourself, but the amount of infrastructure and error-handling code can grow quickly.
This is where Temporal comes in.
- Temporal is a durable workflow execution platform that helps applications reliably execute long-running and failure-prone workflows.
In this article, we'll build a simple order-processing workflow using .NET 10 and Temporal and understand the concepts behind it.
What is Temporal?
Temporal allows you to define a business process as a workflow and execute it reliably.
For example:
Instead of treating these as unrelated background jobs, Temporal lets us model them as one workflow.
The important part isn't just running these steps.
The important part is that Temporal keeps track of the workflow's execution so that it can recover from failures.
Conceptually:
Temporal
|
↓
Order Processing
|
┌────────┼────────┐
↓ ↓ ↓
Payment Inventory Email
If something goes wrong, the workflow can retry activities or continue from the appropriate point.
Why do we need Temporal?
Let's start with a normal ASP.NET Core application.
Imagine this method:
public async Task ProcessOrder()
{
await ChargePayment();
await ReserveInventory();
await GenerateInvoice();
await SendEmail();
}
Now imagine this sequence:
- Charge Payment ✓
- Reserve Inventory ✓
- Generate Invoice ✗
Maybe the invoice service is temporarily unavailable.
You now need to think about:
- Should the invoice operation be retried?
- How many times?
- How long should we wait between retries?
- What happens if the application crashes?
- How do we know which steps already completed?
- How do we resume the process?
- What happens if the workflow needs to wait for an external event?
As the workflow becomes more complicated, you end up building your own workflow infrastructure.
Temporal provides infrastructure for these problems.
Temporal's Core Concepts
Before writing code, let's understand four important concepts:
- Workflow
- Activity
- Worker
- Temporal Server
1. Workflow
A Workflow describes the overall business process.
For our order system:
Order Workflow
Order Workflow
|
├── Charge Payment
|
├── Reserve Inventory
|
├── Generate Invoice
|
└── Send Email
The workflow defines the order in which these operations happen.
2. Activity
An Activity performs actual external work.
For example:
- ChargePayment()
- ReserveInventory()
- GenerateInvoice()
- SendEmail()
Activities are where you normally interact with:
- Databases
- REST APIs
- Payment gateways
- Email services
- File systems
- External applications
A useful mental model is:
Workflow = What should happen?
Activity = Perform the actual work.
3. Worker
The Worker is your application process that executes workflows and activities.
A simplified architecture looks like this:
Temporal Server
↑
|
.NET Worker
/ \
/ \
Workflow Activities
|
┌──────────┼──────────┐
↓ ↓ ↓
Database API Email
The Worker polls Temporal for work and executes it.
4. Temporal Server
The Temporal Server coordinates workflow execution and maintains the information needed for durable execution.
Your application doesn't simply keep workflow state in memory.
Instead:
Your Worker
↓
Temporal
↓
Workflow History
This is one of the fundamental reasons Temporal can recover workflows after failures.
Our Example
Let's build an order-processing system.
The workflow will look like this:
Order Created
|
↓
Charge Payment
|
↓
Check Order Amount
/ \
> ₹10,000 <= ₹10,000
| |
↓ ↓
Manager Approval Normal Process
\ /
\ /
↓ ↓
Send Email
We'll use:
- .NET 10
- C#
- Temporal .NET SDK
- ASP.NET Core
Step 1: Create the .NET Project
Create a new ASP.NET Core application:
- dotnet new web -n TemporalDemo
- cd TemporalDemo
Install the Temporal .NET SDK:
- dotnet add package Temporalio
You can verify the project:
- dotnet run
Step 2: Create the Order Model
Create:
Models/OrderRequest.cs
namespace TemporalDemo.Models;
public class OrderRequest
{
public string CustomerName { get; set; } = "";
public decimal Amount { get; set; }
}
Our API will receive:
{
"customerName": "Sanjay",
"amount": 15000
}
Step 3: Create Activities
Create:
Activities/OrderActivities.cs
using Temporalio.Activities;
namespace TemporalDemo.Activities;
public class OrderActivities
{
[Activity]
public async Task<string> ChargePaymentAsync(
string customerName,
decimal amount)
{
Console.WriteLine(
$"Charging ₹{amount} for {customerName}"
);
// In a real application:
// Call your payment provider here.
await Task.Delay(500);
return "Payment successful";
}
[Activity]
public async Task RequestManagerApprovalAsync(
string customerName,
decimal amount)
{
Console.WriteLine(
$"Manager approval required for {customerName}: ₹{amount}"
);
// In a real application:
// Save an approval request to your database
// or notify a manager.
await Task.Delay(500);
}
[Activity]
public async Task SendEmailAsync(
string customerName)
{
Console.WriteLine(
$"Sending email to {customerName}"
);
// In a real application:
// Call your email provider.
await Task.Delay(500);
}
}
Notice that the Activities contain the actual work.
For example, this:
- ChargePaymentAsync()
could eventually call Stripe, Razorpay, PayPal, or your own payment API.
Step 4: Create the Workflow
Create:
Workflows/OrderWorkflow.cs
using Temporalio.Workflows;
using TemporalDemo.Activities;
namespace TemporalDemo.Workflows;
[Workflow]
public class OrderWorkflow
{
[WorkflowRun]
public async Task<string> RunAsync(
string customerName,
decimal amount)
{
var paymentResult =
await Workflow.ExecuteActivityAsync(
(OrderActivities activities) =>
activities.ChargePaymentAsync(
customerName,
amount),
new()
{
StartToCloseTimeout =
TimeSpan.FromMinutes(5)
});
if (amount > 10000)
{
await Workflow.ExecuteActivityAsync(
(OrderActivities activities) =>
activities.RequestManagerApprovalAsync(
customerName,
amount),
new()
{
StartToCloseTimeout =
TimeSpan.FromMinutes(5)
});
}
await Workflow.ExecuteActivityAsync(
(OrderActivities activities) =>
activities.SendEmailAsync(customerName),
new()
{
StartToCloseTimeout =
TimeSpan.FromMinutes(5)
});
return paymentResult;
}
}
Now we have a workflow.
The workflow says:
- Charge payment
- If amount > ₹10,000, request approval
- Send email
- Finish
Workflow vs Activity
This distinction is extremely important when learning Temporal.
Workflow
if (amount > 10000)
{
// ...
}
The workflow defines the process.
Activity
[Activity]
public async Task<string> ChargePaymentAsync(...)
{
// External API call
}
The Activity performs external work.
Think of it this way:
Workflow
Workflow
|
+-- Activity → Payment API
|
+-- Activity → Database
|
+-- Activity → Email API
Step 5: Create the Worker
The Worker executes our workflow and activities.
Create:
Worker.cs
using Temporalio.Client;
using Temporalio.Worker;
using TemporalDemo.Activities;
using TemporalDemo.Workflows;
var client = await TemporalClient.ConnectAsync(
new("localhost:7233")
);
using var worker = new TemporalWorker(
client,
new TemporalWorkerOptions("order-task-queue")
.AddWorkflow<OrderWorkflow>()
.AddAllActivities<OrderActivities>()
);
Console.WriteLine("Temporal Worker started.");
await worker.ExecuteAsync();
The Worker connects to:
- localhost:7233
which is the Temporal service endpoint in our local setup.
It listens on:
- order-task-queue
for workflow/activity tasks.
Step 6: Start a Workflow from ASP.NET Core
Your API can start a workflow.
For example:
app.MapPost("/orders", async (
OrderRequest request,
TemporalClient client) =>;
{
var workflowId =
$"order-{Guid.NewGuid()}";
await client.StartWorkflowAsync(
(OrderWorkflow workflow) =>;
workflow.RunAsync(
request.CustomerName,
request.Amount),
new WorkflowOptions
{
Id = workflowId,
TaskQueue = "order-task-queue"
});
return Results.Ok(new
{
WorkflowId = workflowId
});
});
The important idea is that your API doesn't need to perform the entire workflow itself.
It starts the workflow:
POST /orders
|
↓
ASP.NET Core
|
↓
Start Workflow
|
↓
Temporal
|
↓
.NET Worker
|
↓
OrderWorkflow
What Happens When We Send an Order?
Suppose we send:
POST /orders
with:
{
"customerName": "Sanjay",
"amount": 15000
}
The workflow executes:
Order Created
↓
Charge Payment
↓
₹15,000 > ₹10,000
↓
Manager Approval
↓
Send Email
↓
Completed
For an order of ₹5,000:
Order Created
↓
Charge Payment
↓
₹5,000 <= ₹10,000
↓
Skip Manager Approval
↓
Send Email
↓
Completed
What Makes Temporal Different?
So far, this might look like ordinary C#.
The interesting part starts when things fail.
Imagine:
Charge Payment
↓
SUCCESS
↓
Manager Approval
↓
Server crashes
A normal in-memory process may lose the current execution state.
Temporal is designed around durable execution.
Conceptually:
Temporal
|
+-- Workflow started
|
+-- Payment completed
|
+-- Approval started
If the Worker crashes, a new Worker can reconnect to Temporal and continue processing the workflow.
This is one of Temporal's biggest advantages.
Activity Retries
External systems fail.
For example:
Your Application
↓
Payment API
↓
503 Service Unavailable
You don't necessarily want your entire workflow to fail permanently.
Temporal allows Activities to have retry policies.
For example, conceptually:
Payment Activity
↓
Attempt 1 → Failed
↓
Wait
↓
Attempt 2 → Failed
↓
Wait
↓
Attempt 3 → Success
This is much safer than implementing random retry loops throughout your application.
A production retry policy should also consider:
- Maximum attempts
- Backoff
- Which errors are retryable
- Which errors are permanent
- Activity timeout
- Long-Running Workflows
Temporal becomes even more useful when a process takes a long time.
Imagine a purchase that requires manager approval.
Order
↓
Payment
↓
Request Approval
↓
WAIT
The manager might approve it tomorrow.
You don't want a .NET thread sitting there doing:
await Task.Delay(
TimeSpan.FromDays(1)
);
Instead, Temporal supports durable timers and workflow waiting mechanisms.
Conceptually:
Workflow
↓
Request Approval
↓
Wait for Signal
|
| 2 days later
|
Manager Approves
↓
Workflow resumes
↓
Send Email
This is a very different problem from simply running a scheduled background job.
Temporal Signals
Suppose the workflow is waiting for approval.
A manager approves the order from an admin application.
The application can send a signal to the workflow.
Conceptually:
Admin Application
|
| "Approved"
↓
Temporal Workflow
|
↓
Resume
|
↓
Ship Order
This allows external events to interact with running workflows.
What if the Worker crashes?
This is one of the first questions you should ask when evaluating Temporal.
Consider:
Workflow
↓
Payment ✓
↓
Inventory ✓
↓
Worker crashes
When a Worker becomes available again, Temporal can provide the information needed to continue the workflow.
This is fundamentally different from relying on variables stored only in your application's memory.
Temporal Architecture
At a high level:
Client
|
↓
ASP.NET Core API
|
↓
Temporal Server
|
Task Queue
|
↓
.NET Worker
|
┌────────┼────────┐
↓ ↓ ↓
Workflow Activity Activity
| |
↓ ↓
Database API
There are three important pieces:
Client
Starts or interacts with workflows.
Temporal Server
Coordinates workflow execution and stores workflow history.
Worker
Runs your workflow and activity code.
Temporal vs Hangfire
If you've used Hangfire in .NET, you may wonder:
Why not just use Hangfire?
Hangfire is excellent for background jobs.
For example:
Every day at 9 AM
↓
Generate Report
Temporal is designed for more complex workflows:
Order
↓
Payment
↓
Inventory
↓
Wait for Approval
↓
Shipping
↓
Email
When Should You Use Temporal?
Temporal makes sense when your workflow is:
- Long-running
- Multi-step
- Distributed
- Failure-prone
- Dependent on external services
- Required to survive application restarts
- Waiting for external events
- Important enough that losing state is unacceptable
Examples include:
Payment processing
Travel booking
User onboarding
Document processing
create image for this flow
When Should You NOT Use Temporal?
Temporal isn't something you should add to every application.
If your requirement is simply:
Run this method every night
you probably don't need Temporal.
Hangfire, Quartz.NET, a cron job or a cloud scheduler may be simpler.
Likewise, if you just need:
HTTP Request
↓
Call API
↓
Send Email
a simple ASP.NET Core service or an automation platform such as n8n may be more appropriate.
Use Temporal when the workflow itself is important and needs durable execution.
A Practical Mental Model
Think of these tools like this:
Hangfire
↓
"Run this job."
Temporal
↓
"Make this workflow reliably execute
even when things fail."
Final Thoughts
Temporal isn't just another background-job library.
Its main idea is durable execution.
Instead of building your own infrastructure for:
- retries
- workflow state
- timers
- failure recovery
- long-running processes
- external events
- workflow history
you can model your business process as a Temporal workflow and let the Temporal platform handle the execution infrastructure.
For .NET developers building distributed systems, payment systems, order processing, SaaS platforms, AI pipelines, or other long-running processes, this can be extremely valuable.
key takeaways:
Don't think of Temporal as a scheduler. Think of it as infrastructure for reliably executing business processes.
Don't just build workflows that work — build workflows that can survive failures.
Have you used Temporal or another workflow engine in your .NET projects? Share your experience in the comments.





Top comments (0)