Yahoo Mail 6 - Rethinking Email Engineering

Female co-workers discussion in an office

By James Rochabrun, Software Apps Engineer

Launching Yahoo Mail 6 for iOS required us to re-architect our navigation system, while also migrating from Objective-C to Swift without negatively impacting the experience of our production app. 

With 4.6 stars in the App Store and millions of daily active users, we maintain a rigorous system that allows us to maintain our low average crash rate of just 0.02%, so this was an interesting challenge.

From an engineering perspective, this was not technically a re-write but rather a re-implementation of our container and navigation system, and re-skinning of various existing UI components. It was the perfect opportunity for us to gradually move away from Objective-C and old school frame-based layouts and adopt new Apple technologies like AutoLayout and Swift.

Planning and Execution

Navigation through the old Yahoo Mail App is initiated via a “Hamburger” menu. In our new app the most important content from your email is now divided across different tabs at the bottom of the screen, while key components like Search and Compose reside in the top navigation, and the folder switching function has been brought into the Inbox button.

Early on in the process, only a few engineers were tasked with working on these changes to set base infrastructure, leaving the majority of the team focused on maintaining our app in production. This approach was key because we could ensure that our production app was not impacted as new features were built. As the year went by, more engineers were moved into the project until we were fully staffed on building out the new Yahoo Mail. We divided our entire build and deployment process into 5 phases:

Phase 1 • Setup

 We built a skeleton app with key features in place such as account management, instrumentation, and database support that lived in a new target alongside our existing app. This way, we could share files and as new UI components were completed, they'd only be added to the new skeleton app.

In the meantime, we could readily prototype our new navigation in a lite version of our app and share the experience easily with product and designers. This later helped our engineering team identify some customization limitations in native iOS components.

Our entire navigation system was handled by just one sided menu and in code, our approach turned out to be equally monolithic: everything was handled by a huge container controller - almost 4,000 lines of code!

This was a great opportunity for us to rethink the architecture, so we created smaller containers that allowed us to easily present our new smart views (e.g. Attachments, Deals, Groceries, Subscriptions, etc.) from the bottom navigation bar. And we also implemented a "Router" system creating small helper classes that encapsulated our navigation and custom transition code, greatly improving our navigation system’s reusability.

So at this point there were 3 sets of files:

  • Files that were included only in old mail target - only old Objective-C files.
  • Files that were included only in new mail target - only new files mostly in Swift.
  • Files included in both targets - only shared components.

Phase 2 • Build 

In this phase, we started to build out our new screens and features while keeping in mind the strategy of “reuse as much as you can.” In doing so we had to improvise and devised 3 basic ways to reuse our existing code:

  • Try to use the existing file as it is. If needed you could use preprocessor macro (eg #if NEWMAIL_TARGET) to do/perform some logic only for new mail target.
  • If changes are fairly huge then subclass or write a container for the existing classes/files.
  • If the feature you are building is completely new then you should just create a new class and start fresh in Swift.

Code duplication was kept at a minimum, and any refactored or new code was written in Swift.

Phase 3 • TestFlight

Ready for Testflight! The plan was to release this as a brand new app with its own app id, provisioning profiles, and certs, etc. This app would not actually go out to production, yet, but allowed us to test with real customers. This went well until our PMs threw us a curveball by introducing the following additional requirements:

  • We need an app which has the capabilities of both the new and current app.
  • The binary size should not exceed more than 20% of the current app.
  • We should be able to control the rollout based on locale and device type.
  • We need to bucket test old and new experience side by side in production to test product metrics.

Basically we would have to do a gradual phased rollout and, most importantly, we would need a way to turn off the new experience if necessary.

Phase 4 • Merging two apps into one

At this point, the engineering team came up with a solution:

  • Continue to maintain two targets in our project.
  • All the files that had been exclusively added to the new target would now also be bundled with the old target.
  • Replace all our build macros (eg #if NEWMAIL_TARGET) with a static variable (isNewMailTarget) that could be checked at runtime.

Our app was ready to change its colors at any time.  We released to 2% of our users in production, made sure their feedback was addressed, fixed crashes and blockers -- and finally were ready to scale our release gradually to 25%, 50% and finally 100% of our users. By the way, our app size only went up 5% and there was no performance impact at all!

Phase 5 • Cleanup

This is our future strategy, one we are in the process of implementing. In this phase, we plan to ditch our old target code. When done, both of our targets and functionality will be the same.

So what were the results of all this work?

  • Performance: The app has a 20% faster cold start due to startup optimizations.
  • Speed: We are pre-caching data and instantiating tabs on-demand.
  • Maintenance: Our maintainability is improved as we are moving to more modern app mechanics and have removed deprecated patterns such as nested navigation controllers.
  • Reusability: It’s much easier to add new tabs or views which are more discoverable than the prior design.
  • Reliability: Moving to Swift for the new code has made it more type-safe and crash resilient.
  • Flexibility: Our app is now highly customizable, including support for iOS 13 dark/light mode.
  • Testing: We have built a flexible system that allows us to perform different bucket tests including perform completely different navigation experiments.

If you didn’t already, go to the App Store and give it a try. Let us know what you think by submitting feedback via the app.