By the way, this is only Part 1. In Part 2 I go deeper into how we handled releases, branching, and hotf-ixes on top of this workflow.A Practical Workflow That the Whole Team Could Live WithOn one of my projects, we were building a brand-new team from scratch, and I was trying to find a workflow that would actually fit us. We started with a small MVP that quickly turned into a much bigger product, and the pace of changes was high. What we were doing, how we were doing it, when things had to be delivered, and which part of the team handled what - everything evolved constantly.So we needed something flexible, but at the same time simple, transparent, and easy for everyone to follow. A setup that felt clear, predictable, and honestly just comfortable to work with.Here's how it worked, why it helped the team move faster, and why I would set it up the same way again.High-level flowVisual version of the workflow I describe in this article.How we structured ticketsEpic↳ Story 1↳ BE Subtask↳ FE Subtask↳ QA Subtask↳ Story 2↳ BE Subtask↳ FE Subtask↳ QA SubtaskBefore diving into the statuses, it’s worth mentioning how we actually organized the tickets themselves. We used a pretty classic story-based structure, but with a few tweaks that made day-to-day work much easier.Each feature started with an Epic. Inside the epic we had several Stories - usually one story per logical piece of the feature.And inside every story, we created subtasks for each team involved:Backend subtask - for service logic, API changes, data flows.Frontend subtask - UI, FE logic, integration with BE.QA subtask - test scenarios, validation, edge cases.This structure had a few big advantages:We could easily track progress for each part separately - BE done, FE done, QA in progress, etc.It was simple to see which part is blocking which.Estimations were more accurate since each subtask had its own time tracking.Stories stayed clean, and epics gave us the “big picture”.Nothing revolutionary - but having this structure in place made the whole workflow below much more predictable.Ticket statuses at a glanceThe lifecycle of a ticket was split into a few clear and predictable statuses:New - freshly created, waiting in the backlog.In progress - developer is actively working on it.To test - deployed to integration and ready for QA.In QA - QA team is currently testing it.Tested - QA finished checks, no issues found.Closed - PO approved and the ticket is delivered.Vertically, the workflow also showed who owns the ticket at each step:Development - from New to To test.QA - In QA and Tested.PO - Closed.Development flow: from “New” to “To test”The developer part was intentionally simple. No overthinking, no fancy rules. The first thing you always checked was: which branch should I work on?If the ticket already had a branch - perfect, reuse it. If not, you created a new one using our naming pattern. That’s it. One rule, zero confusion.After that, it’s the usual flow: implement your changes. Work locally, keep commits clean, don’t push broken stuff.When everything works locally, you create an MR and deploy it to the dev environment. This step saved the team so many hours - devs could instantly test their work before QA ever saw it. A quick smoke test on dev usually prevented the typical back-and-forth.Only when everything worked on dev did we move forward: MR gets reviewed, merged into integration, and the ticket moves to “To test”, officially entering the QA phase.What happens next: QA stepsWhen a ticket reaches “To test”, development is done and the QA team takes over. To make things fully transparent, we split QA into two simple stages.A tester picks up the ticket and moves it to “In QA”. This just means: “I’m testing this right now.” No guessing, no wondering who is working on what.While in QA, the tester checks the functionality on the integration environment: behaviour, edge cases, UI, logic - everything. If something is off, QA creates a bug ticket linked to the original one. The original ticket returns to “In progress” and we continue from there.If everything looks good, QA finishes their checks and moves the ticket to “Tested”. At this point, the feature is essentially ready for PO validation.PO validationIn “Tested”, the Product Owner does the final check. They validate behaviour, acceptance criteria, business logic - basically making sure the ticket does what it’s supposed to do.If something doesn’t match expectations, the PO sends the ticket back to “In progress”. Simple loop, nothing complicated.If the PO is happy with the result, the ticket moves to “Closed”. After deployment, it becomes part of the release and the cycle is complete.Why this workflow worked so wellOwnership was always clear - Dev → QA → PO. Zero ambiguity.Adding the “In QA” status fixed the classic “is anyone testing this?” problem.Developers tested on dev first - reducing pointless QA cycles.Branch naming was predictable - you could always find what you need.Manual steps were easy t…