
Wie AWS Step Functions komplexe Geschäftsprozesse serverlos abbilden – gezeigt an Lead-Generierung und Email-Outreach aus unserer Praxis.
Serverlose Architekturen sind leistungsfähig – aber sobald ein Prozess mehr als eine Lambda-Funktion umfasst, wird die Orchestrierung zur Herausforderung. Wer hat den aktuellen Status? Was passiert bei einem Fehler in Schritt 3 von 7? Wie wird ein Retry gehandhabt? Genau hier kommen AWS Step Functions ins Spiel.
In diesem Artikel zeigen wir, wie Step Functions komplexe Business-Workflows serverlos abbilden – mit zwei realen Praxisbeispielen aus unserer Arbeit bei codehero: Lead-Generierung für Photovoltaik-Angebote und automatisierte Email-Outreach-Kampagnen.
AWS Step Functions ist ein vollständig verwalteter Orchestrierungsservice, der es ermöglicht, mehrere AWS-Services zu visuellen Workflows zu kombinieren. Statt Abhängigkeiten zwischen Lambda-Funktionen hart zu verdrahten, definiert man den Ablauf deklarativ in der Amazon States Language (ASL).
Jeder Workflow wird als State Machine modelliert: Eine Abfolge von Zuständen (States), die Aufgaben ausführen, Entscheidungen treffen, parallel verzweigen oder auf externe Events warten. Der Service übernimmt dabei automatisch Zustandsverwaltung, Fehlerbehandlung und Retry-Logik.
Viele Teams starten mit Lambda-zu-Lambda-Aufrufen: Eine Funktion ruft die nächste auf. Das funktioniert bei 2-3 Schritten – skaliert aber schlecht:
Step Functions lösen diese Probleme auf Infrastruktur-Ebene:
| Feature | Lambda-Verkettung | Step Functions |
|---|---|---|
| Zustandsverwaltung | Manuell (DynamoDB, S3) | Automatisch |
| Fehlerbehandlung | try/catch pro Funktion | Deklarative Retry/Catch |
| Parallelisierung | Manuell mit Promise.all | Parallel-State |
| Monitoring | CloudWatch Logs zusammensetzen | Visuelle Ausführungshistorie |
| Timeouts | Lambda-Timeout (15 Min.) | Bis zu 1 Jahr (Standard) |
| Kosten bei Idle | Nicht möglich | Wait-State kostenlos |
Eine State Machine besteht aus verschiedenen Zustandstypen, die sich kombinieren lassen:
Führt eine Aktion aus – typischerweise eine Lambda-Funktion, aber auch DynamoDB-Operationen, SQS-Nachrichten, ECS-Tasks oder HTTP-Endpunkte sind direkt integriert (über SDK-Integrationen, ohne Lambda dazwischen).
Verzweigt den Workflow basierend auf Bedingungen – vergleichbar mit einem if/else in Code. Unterstützt numerische Vergleiche, String-Matching und boolesche Logik.
Führt mehrere Branches gleichzeitig aus und wartet, bis alle abgeschlossen sind. Ideal für unabhängige Verarbeitungsschritte wie gleichzeitige API-Aufrufe oder Datenvalidierungen.
Iteriert über ein Array und führt für jedes Element einen Sub-Workflow aus. Der Distributed Map Modus verarbeitet sogar Millionen von Einträgen parallel – etwa aus S3-Dateien oder DynamoDB-Tabellen.
Pausiert den Workflow für eine definierte Zeit oder bis zu einem bestimmten Zeitpunkt. Der Workflow „schläft" dabei kostenlos – im Gegensatz zu einer wartenden Lambda-Funktion.
Für eigenheim-pv.de haben wir einen vollautomatisierten Lead-Workflow mit Step Functions aufgebaut. Besucher füllen auf der Website einen mehrstufigen Fragebogen aus (Dachtyp, Fläche, Stromverbrauch, Standort) und erhalten innerhalb weniger Minuten ein individuelles Angebot von passenden Fachbetrieben.
{
"Comment": "Lead Generation Pipeline – eigenheim-pv.de",
"StartAt": "ValidateInput",
"States": {
"ValidateInput": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:validate-lead",
"Next": "ScoreLead",
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["ValidationError"],
"Next": "HandleInvalidInput"
}
]
},
"ScoreLead": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:score-lead",
"Next": "CheckLeadQuality"
},
"CheckLeadQuality": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.score",
"NumericGreaterThanEquals": 70,
"Next": "MatchInstallers"
},
{
"Variable": "$.score",
"NumericGreaterThanEquals": 40,
"Next": "EnrichAndMatch"
}
],
"Default": "LowQualityPath"
},
"MatchInstallers": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:getItem",
"Parameters": {
"TableName": "solar-installers",
"Key": { "region": { "S.$": "$.region" } }
},
"Next": "ParallelNotifications"
},
"ParallelNotifications": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "SendCustomerConfirmation",
"States": {
"SendCustomerConfirmation": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:send-confirmation",
"End": true
}
}
},
{
"StartAt": "NotifyInstallers",
"States": {
"NotifyInstallers": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:notify-installers",
"End": true
}
}
}
],
"Next": "ScheduleFollowUp"
},
"ScheduleFollowUp": {
"Type": "Wait",
"Seconds": 172800,
"Next": "SendFollowUpEmail"
},
"SendFollowUpEmail": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:send-followup",
"Next": "TrackConversion"
},
"TrackConversion": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:updateItem",
"Parameters": {
"TableName": "leads",
"Key": { "leadId": { "S.$": "$.leadId" } },
"UpdateExpression": "SET #s = :s, completedAt = :t",
"ExpressionAttributeNames": { "#s": "status" },
"ExpressionAttributeValues": {
":s": { "S": "completed" },
":t": { "S.$": "$$.State.EnteredTime" }
}
},
"End": true
},
"HandleInvalidInput": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:handle-invalid",
"End": true
},
"EnrichAndMatch": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:enrich-lead",
"Next": "MatchInstallers"
},
"LowQualityPath": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:low-quality-nurture",
"End": true
}
}
}Für eine B2B-Outreach-Kampagne orchestrieren wir mit Step Functions den gesamten Email-Versand inklusive Personalisierung, Rate-Limiting, Bounce-Handling und automatisierten Follow-ups.
Outbound-Email-Kampagnen sind komplex: Tausende Empfänger müssen individuell angesprochen werden, SES-Sending-Limits dürfen nicht überschritten werden, Bounces müssen sofort verarbeitet werden, und Follow-up-Sequenzen sollen automatisch gesteuert sein – alles ohne einen einzigen Server zu betreiben.
{
"Comment": "Email Outbound Outreach Pipeline",
"StartAt": "LoadCampaign",
"States": {
"LoadCampaign": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:load-campaign",
"Next": "ProcessRecipients"
},
"ProcessRecipients": {
"Type": "Map",
"ItemsPath": "$.recipients",
"MaxConcurrency": 10,
"Iterator": {
"StartAt": "PersonalizeEmail",
"States": {
"PersonalizeEmail": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:personalize-email",
"Next": "SendEmail"
},
"SendEmail": {
"Type": "Task",
"Resource": "arn:aws:states:::ses:sendEmail",
"Parameters": {
"Destination": {
"ToAddresses.$": "States.Array($.email)"
},
"Message": {
"Subject": { "Data.$": "$.subject" },
"Body": {
"Html": { "Data.$": "$.htmlBody" }
}
},
"Source": "outreach@codehero.solutions"
},
"Next": "TrackSent",
"Retry": [
{
"ErrorEquals": ["SES.SesException"],
"IntervalSeconds": 5,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleSendError"
}
]
},
"TrackSent": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:updateItem",
"Parameters": {
"TableName": "outreach-tracking",
"Key": { "recipientId": { "S.$": "$.recipientId" } },
"UpdateExpression": "SET emailStatus = :s, sentAt = :t",
"ExpressionAttributeValues": {
":s": { "S": "sent" },
":t": { "S.$": "$$.State.EnteredTime" }
}
},
"Next": "WaitForFollowUp1"
},
"WaitForFollowUp1": {
"Type": "Wait",
"Seconds": 259200,
"Next": "CheckEngagement"
},
"CheckEngagement": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:check-engagement",
"Next": "ShouldFollowUp"
},
"ShouldFollowUp": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.opened",
"BooleanEquals": false,
"Next": "SendFollowUp1"
},
{
"Variable": "$.replied",
"BooleanEquals": true,
"Next": "MarkAsConverted"
}
],
"Default": "SendFollowUp1"
},
"SendFollowUp1": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:send-followup",
"End": true
},
"MarkAsConverted": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:updateItem",
"Parameters": {
"TableName": "outreach-tracking",
"Key": { "recipientId": { "S.$": "$.recipientId" } },
"UpdateExpression": "SET emailStatus = :s",
"ExpressionAttributeValues": {
":s": { "S": "converted" }
}
},
"End": true
},
"HandleSendError": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:handle-bounce",
"End": true
}
}
},
"Next": "GenerateReport"
},
"GenerateReport": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-1:xxx:function:campaign-report",
"End": true
}
}
}Map State mit MaxConcurrency: Durch das Limit auf 10 gleichzeitige Ausführungen halten wir die SES-Sending-Rate ein – ohne externen Scheduler oder Queue.
Intelligentes Follow-up: Der Choice-State prüft nach 3 Tagen, ob die Mail geöffnet oder beantwortet wurde. Nur Empfänger ohne Engagement erhalten eine Follow-up-Mail.
Direkte SES-Integration: Der Versand nutzt die SDK-Integration (arn:aws:states:::ses:sendEmail) – keine Lambda-Funktion nötig, was Kosten und Latenz reduziert.
Fehlerresilienz: Wenn SES einen Fehler wirft, greift automatisch die Retry-Logik mit exponentiellem Backoff. Permanente Fehler (Bounces) werden separat behandelt.
Der State-Input/Output ist auf 256 KB begrenzt. Große Datenmengen gehören in S3 oder DynamoDB – die State Machine verwaltet nur die Referenzen.
Da Step Functions bei Fehlern automatisch Retries ausführen, müssen alle Lambda-Funktionen idempotent sein. Verwende DynamoDB Conditional Writes oder Idempotency-Keys.
| Eigenschaft | Standard | Express |
|---|---|---|
| Max. Laufzeit | 1 Jahr | 5 Minuten |
| Ausführungsgarantie | Exactly-once | At-least-once |
| Preis | Pro State-Transition | Pro Ausführung + Dauer |
| Ideal für | Geschäftsprozesse, Long-Running | High-Volume, Event-Processing |
Definiere Retry-Policies direkt im State – nicht in der Lambda-Funktion. BackoffRate und MaxAttempts sorgen für kontrollierte Wiederholungen. Catch fängt Fehler ab und leitet sie an Error-Handler weiter.
Für einfache Datenmanipulation braucht man keine Lambda-Funktion. Intrinsic Functions wie States.Format(), States.JsonMerge() oder States.Array() können direkt in der ASL-Definition verwendet werden.
Step Functions Standard Workflows berechnen pro State-Transition:
Für den Lead-Generation-Workflow mit 7 States pro Durchlauf:
Ja, wenn:
Nein, wenn:
AWS Step Functions verwandeln komplexe, fehleranfällige Service-Ketten in deklarative, visuelle Workflows. Die beiden Praxisbeispiele zeigen, wie unterschiedlich die Einsatzmöglichkeiten sind: Von Consumer-Facing Lead-Generierung mit automatisierten Follow-ups bis hin zu B2B-Outreach-Kampagnen mit intelligenter Engagement-Steuerung.
Der größte Vorteil: Man muss sich nicht mehr um die Orchestrierungslogik kümmern. Keine selbstgebauten Queues, keine Status-Tracking-Tabellen, keine Retry-Wrapper. Step Functions übernehmen die undankbare Arbeit – und man kann sich auf die Geschäftslogik konzentrieren.
Step Functions sind der Kleber, der serverlose Architekturen zusammenhält. Sie ersetzen hunderte Zeilen Orchestrierungscode durch eine JSON-Definition.
Sie planen einen Workflow mit AWS Step Functions? Sprechen Sie mit unseren AWS-Experten – wir helfen Ihnen bei Architektur, Implementierung und Betrieb.
Wolfgang Müller
CEO & Fullstack-Developer
Kostenloses Erstgespräch — ehrlich, technisch fundiert, unverbindlich. Antwort innerhalb von 24 Stunden.