During my three years at Efipeek, I worked primarily on Carter-Cash’s e-commerce platform. My work spanned Symfony backend development and interfaces, from understanding requirements to supporting releases in production.
My cofounder and I developed a vintage and upcycled clothing rental concept, primarily as a subscription box. I was involved across the business and built the first web MVP. With support from EuraTechnologies and 1Kubator, we reached that first MVP before deciding to stop the project ahead of launch.
From the concept to how the service would work
Shaping the offer together
La Bouclup’ was a project built by two co-founders around the rental of vintage and upcycled outfits, primarily through a subscription box. What began as the straightforward idea of renting clothes gradually grew into a broader reflection on how we dress.
My cofounder and I worked across the whole project: shaping the offer, defining how the service would work and turning those decisions into a first MVP. I was responsible for building that web MVP.
Working out how the service would run
The lifecycle of an outfit brought together many of the project’s core questions:
Rental
Return
RestockingInspection & cleaning
Availability
The longer-term goal was to combine the expected return date with preparation time to anticipate when an outfit would become available again and allow it to be reserved for a future box.
This made the connection between a product rule and its technical implementation tangible.
Developing the project with incubator support
Two incubation programs supported us as the project matured:
EuraTechnologiesMay — July 2021
1KubatorApril — June 2022
These programs helped us sharpen the concept, develop the business model, challenge our assumptions, and prepare the pitch and go-to-market approach.
A lasting instinct: bridging product and engineering
~/efipeek/Carter-Cash
Three years working on Carter-Cash
Selected work from my time at Efipeek. Choose a topic to explore the problem, my contribution and the technical decisions.
Carter-Cash wanted to let customers initiate returns online. The initial request defined the goal and considered using a logistics platform, but the workflow, its rules, and its technical feasibility still needed to be worked out.
Working with the Carter-Cash project manager, who represented the business and customer service teams, I explored the Woop API and tested the initial ideas against the site’s constraints. I identified what was feasible, proposed the necessary adjustments, filled in the remaining business rules, and then designed and built the web workflow.
Making complex returns feel simple
A return did not always map to a single delivery. Products could ship separately, arrive on different dates, or be divided across several packages. The rules also changed depending on whether the customer wanted to return the items remotely or at a store.
I translated these situations into a workflow that identifies eligible products, groups them by their original delivery, and creates as many returns and shipping labels as needed. Each step adapts to the customer’s choice: store selection appears only for in-store returns, while split deliveries and multi-package orders are handled by the system.
Technical decisions
Backend with Symfony
Controllers, forms, custom validators, domain services, and Doctrine entities handle return creation, validate the selected products, and group them by their original delivery.
API and asynchronous processing
Dedicated HTTP services manage communication with Woop, including authentication, error handling, and logging. When a shipping label was not immediately available, Symfony Messenger continued retrieving it without blocking the customer’s journey.
Interfaces and documents
Twig screens, supported by JavaScript, jQuery, and SCSS, dynamically adjust the available choices. Return slips and shipping labels are generated from templates, converted to PDF, and then made available to the customer or attached to the relevant communications.
Testing and reliability
PHPUnit tests cover domain services, grouping rules, and the objects exchanged with the APIs. They complement detailed test plans, functional acceptance testing, and error monitoring after release.
One service, regardless of the entry point
The online returns workflow could not cover every situation. A customer might encounter an issue on the site or need direct assistance from customer service to create a return.
In a later iteration, I designed APIs for the internal tool used by customer service agents. They can retrieve the required information and create a return from their own interface while reusing the same services and business rules as the web workflow. Customers and agents therefore follow the same rules, even when the return starts from a different interface.
Owning the solution through production
I supported each update to the returns workflow through release: preparing test data and test plans, running acceptance testing with Carter-Cash and its customer service team, verifying exchanges with the connected systems, and monitoring the production deployment.
Once the solution was live, incidents and change requests related to returns were consistently assigned to me. I investigated their causes, implemented the required fixes, and adjusted the rules in collaboration with Carter-Cash project management. I remained the technical point person for this area until I left the project.
Connecting orders to Datahub
Turning data exchange requirements into a technical solution
Carter-Cash wanted to send its orders to Datahub. Behind that goal was a set of information distributed across the existing model: orders, shipments, customers, stores, products, pricing, history, and fulfillment methods.
I analyzed these sources, identified the required information, and defined how it should be transformed into Datahub’s expected format. I then designed the complete exchange, from the triggering event in the order lifecycle to the construction and authenticated delivery of the data.
Moving data without blocking the application
Datahub delivery could not run directly during a customer action or an important business transition. A slow response or service outage could otherwise block the site unnecessarily.
I chose an asynchronous architecture based on Symfony Messenger. When an order reaches the relevant state, a message is placed on a dedicated queue. A worker later processes it independently, reloads the required information, and sends it to Datahub. The action that triggered the exchange can therefore complete normally, even when the external service is slow or unavailable.
Never letting a data flow fail silently
An asynchronous Datahub delivery can fail long after the order event that triggered it. Without dedicated tracking, an order’s data might never reach the service, while the failure remained difficult to see or reproduce.
I implemented persistent failure tracking for each affected shipment, including the returned status, number of attempts, and date of the latest attempt. Dedicated logs also retain the trigger source and context required for diagnosis. When a later delivery succeeds, the previously recorded failure is automatically cleared.
Making integrations visible and actionable
Datahub delivery monitoring could not rely on logs alone. Access would have remained limited to developers, and incidents would have been difficult to find over time. I therefore proposed a dedicated back-office interface where failed processes are directly visible.
The view brings together the information needed for diagnosis: the related order, shipment, status, failure count, and latest attempt. An action lets the delivery be retried manually through the same service used by the automatic process. A successful retry clears the error; otherwise, its tracking is updated.
Technical decisions
Construction and delivery
A builder transforms orders and their shipments into the expected format. A client built on Symfony HttpClient obtains an access token, sends the data, and interprets the external service’s response.
Asynchronous processing
A change in order state dispatches a Symfony Messenger message. A handler then reloads the shipment and delegates delivery to a shared service that is also used for manual retries.
Errors and operations
Doctrine persists failures and their attempts, while a dedicated Monolog channel provides technical context. Sonata Admin exposes this information and lets users retry a delivery without duplicating business logic.
Other integrations
I also explored the Woop API to replace a legacy source of pickup locations. I identified the required data: location, pickup-point identity, opening hours, and accepted package categories, then completed most of the integration into the delivery journey.
Taking over authentication and reconciling accounts
Becoming the point person for an existing system
Auth0 was already handling customer authentication when I joined the project. I had not taken part in its initial implementation, but account and sign-in incidents gradually led me to work in this area on a regular basis.
I used the Auth0 console to inspect identities, diagnose access issues, and help users regain access. By repeatedly handling these requests and connecting the service’s behavior with the Symfony application, I became the team’s operational point person for Auth0.
Taking over and completing the Symfony integration
Following several security initiatives, the team decided to integrate Auth0’s official Symfony bundle. Another developer had started the work, but their departure left an initial version that was incomplete and only partly adapted to the site.
I took over that foundation, identified the remaining journeys and edge cases, and adapted the integration to the existing application. I completed the missing pieces, removed legacy mechanisms that were no longer needed, and brought the work to a final version ready for deployment.
Securing account creation and lifecycle
Adding an authentication security rule never affected sign-in alone. Account verification, for example, could also affect existing customers, the post-registration journey, error messages, and the ability to resend required communications.
I assessed these impacts and documented the business decisions required before new protections could be enabled. I then developed the Auth0 Actions and triggers, along with the Symfony endpoints needed for the selected registration checks. I also adapted session and logout handling to maintain a consistent lifecycle between Auth0 and the application.
Merging identities without losing their history
The authentication changes required handling customers who had several accounts for the same identity. Simply deleting one could remove part of their history or leave inconsistent relationships between the application and Auth0.
I designed and built a process that detects duplicates, selects the primary account based on activity, and transfers related orders, carts, addresses, vehicles, and relevant preferences to that account. Accounts that are no longer needed are then removed locally and from Auth0 where required. Locks, execution limits, dedicated logs, and summaries make each merge controllable.
Technical decisions
Symfony authentication
The bundle integrates with the Security component through providers, an authenticator, an entry point, and dedicated subscribers. I adapted these components to the application’s different profiles and existing journeys, then removed legacy mechanisms that were no longer needed.
Auth0 lifecycle
Auth0 Actions and triggers communicate with Symfony controllers to apply the required checks during registration. Logout, session, and identifier management were also adapted to keep Auth0 and the application in a consistent state.
Account migration
A Symfony command uses Doctrine and the Auth0 API to merge duplicate accounts, transfer related data, and delete identities that are no longer needed. Locks, execution limits, and dedicated logs protect the operation.
Production release
I was directly involved in the production rollout during a planned window designed to limit customer impact. The new Auth0 foundation, required Actions, and account merge were verified before the full journey was reopened.
Keeping carts across devices
Resuming a cart on any device
Originally, the cart relied mainly on the browser session. After a period of inactivity, signing back in, or moving to another device, customers could lose their active cart and have to start over.
I redesigned cart initialization so it no longer depended only on that session. When a customer signs in, the system can retrieve their latest valid cart from the database and restore it to the journey. If no cart can be resumed, a new one is created for the customer’s current context.
Reconciling cart, identity, and store
Several states could exist at sign-in: an anonymous cart in the current session, an earlier cart still active in the database, or a store that differed from the one previously associated with the customer. Sales associate accounts also required behavior distinct from standard customer accounts.
I structured the rules used to resolve these situations. An anonymous cart can be assigned to the customer who has just signed in; when none exists, the latest valid cart is retrieved. The cart’s store and the session’s store are then synchronized according to context. Customers recover their shopping environment, while sales associate use remains isolated in a dedicated cart.
Understanding cross-device journeys
Cart continuity allowed customers to keep shopping, but it also raised a new question: how did a journey that began on mobile, desktop, or tablet progress through to the order?
I designed tracking for the devices used at key stages of the cart: creation, device change, entry into payment, and completion. Rules ensure that only consistent transitions are recorded, and an aggregated view makes the main cross-device journeys available for analysis. I supported this feature through its production release.
Technical decisions
Cart initialization
A central Symfony service coordinates the different cases: a cart already in the session, assigning an anonymous cart after sign-in, retrieving the latest valid cart, or creating a new one. A Doctrine repository selects the reusable cart based on the state of its related order.
Identity and store context
The rules explicitly distinguish customers from sales associates. The cart also retains its store so a resumed journey can reconcile the saved context with the new session.
Event-driven tracking
EventDispatcher decouples device logging from the main business journey. Events are dispatched at meaningful stages, then a service applies integrity rules, detects the device type, and records only relevant transitions. A SQL view aggregates completed journeys.
Testing and reliability
PHPUnit covers the main initialization scenarios: creation, assignment after sign-in, database retrieval, and sales-associate-specific handling. Security, session, repository, and Doctrine dependencies are mocked to isolate the business rules.
Improving search and product availability
Helping customers find the right product
Choosing a tire involves more than matching its dimensions. Load and speed ratings, season, and Runflat technology all determine compatibility, with different rules for summer, winter, and all-season tires.
As part of an update developed with the team, I helped translate these rules into Algolia filters: comparing ratings, adapting criteria by season, and handling compatible products even when some attributes differed from the initial search. I also worked on the information shown to explain those differences, then verified result consistency using Algolia queries and a functional test plan.
Showing availability customers can rely on
On a site connected to physical stores and several supply sources, availability is more than an inventory count. It also depends on the selected store, ordering options, product characteristics, and whether the customer chooses pickup or delivery.
I helped update these rules and how they were presented throughout the journey. I adapted the use of new inventory, pricing, and availability data provided by Algolia. In a later change, I also centralized the permitted fulfillment options in a Symfony service and added server-side validation so the available choices remained consistent through the cart.
Evolving a legacy catalog
The catalog had grown over time around several data sources. Depending on the product type, country, or screen, information could come from Algolia, external catalog services, or the database. A change to a single attribute could therefore affect search, pricing, availability, analytics, or the final display.
My work often involved taking over work already in progress, identifying its impact across the codebase, and adapting the affected layers. I worked on new Algolia attributes, country-specific behavior, sorting and pagination, availability messages, and fallback values for missing data. Each change required checking several sources and screens, not only the place where the request originated.
Technical decisions
Search and filtering
Symfony services build Algolia filters and facets based on product attributes, vehicle, store, and country. I updated these rules as well as sorting and pagination, particularly for the Tire Selector and product listings.
Display and interactions
Results, active filters, and autocomplete suggestions are rendered with InstantSearch.js, JavaScript, and Twig. The components adapt prices, availability, compatibility information, and available actions to the customer’s context.
Data and availability
The journey combines data from Algolia, catalog APIs, and Doctrine. I adapted the mappings between these sources, added fallback values for missing attributes, and centralized selected availability rules in reusable Symfony services with server-side validation.
Validation and monitoring
Results were checked with Algolia queries, functional test plans, and acceptance testing on the site. Logs and analytics supported diagnosis when a listing or search did not behave as expected.
Addressing reliability risks early
Addressing a risk before it became an incident
Order, shipment, and product references were derived from part of their global identifiers. As volume increased, this mechanism could reuse an earlier value and create a collision. A temporary fix had delayed the problem without removing its cause.
After the risk was raised during a daily meeting, I analyzed the existing mechanism and the many components that depended on these references. I compared several strategies, presented their tradeoffs, and recommended an architecture based on dedicated counters by store, use case, and period. Once the approach was approved, I implemented it with the protections required for concurrent creation.
Maintaining data before it accumulates
I noticed that many obsolete carts remained in the database indefinitely. I raised the issue, then proposed and designed their cleanup without an initial client request. I later designed a similar process for a recurring need involving old redirects.
Both processes distinguish data that is still useful from data that can be removed, then work in batches to limit their impact. Locks, maximum runtimes, logs, execution histories, and email summaries make each run traceable, while scheduled execution maintains the cleanup over time.
Treating regression prevention as part of the job
On an e-commerce platform, a regression can prevent a search, sign-in, or order from completing. Because many rules were interconnected, verifying behavior was an integral part of my development work.
I prepared detailed test plans, ran functional acceptance tests, and added PHPUnit coverage for sensitive services and business rules. Together with Vincent, I also helped create a Cypress project covering many end-to-end journeys. The project was run manually when needed; it was not yet a systematic or fully automated test pipeline.
Technical decisions
Reference generation
To replace the earlier mechanism based on part of the global identifiers, I created a shared generation service for orders, shipments, and ordered products. Each store has its own counters, separated by reference type and period.
Concurrent creation
Doctrine guarantees the uniqueness of each counter. When simultaneous requests attempt to use the same value, optimistic locking detects that the counter has already changed. The service reloads it and retries generation with the next number.
Data cleanup
Obsolete carts and redirects are removed through Symfony Console commands. The processes use DBAL to work in batches, locks to prevent concurrent runs, and maximum runtimes to limit database impact.
Monitoring and operations
Logs, alerts, and email summaries make each execution traceable. A Sonata interface exposes counter usage, while a dedicated command prepares references for a new period or initializes them for a new store.
Testing and tooling
PHPUnit covers reference generation, concurrent creation, and redirect cleanup, among other cases. Data-generation commands also support high-volume acceptance testing outside production.