As one of two founding engineers, I held primary ownership of Cendra's web and mobile product surfaces, and contributed across the integration and operational systems around them. Over twelve months the work grew from rebuilding the early web product into most of the surface through which short-term rental operators configure, monitor and run the product — and into being the integration point that carried a widening contributor base's work safely into production.

The measured claims below say where my hands were; they do not say what the year actually felt like, which was learning at speed from people better than me at their own crafts. Mümin Şahin led the backend and had shaped its architecture from the company's first day. Aybüke Hamide Ak conceived and built the AI side end to end; working closely with her and Mümin was one of the genuine pleasures of the tenure. Ali Ozaltin taught me most of what this page means by product engineering and kept the team ahead of every trend worth knowing; Can Koseoglu brought modern, customer-journey-first thinking that changed how I build for operators; and on the commercial side, Yağız Alp Yelis owned the product's success with customers as completely as any engineer owned a repository.

At a glance

  • Role — one of two founding engineers, across a twelve-month tenure with 239 active development days.
  • Companycendra.ai; the company's $1M seed announcement (February 2026, led by Revo Capital) names me as founding engineer.
  • Product — a platform automating guest communication and property operations for short-term rental operators.
  • Surface — the web application across 27 product areas in 13 languages; both native mobile applications; the design system; the notification and real-time system; the product layer around the company's AI capabilities; and the release and incident process.
  • Authorship — 84.7% of surviving frontend code by sampled git blame -w -M, and 100% of every commit ever made to the mobile paths.
  • Delivery — 377 production releases coordinated across four repositories, under 17 runbooks I wrote.
  • Measured outcome — approximately 19,000 unnecessary push notifications per day eliminated (extrapolated from a production telemetry sample).

Every figure on this page is explained under what is measured, and how, including what each measurement cannot show. The figures exist because the repositories are private — method is the only evidence I can carry out of employed work — and they measure my share of the work, not the work's worth. The people it was shared with are above.

The product problem

A small engineering team was shipping a multi-surface product — web, iOS, Android, a .NET backend, integration services and a real-time hub — into a market where operators judge the product by whether their phone behaves reasonably. That combination produces two problems that dominated my time.

The first is that a young web codebase carrying 27 product areas in 13 languages either becomes a design system or becomes unmaintainable, and the choice is made by whether anybody does the consolidation work.

The second is notifications. The system was producing push notifications and in-app notifications from independent producers, which meant they could disagree: a user could receive a push for something that never appeared in their notification list, or the reverse. Volume was also high enough that a large share of what was being sent did not warrant a notification at all — and an ignored notification stream is the failure that cannot be undone by sending better notifications later.

What I owned, and where the lines ran

What I owned

  • The web application — primary author, and the design system the rest of the team built against.
  • Both mobile applications — sole author across the entire history of the iOS and Android projects, including the release chain.
  • The notification and real-time system — end to end, spanning four repositories, including its whole test suite.
  • The AI product layer — the surfaces users touched, and the transport between them and the AI services.
  • Release and incident process — 17 runbooks, 377 coordinated production releases, and the integration merges that brought multi-author work to production.
  • The backend, as a substantial contributor — primary author of its test suite, and of the parts that needed design rather than volume.

The division of labour on the founding team: Aybüke owned the core AI architecture — the LangGraph services, agent workflows and RAG pipelines — and I built the product layer those capabilities reached users through, working inside the service code when integration or production problems required it. The backend predates me and grew under Mümin's leadership; I joined it late and contributed substantially rather than architecting it. Cendra's product-level metrics — automation rates, platform reach — are the company's figures. None of this is a capability line: where I own an AI architecture end to end, it is LILA.

The shared ground between us — agent configuration, the knowledge base, agentic rule authoring and the in-product assistant — met over a transport layer of AG-UI, server-sent events and SignalR. Git can show authorship, changed surfaces and who carried what into production; it cannot show why a decision was made or what was agreed in conversation — so this page claims only the former kind of thing.

The shares below are the measured version of that boundary. Each bar is my attributed share of one surface; the remainder belongs to other contributors. The rows measure different things — surviving lines for the blame rows, commit share for the others — and each is defined under what is measured, and how.

Web application84.7% · sampled blame
iOS and Android100% · every commit, all time
Design system97.9% · commit share
Notification test suite100% · 79 of 79 commits
Backend23.2% · sampled blame, joined late

Filled — attributed to me. Web and backend are surviving-code shares by sampled blame; the rest are commit shares.

The product surface

Cendra's web application is organised into 27 product areas — the count is the number of top-level areas in the application's authenticated route structure at my tenure's end, so it is a reproducible figure rather than a marketing one. A list of 27 names would say very little. Here is the product the way an operator meets it, and where my work sat in each part.

Configuring the system. Defining agent behaviour, personas and operating constraints; managing the knowledge sources agents reason over; expressing automation rules in an operator's own terms through a visual editor. I built these surfaces — the agent configuration area, the knowledge base, the rule editor — in full.

Connecting the outside world. Operators arrive with a property management system and guest conversations spread across messaging channels, WhatsApp among them. The WhatsApp-facing surfaces were substantially mine; the wider set of provider-connection flows and the PMS- and channel-facing backend carried more hands, with my part there a contribution. The integration services that normalise provider traffic were other engineers' ground.

Running the day. The unified inbox, scheduling and calendar views, task management, the workflow builder, upsells and payment flows. My share here varies by area, and I would rather say so than average it: the workflow builder is mine in full, the inbox and the payment flows were substantially mine during my tenure, and the calendars, task management and upsells carried more hands than mine.

Getting from signup to production use. The onboarding flow, the dashboard an operator lands on afterwards, and the notification settings that decide what reaches their phone. Onboarding predates me; during my tenure I became its primary author.

The point is not that I touched everything — the measured shares above show I did not. The point is that configuration, connection, daily operation and onboarding were one coherent responsibility, and they are the context for everything below.

Integration and production delivery

The contributor base of the frontend widened while I was there — co-founders and new engineers shipping alongside me. My role at that boundary is one of the better-evidenced claims on this page: during my tenure, 111 of the 118 integration merges into the frontend's production branch were mine.

Two delivery figures appear on this page, and they measure different things. 377 production releases counts release events coordinated across all four repositories — web deployments, mobile over-the-air bundles, backend and real-time services. 111 of 118 counts something narrower and directly git-verifiable: merge commits on one production branch's first-parent history, the record of whose hand brought changes into production in that repository. One is breadth across repositories; the other is depth on one branch. Neither is a count of code I wrote — those figures are elsewhere on this page.

The staging branch makes the same point from the other side, and it belongs here for honesty: on staging, contributors merged their own work — my share of its integration merges over the tenure was 243 of 483, roughly half. Integration did not converge on me until the production gate, and that is exactly what the two figures together say: a team that shipped freely to staging, and one person carrying the release step.

In practice this meant multi-author changes moved into production in repeatable release waves, under the 17 runbooks described below. A merge commit shows who integrated a change, not who reviewed it; the claim here is the release step, and I held it.

Deep dive: the notification and real-time system

A notification system needs a suppression model before it needs a delivery model, because volume is easy to add and very hard to take back.

The system I owned most completely spanned four repositories. Domain events entered an event-processing consumer that classified and routed them; the consumer wrote to an outbox; the real-time hub read from the outbox and drove live web updates and the push service, which reached iOS and Android; the in-app notification centre read from the same outbox rows.

Around that sat the parts that determine whether a notification stream is tolerable: fan-out with deduplication and stable grain keys, burst suppression, preference gating, rate limiting, collapse keys, recipient filtering, and device-token lifecycle management. I wrote its entire test suite — 79 of 79 commits to the notification test directory.

This is described at the level Cendra has already discussed publicly. There is no source code, no internal architecture document and no infrastructure detail on this page, and there would not be for any future employer's work either.

One outbox row drives both push and in-app notification

Before the change, the two were produced independently and could therefore disagree. Making them the same record made divergence structurally impossible rather than a bug to be fixed repeatedly.

The trade-off is coupling. One record now has to satisfy two consumers with different needs — a push payload wants to be small and a notification-centre entry wants to be rich — and a schema change touches both surfaces at once. I would take that over two producers whose disagreement is invisible until a customer reports it.

Suppression before delivery — and correcting my own design to get there

My initial design treated delivery as the problem and suppression as a later refinement. That ordering was mine and it was wrong. A notification system needs a suppression model before it needs a delivery model, because volume is easy to add and very hard to take back once users have adjusted to it.

The correction was a three-phase programme: event classification separating genuinely notifiable events from telemetry, then preference gating, rate limiting, recipient filtering and collapse keys. The measured result is below. This work is the direct ancestor of the delivery gate in LILA, where suppression is the first thing built rather than the last.

Shipping it: two native shells and a written-down release process

One web codebase in two native shells

Both mobile applications are a single web codebase wrapped in native shells through Capacitor, with sixteen native plugins, Firebase Cloud Messaging for push, over-the-air update delivery, and publication through App Store Connect and Google Play. For a three-engineer founding team shipping to web, iOS and Android, the alternative was not two native apps — it was no mobile apps.

The trade-off is that you inherit the platforms' disagreements. Keyboard handling is the clearest example: iOS WKWebView and Android soft keyboards resize the viewport differently, and both fight a web layout that owns its own scroll containers. The resolution was to let the native layer resize while explicitly disabling the framework's scroll assistance, because otherwise the page double-scrolls, and then to carry the keyboard height in a CSS custom property set by a hook, which drives the composer, dialog and sheet layouts through three dedicated stylesheets. That is the kind of problem you only meet by shipping.

An over-the-air update feature I chose not to ship

The update mechanism applied bundles on the relaunch after next, which produced recurring "I shipped it but the app is still old" confusion. I changed it to apply on the next cold start. That opened a narrow window during boot, and the update library offered a splash-screen feature to cover it.

I tried it, then reproduced a regression on a real device: the splash flashed on every background-to-foreground switch, not only on cold boot. Reading the plugin's own source explained why — the configuration I had enabled mapped to a mode that made the handler fire unconditionally. I reverted it and wrote the reasoning into the configuration file, including the source file and approximate line, so nobody would re-litigate the decision.

A narrow benefit during boot was not worth degrading every app switch. That episode is the clearest illustration I have of how I work: reproduce on real hardware, read the dependency's source rather than guessing, decide against shipping, and write down why.

Writing the release process down

Releases spanned four repositories with ordering constraints: backend routes had to land before the frontend calling them, and a web deployment and a mobile over-the-air bundle had to activate together. That ordering lived in one person's head, which is a single point of failure disguised as competence.

The fix was 17 runbooks covering staged rollout, feature-flagged release, a 48-hour post-merge soak, rollback, cross-repository dependency ordering, smoke matrices, incident response and on-call handover. The other half of it is a completion standard: nothing counted as done until it had been observed behaving correctly in production logs, in the database, or on a real device. I adopted that rule after being wrong often enough to stop trusting a passing build.

The cost is process weight on a small team, and runbooks decay unless they are used. The cadence is what kept these ones current: 377 production releases across four repositories, roughly one every 22 hours.

The AI product layer, and its boundary

Aybüke owned the orchestration. I built what users touched:

  • Agent configuration — defining agent behaviour, personas and operating constraints.
  • Knowledge base — managing the sources agents reason over.
  • Agentic rule authoring — a visual interface for expressing automation rules in an operator's own terms.
  • In-product assistant — conversational assistance inside the product.

Plus the transport between the product and the AI services: the Agent-UI protocol, server-sent events for streaming, and the real-time hub for notifications.

The rule-authoring surface is the piece worth discussing. Building an interface through which a non-technical operator expresses a rule that an agent will then enforce requires understanding the rule semantics about as deeply as the person implementing the enforcement: the interface has to prevent expressing rules the engine cannot honour, and it has to make the consequences legible before the user commits to them. That is why I contributed to how those semantics were shaped — the interface work and the semantics work are not separable.

What is measured, and how

The measurements below were taken while I held authorised access to the repositories, using ordinary git invocations against the production branches. I cannot demonstrate them live and would not offer to: displaying a former employer's private repository to a third party is not something I am willing to do. What I can offer is the method in full, so it can be judged on its own terms, and two Cendra co-founders who have agreed to act as references — contact details on request.

Method. Three techniques, in increasing order of rigour. An all-branch commit count measures total authored activity and inflates, because it counts abandoned branches. A production-branch count scoped to my tenure measures what reached customers while I was there, but counts commits rather than surviving code. git blame on the production branch measures which lines are alive in production and who wrote them; it is slow, so it is sampled. Before any share is computed, author identities are normalised — the same person can appear in a history under more than one name and email, and counting me strictly while counting others loosely (or the reverse) would bend every percentage on this page.

Blame is the authoritative one, and the parameters are load-bearing: git blame -w -M origin/prod. The -w flag ignores whitespace-only changes and -M detects moved and copied lines, so reformatted or relocated code is attributed to its original author rather than to whoever touched it last. Both make the figure more conservative rather than more flattering, and anyone familiar with the command can check that claim without seeing the repository. Sampling is deterministic — every Nth file across the sorted file list — so it is reproducible and not biased by directory. Where a figure is scoped to my tenure it says so, because the repositories predate me and all-time totals would count years of other people's work I had no opportunity to participate in.

Frontend. Blame on a deterministic one-in-seven sample of the production TypeScript sources — several hundred files and tens of thousands of lines attributed — puts 84.7% of surviving frontend code under my authorship, with the remainder spread across eight other contributors. Commit share on the production branch, scoped to my tenure, is 80.7%.

The gap between those two figures is the point. A blame share above a commit share means code written by others was disproportionately replaced by code written by me: I did not only add volume, I rewrote and consolidated the existing surface. That is why I describe myself as the web application's primary author rather than as the person who wrote the most.

Mobile. 47 of 47 commits to ios/, 28 of 28 to android/, 11 of 11 to the Capacitor configuration, and 11 of 11 to the Playwright end-to-end suites. Across the entire history of the repository, no other author has ever committed to those paths. It is the least arguable figure here, and also the narrowest: it says I wrote all of it, not that all of it was hard.

Design system and shared layer. 140 of 143 commits to the 93 component primitives (97.9%), 174 of 175 to the styling layer (99.4%), 484 of 513 to shared hooks, layouts and utilities (94.3%), and 310 of 429 to the locale files covering 13 languages including full Arabic right-to-left (72.3%). Volume is not the point of measuring that; the point is that the component library became the standard the rest of the team built against, which is what separates a design system from a folder of components. I also built the CI tooling that enforces internationalisation — translation-coverage auditing, hardcoded-string detection and design-token linting — because translation that is not enforced in CI decays within weeks.

Backend. Blame on a deterministic sample of the production C# sources attributes 23.2% of surviving backend code to me; tenure-scoped commit share is 30.9%. Both figures belong with the caveat above: I joined six months into my tenure, eighteen months into the codebase's life. Where that contribution sits is more informative than the total — since joining, my share of commits is 83.2% of the test suite, 55.4% of the event-processing consumer, 53.6% of domain services, 24.1% of infrastructure, 20.8% of the core domain and 16.6% of CQRS feature handlers. Low in shared CRUD surfaces, higher where architectural judgement was required. Other contributors' shares are their own information and are not mine to publish.

The 27 product areas. The count of top-level product areas in the authenticated application's route structure at the tenure-end snapshot — a directory count, reproducible by anyone with the repository, not an estimate or a rounding.

Product-surface shares. Where the product-surface section says an area was "mine in full", "substantially mine during my tenure" or "carried more hands than mine", each phrase summarises a tenure-scoped commit count for that area's paths, excluding merge commits, my share against the total — measured on both the staging and the production branch, with the band taken from whichever branch is weaker for me. The per-area numbers stay in my private records rather than on this page for two reasons: area counts must never be summed — one commit can touch several areas — and a wall of per-area figures would imply a precision that commit counts do not have. Commit counts measure activity, not difficulty.

Integration merges. Counted with git rev-list --count --first-parent --merges, author-filtered and tenure-scoped, on both delivery branches. Production branch: 111 of 118 (all-time 111 of 197; the difference is the branch's history before I joined). Staging branch: 243 of 483. First-parent history is what makes these the integration record — it counts merges exactly as each branch received them. The pairing is deliberate: staging shows a team merging its own work; production shows where integration converged. Neither figure is the same thing as the 377 releases above: a release can span several repositories, and not every merge is a release.

Across four repositories, production branches scoped to my tenure: 5,717 of 10,402 commits, or 55.0% combined.

The notification outcome. Approximately 19,000 unnecessary push notifications per day eliminated, with zero degraded events and zero authentication failures. The method matters as much as the number: this was measured in production log telemetry over a ten-minute observation window against the prior baseline, and then scaled to a daily figure. It is not a directly observed 24-hour count, and I would not present it as one. It is a measurement of a ten-minute sample, extrapolated, and it was measured rather than inferred from the size of the change.

Limitations, and what I would change

  • The blame figures are sampled, not exhaustive. A one-in-seven deterministic sample is reproducible and unbiased by directory; it is still a sample, and a full blame pass could move the number in either direction.
  • The 19,000/day figure rests on a ten-minute window. A day of observation across weekday and weekend traffic would be a better measurement, and I no longer have access to take it. The direction and the order of magnitude are sound; the precision implied by a tidy five-figure number is not.
  • Commit counts measure activity, not difficulty. 100% of a path with 47 commits and 84.7% of a large surviving codebase are very different claims, and the mobile figure is the weaker of the two despite being the tidier one.
  • The integration-merge figures inherit branch discipline. First-parent history only records integration that happened through merges; squash strategies and direct pushes leave no merge commit, and process was thinner before my tenure than during it. 111 of 118 says who carried changes into production, and 243 of 483 says staging was genuinely shared; neither says any change was reviewed.
  • The product-surface bands are commit-derived. "Substantially mine" summarises a tenure-scoped commit share, and inherits everything commit counts cannot show.
  • I got the notification design ordering wrong first time. The suppression programme was me correcting my own earlier design, not a feature I planned. What I take from it is that the ordering of a system's concerns is itself a design decision, and I now build the restraint half first.
  • The over-the-air splash regression was found by chance on a real device. No test I had would have caught it, because there was no automated coverage of background-to-foreground transitions at all. If I were doing it again, the device-lifecycle path would get explicit coverage before the release chain got another feature.
  • I cannot show you any of this running. That is a genuine limitation of private employed work rather than a rhetorical one, and it is why the method above is written out in more detail than the figures.

Further figures from this tenure — specifications written, implementation plans and merge commits — are on engineering evidence.

What colleagues say

As the engineer responsible for Cendra's core AI services, I worked with Efe extensively on bringing LangGraph-based agents, RAG pipelines, and AI-driven workflows into the user-facing product. … Our work often crossed the boundaries between AI, frontend, backend, and real-time infrastructure, and Efe was consistently collaborative and solution-oriented throughout that process.

— Aybüke Hamide Ak, Founding AI Engineer at Cendra (via LinkedIn · July 2026)

What impresses me most about Efe is the way he works—taking full ownership of every task and attending to every single detail.

— Yağız Alp Yelis, Account Manager at Cendra (via LinkedIn · July 2026)

Full recommendation texts are on the resume references section.

September 2025, a month into the tenure: the Cendra year was shipped from wherever the laptop opened. That is a description of how the work happened, not a recommendation of it.

Public record

Cendra announced a $1M seed round on 11 February 2026, led by Revo Capital, with follow-on participation from Heartfelt Capital, Türkiye Development Fund and APY Ventures. The announcement is company-authored and names the founding team — "Co-founded by Can Koseoglu, Mumin Sahin, and Ali Ozaltin alongside founding engineers Aybuke Hamide Ak and Efe Genc" — rather than presenting a personal metric of mine. The announcement names two founding engineers, Aybüke and me, and that is the count this page uses. In engineering terms there were three of us building from the start — Mümin, listed among the co-founders, on the backend; Aybüke on the AI services; and me across the product surfaces.