
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.
Was sind AWS Step Functions?
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.
Warum Step Functions statt direkter Lambda-Verkettung?
Viele Teams starten mit Lambda-zu-Lambda-Aufrufen: Eine Funktion ruft die nächste auf. Das funktioniert bei 2-3 Schritten – skaliert aber schlecht:
- Fehler in Schritt 4 führen zu inkonsistenten Zuständen ohne Retry-Mechanismus
- Der Gesamtstatus des Workflows ist nirgendwo sichtbar
- Timeouts, Parallelisierung und Branching müssen manuell implementiert werden
- Debugging erfordert das Zusammensetzen von Logs aus dutzenden CloudWatch-Streams
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 |
Die wichtigsten State-Typen
Eine State Machine besteht aus verschiedenen Zustandstypen, die sich kombinieren lassen:
Task State
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).
Choice State
Verzweigt den Workflow basierend auf Bedingungen – vergleichbar mit einem if/else in Code. Unterstützt numerische Vergleiche, String-Matching und boolesche Logik.
Parallel State
Führt mehrere Branches gleichzeitig aus und wartet, bis alle abgeschlossen sind. Ideal für unabhängige Verarbeitungsschritte wie gleichzeitige API-Aufrufe oder Datenvalidierungen.
Map State
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.
Wait State
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.
Praxisbeispiel 1: Lead-Generierung für Photovoltaik
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.
Der Workflow im Überblick
- Formular-Eingang: API Gateway empfängt die Formulardaten und triggert die State Machine
- Datenvalidierung: Lambda validiert Eingaben (PLZ, Dachfläche, Verbrauch) und reichert mit Geodaten an
- Lead-Scoring: Basierend auf Dachfläche, Verbrauch und Region wird ein Score berechnet
- Fachbetrieb-Matching: DynamoDB-Abfrage findet passende Solarteure im Umkreis
- Parallel-Versand: Gleichzeitig werden Bestätigungsmail an den Interessenten UND Lead-Benachrichtigungen an Fachbetriebe gesendet
- Follow-up Scheduling: Ein Wait-State plant eine automatische Nachfass-Mail nach 48 Stunden
- Conversion-Tracking: Status-Update in DynamoDB + Analytics-Event
{
"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
}
}
}Warum Step Functions hier ideal sind
- Der Wait-State für das 48h-Follow-up kostet nichts – eine Lambda-Funktion könnte nicht 2 Tage warten
- Parallel-State für gleichzeitige Kunden- und Fachbetrieb-Benachrichtigung
- Choice-State für Lead-Scoring-Branching ohne if/else-Spaghetti
- Automatische Retries bei transienten SES- oder DynamoDB-Fehlern
- Visuelle Nachverfolgung jeder einzelnen Lead-Journey in der AWS Console
Praxisbeispiel 2: Email Outbound Outreach
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.
Die Herausforderung
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.
Der Workflow im Überblick
- Kampagnen-Start: Eine neue Kampagne wird über die Admin-API getriggert
- Empfänger laden: Distributed Map iteriert über die Empfängerliste aus DynamoDB
- Personalisierung: Jede E-Mail wird individuell aus Templates generiert
- Rate-Limited Versand: SES-Versand mit eingebautem Throttling über Wait-States
- Bounce/Complaint Handling: SNS-Subscription verarbeitet SES-Events und aktualisiert die State Machine
- Follow-up Sequenz: Wait-States steuern automatische Nachfass-Mails nach 3, 7 und 14 Tagen
- Reporting: Aggregierte Metriken werden in DynamoDB geschrieben
{
"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
}
}
}Highlights dieser Architektur
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.
Step Functions Best Practices
1. Payload-Größe beachten
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.
2. Idempotente Task-Funktionen
Da Step Functions bei Fehlern automatisch Retries ausführen, müssen alle Lambda-Funktionen idempotent sein. Verwende DynamoDB Conditional Writes oder Idempotency-Keys.
3. Standard vs. Express Workflows
| 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 |
4. Error Handling mit Catch und Retry
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.
5. Intrinsic Functions nutzen
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.
Kosten: Was kostet Step Functions?
Step Functions Standard Workflows berechnen pro State-Transition:
- 4.000 kostenlose State-Transitions pro Monat (Free Tier)
- Danach: $0,025 pro 1.000 State-Transitions
Für den Lead-Generation-Workflow mit 7 States pro Durchlauf:
- 1.000 Leads/Monat = 7.000 Transitions = ca. $0,075/Monat
- 10.000 Leads/Monat = 70.000 Transitions = ca. $1,65/Monat
Wann sollte man Step Functions einsetzen?
Ja, wenn:
- Der Prozess mehr als 2-3 Schritte hat
- Fehlerbehandlung und Retries wichtig sind
- Parallelisierung oder Branching benötigt wird
- Lang laufende Prozesse mit Wartezeiten existieren (Stunden, Tage)
- Nachvollziehbarkeit und Audit-Trail gebraucht werden
Nein, wenn:
- Eine einzelne Lambda-Funktion ausreicht
- Rein Event-getriebene Pipelines ohne Branching (→ EventBridge Pipes)
- Extrem hohes Volumen mit minimaler Logik (→ SQS + Lambda)
Fazit
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.
Haben Sie Fragen zu diesem Thema?
Wir beraten Sie gerne zu AWS, Stripe oder Ihrer individuellen Softwareentwicklung.
Kostenlose Beratung anfragen