<?xml version='1.0' encoding='utf-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xml:base='https://www.fastmail.com'>
    <title>Fastmail Blog</title>
    <subtitle>Blog posts from the Fastmail team</subtitle>
    <updated>2026-03-05T00:00:01Z</updated>
    <id>https://www.fastmail.com</id>
    <link rel='alternate' type='text/html' hreflang='en' href='https://www.fastmail.com/blog/' />
    <link rel='self' type='application/atom+xml' href='https://www.fastmail.com/blog/feed.xml' />
	<rights>&#169; 2026 Fastmail Pty. Ltd. All rights reserved</rights><entry>
            <title>Multi-window support and a better compose experience</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/multi-window-support/' />
			<id>https://www.fastmail.com/blog/multi-window-support/</id>
			<updated>2026-03-05T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;Today we’re releasing a major update to the Fastmail interface, with improvements focused on making mulitasking and compose better. Here’s what’s new.&lt;/p&gt;&lt;h2 id=&quot;multi-window-support&quot; tabindex=&quot;-1&quot;&gt;Multi-window support&lt;/h2&gt;&lt;p&gt;Sometimes you need to reference one email while replying to another, or draft several messages at once. Fastmail now supports opening compose and conversation views in separate windows.&lt;/p&gt;&lt;p&gt;Hold &lt;strong&gt;Shift&lt;/strong&gt; when clicking reply, forward, or any compose action to open it in a new window. You can also use &lt;strong&gt;Shift+R&lt;/strong&gt; to reply in a new window, or &lt;strong&gt;Shift+A&lt;/strong&gt; to reply all. Or pop out a whole conversation into its own window by shift-clicking in the message list, or using the button next to the subject in the top right if it’s already open.&lt;/p&gt;&lt;p&gt;Your undo send timer works across windows too — if you send from a pop-out window, the undo notification appears in your main window so you’re still in control.&lt;/p&gt;&lt;p&gt;&lt;video class=&quot;aspect-video relative rounded-lg shadow-card z-50&quot; autoplay loop playsinline muted controls width=&quot;1280&quot; height=&quot;720&quot;&gt; &lt;source src=&quot;/assets/blog/2026-03-05-multi-window-support/compose+windows.webm&quot; type=&quot;video/webm; codecs=vp9,vorbis&quot;&gt; &lt;source src=&quot;/assets/blog/2026-03-05-multi-window-support/compose+windows.mp4&quot; type=&quot;video/mp4&quot;&gt; &lt;/video&gt;&lt;/p&gt;&lt;h2 id=&quot;inline-replies&quot; tabindex=&quot;-1&quot;&gt;Inline replies&lt;/h2&gt;&lt;p&gt;Replying to a message in a conversation now keeps you right where you are. Instead of switching to a separate compose screen, your reply appears inline within the thread, so you can see the full conversation while you write. It’s the most natural way to reply — you stay in context, and your draft lives alongside the messages you’re responding to.&lt;/p&gt;&lt;p&gt;If you’d rather compose in a focused view like before, you can expand your reply to full screen at any time. We’ll remember your preference.&lt;/p&gt;&lt;h2 id=&quot;a-cleaner-compose&quot; tabindex=&quot;-1&quot;&gt;A cleaner compose&lt;/h2&gt;&lt;p&gt;We’ve unified the look of our compose view across mobile and desktop, giving you a cleaner, more consistent interface wherever you’re writing.&lt;/p&gt;&lt;p&gt;A few highlights:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Switch between Reply and Reply All&lt;/strong&gt; mid-compose without losing your work. A new button in the compose header lets you change your reply mode on the fly, and Fastmail will intelligently recalculate the recipients for you.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Hide the formatting toolbar&lt;/strong&gt; if you prefer a distraction-free writing area. A toggle in the toolbar lets you show or hide formatting options with a single click, and your preference is remembered.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Drag and drop recipients&lt;/strong&gt; between To, Cc, and Bcc. The Cc and Bcc fields appear automatically when you start dragging an address, and tuck away again when you’re done if not needed. You can select multiple recipients using Shift-click for a range, or Cmd/Ctrl click to select individually.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Quickly add a recipient to your contacts&lt;/strong&gt; or view their contact information if already a contact. Just click the recipient to select their token, then click again to get a menu of options.&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;move-the-reading-pane-below-your-inbox&quot; tabindex=&quot;-1&quot;&gt;Move the reading pane below your inbox&lt;/h2&gt;&lt;p&gt;You’ve always been able to show a reading pane to the right of your message list. Now you can choose to place it below instead. This horizontal split gives you the full width of the screen for reading, which is especially useful on narrower monitors. You’ll find the option in your mail preferences.&lt;/p&gt;&lt;h2 id=&quot;and-more&quot; tabindex=&quot;-1&quot;&gt;And more&lt;/h2&gt;&lt;p&gt;For those of us who don’t want our signature repeated in every thread, there’s now an option to turn off signatures on replies, forwards, or both. Head to &lt;a href=&quot;https://app.fastmail.com/settings/preferences&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Settings → Mail preferences&lt;/a&gt; to choose whether your signature appears above or below quoted text — or not at all.&lt;/p&gt;&lt;p&gt;We’ve also added find-in-conversation to our desktop app. Just hit Cmd-F on Mac, or Ctrl-F on Windows/Linux, to get a search box letting you quickly find text in a long conversation.&lt;/p&gt;</content>
        </entry><entry>
            <title>Why customers trust Fastmail</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/why-customers-trust-fastmail/' />
			<id>https://www.fastmail.com/blog/why-customers-trust-fastmail/</id>
			<updated>2026-02-23T14:00:00Z</updated><author>
				<name>Bek Fraser</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;We’ve been providing email since 1999. Over 25 years later, we’re still here, still independent, still employee-owned, and still focused on one thing: giving you the best email experience possible.&lt;/p&gt;&lt;p&gt;But we know trust isn’t built by what a company says about itself. It’s built by what customers say when they’re talking to each other. So we went looking. We searched numerous reviews, forums and tech blogs to understand what people think about Fastmail and why they choose to stay. Here’s what we found.&lt;/p&gt;&lt;h2 id=&quot;you-re-the-customer-not-the-product&quot; tabindex=&quot;-1&quot;&gt;You’re the customer, not the product&lt;/h2&gt;&lt;p&gt;This phrase came up more often than any other when people spoke about us. Across all of the forums and reviews we searched, the single biggest reason people trust Fastmail is the simplicity of our business model: you pay us for email, and we provide you with feature-rich email. It’s that simple.&lt;/p&gt;&lt;p&gt;When a business you deal with makes its money by serving you, not by selling your data to someone else, trust naturally follows.&lt;/p&gt;&lt;h2 id=&quot;real-humans-real-support&quot; tabindex=&quot;-1&quot;&gt;Real humans, real support&lt;/h2&gt;&lt;p&gt;This is the theme that surprises people most. At a time when reaching a real person at a tech company feels increasingly rare, all our customers can raise a support ticket and have that ticket directed to a real person. We saw in the forums that one of the most upvoted reasons for choosing Fastmail is that if something goes wrong, you can reach a real person without needing to write a viral blog post to get attention.&lt;/p&gt;&lt;h2 id=&quot;history-we-have-been-around-since-1999-and-we-aren-t-going-anywhere&quot; tabindex=&quot;-1&quot;&gt;History — we have been around since 1999, and we aren’t going anywhere&lt;/h2&gt;&lt;p&gt;Email is infrastructure. People always want to know that the service they have trusted for decades will still be there tomorrow. Fastmail’s history, dating back even before Gmail, is a consistent signal of trust across all reviews we found.&lt;/p&gt;&lt;h2 id=&quot;innovative-features-such-as-masked-email-and-aliases-changed-the-way-many-people-manage-their-online-identity&quot; tabindex=&quot;-1&quot;&gt;Innovative features such as Masked Email and aliases changed the way many people manage their online identity&lt;/h2&gt;&lt;p&gt;Our alias feature and the Masked Email integration (with 1Password) are among the most frequently celebrated features across the reviews. Customers can have many aliases, and create a unique email address for different services, subscriptions and vendors. This was seen as transformative for many users; they were able to identify which companies sold their data, be able to isolate spam to a single disposable address, and maintain different accounts for personal and professional use — all from one location.&lt;/p&gt;&lt;p&gt;Across the reviews we read, custom domains and aliases came up repeatedly as the tipping point — the features that turned consideration into a decision to switch.&lt;/p&gt;&lt;h2 id=&quot;speed-and-usability-it-is-fast-clean-and-just-works&quot; tabindex=&quot;-1&quot;&gt;Speed and usability — “It is fast, clean and just works”&lt;/h2&gt;&lt;p&gt;Our name isn’t a coincidence. Reviews across Trustpilot, Capterra, and Hacker News all describe the web interface as noticeably faster and lighter than Gmail’s. Add to this that there are no ads, our users have a clean, focused interface where the only thing competing for your attention is simply your actual email.&lt;/p&gt;&lt;h2 id=&quot;open-standards-mean-freedom&quot; tabindex=&quot;-1&quot;&gt;Open standards mean freedom&lt;/h2&gt;&lt;p&gt;Our tech-savvy users value our commitment to open standards — IMAP, SMTP, CalDAV, CardDAV and now our very own JMAP protocol. We are also a major contributor to the open-source Cyrus IMAP project, providing a dedicated team of developers.&lt;/p&gt;&lt;p&gt;What this means for all our customers is that you are not locked in. You can use different email clients such as Apple Mail, Thunderbird, and Outlook. If you ever decide to leave, your data and your domain go with you. In short, your email works with virtually any app, and it belongs to you — not us.&lt;/p&gt;&lt;h2 id=&quot;why-does-any-of-this-matter&quot; tabindex=&quot;-1&quot;&gt;Why does any of this matter?&lt;/h2&gt;&lt;p&gt;We didn’t write these reviews. Our customers did — on open forums that we don’t control, in conversations we weren’t part of. The themes are consistent — people trust Fastmail because we are transparent about what we do, honest about what we don’t do, and focused on keeping their trust every single day.&lt;/p&gt;&lt;p&gt;Your email is personal. Your email is important. You deserve a provider that treats it that way.&lt;/p&gt;</content>
        </entry><entry>
            <title>Safer Internet Day: Why your search engine matters more than you think</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/safer-internet-day-why-your-search-engine-matters-more-than-you-think/' />
			<id>https://www.fastmail.com/blog/safer-internet-day-why-your-search-engine-matters-more-than-you-think/</id>
			<updated>2026-02-09T05:00:00Z</updated><author>
				<name>Bek Fraser</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;You chose Fastmail because you care about your privacy. You understand why independent providers matter. But there’s a crucial piece of the privacy puzzle that often gets overlooked: your search engine.&lt;/p&gt;&lt;p&gt;Every time you search the web, you share what you’re curious about, what problems you’re solving, what health symptoms you want to understand, what you’re shopping for, and what matters to you. For many of us, that search bar is the gateway to everything we do online. So why hand that data over to companies whose business model depends on tracking you?&lt;/p&gt;&lt;h2 id=&quot;the-search-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;The ‘search’ problem&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Even with the most private browser, if you’re using a search engine that tracks you, you’re still handing over valuable data about your interests, location, and browsing patterns.&lt;/p&gt;&lt;p&gt;Traditional search engines make money by:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Tracking your search history to build detailed profiles&lt;/li&gt; &lt;li&gt;Following you across the web to see which results you click&lt;/li&gt; &lt;li&gt;Selling your data to advertisers who use it for targeted campaigns&lt;/li&gt; &lt;li&gt;Combining your search behavior with other services to create comprehensive profiles about you&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;It’s the same surveillance model we’ve been helping you escape by moving away from other commercial service providers.&lt;/p&gt;&lt;h2 id=&quot;then-there-s-kagi-search-that-works-for-you&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Then there’s Kagi: Search that works for you&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;We’re excited to share that Fastmail has joined Kagi’s “Friends of Kagi” program — a collection of privacy-first companies that share our values and commitment to putting customers first.&lt;/p&gt;&lt;p&gt;Kagi (pronounced KAH-gee) is a paid search engine that delivers high-quality results without tracking, profiling, or advertising. Like Fastmail, they’re user-funded with no external shareholders pushing for data monetization.&lt;/p&gt;&lt;h3 id=&quot;what-makes-kagi-different&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;What makes Kagi different?&lt;/strong&gt;&lt;/h3&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;No tracking&lt;/strong&gt;: Kagi doesn’t track your searches, IP address, or browsing behavior. They don’t build profiles or share data with third parties.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Better results&lt;/strong&gt;: Without the need to maximize ad clicks, Kagi focuses on giving you the best answer quickly. You can customize which sites you see more or less of.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Ad-free&lt;/strong&gt;: No sponsored results competing with actual answers. Just the information you’re looking for.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Privacy by business model&lt;/strong&gt;: Just like Fastmail, you pay for the service, and they work for you. No split loyalties.&lt;/li&gt; &lt;/ul&gt;&lt;h3 id=&quot;completing-your-privacy-stack&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Completing your privacy stack&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;If you’ve already switched to Fastmail and a private browser, adding Kagi creates a powerful privacy stack:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;&lt;strong&gt;Private email&lt;/strong&gt; (Fastmail): Protects your communications and blocks tracking pixels&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Private browser&lt;/strong&gt; (Vivaldi, Firefox, Brave, or Safari): Limits cookies, fingerprinting, and tracking&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Private search&lt;/strong&gt; (Kagi): Keeps your queries and interests confidential&lt;/li&gt; &lt;/ol&gt;&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Kagi is a search engine, not a browser. You can use Kagi with any browser you prefer. Simply set Kagi as your default search engine, and when you type a search into your browser’s address bar, it sends that search to Kagi — all without tracking you.&lt;/p&gt;&lt;h2 id=&quot;ready-to-build-your-privacy-stack&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Ready to build your privacy stack?&lt;/strong&gt;&lt;/h2&gt;&lt;ol&gt; &lt;li&gt;Switch to Fastmail for private, ad-free email&lt;/li&gt; &lt;li&gt;Choose a privacy-focused browser&lt;/li&gt; &lt;li&gt;Change your default search to Kagi&lt;/li&gt; &lt;li&gt;Use browser extensions like Privacy Badger for additional protection&lt;/li&gt; &lt;/ol&gt;&lt;h2 id=&quot;final-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Final thoughts&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Privacy isn’t about having something to hide — it’s about having the right to control your own information. By choosing Fastmail, a private browser, and now Kagi, you’re building a digital environment where you’re in control. Where companies serve you rather than the other way around. Where your data stays yours.&lt;/p&gt;&lt;p&gt;That’s the internet we believe in, and we’re proud to partner with companies like Kagi who share that vision.&lt;/p&gt;&lt;h2 id=&quot;special-offer-for-fastmail-customers&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Special offer for Fastmail customers&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;As part of our “Friends of Kagi” partnership, Fastmail customers will get the first 3 months free on the Kagi Professional plan using this &lt;a href=&quot;https://kagi.com/p/FASTMAIL8057F5C568&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;</content>
        </entry><entry>
            <title>Being better with passwords</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/change-your-password/' />
			<id>https://www.fastmail.com/blog/change-your-password/</id>
			<updated>2026-02-01T19:00:00Z</updated><author>
				<name>Bek Fraser</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;February 1 is global &lt;em&gt;Change Your Password Day&lt;/em&gt;. In the past, many companies would force users to change their password every month or so, even if there was no evidence of compromise. This was annoying, and did not generally make you more secure — mostly people would just reuse the same password they used everywhere with a different number on the end. Thankfully, this is no longer considered best practice. However, we thought it would still be a good day to reflect instead on what we &lt;em&gt;should&lt;/em&gt; do.&lt;/p&gt;&lt;h2 id=&quot;how-to-be-better-with-passwords&quot; tabindex=&quot;-1&quot;&gt;How to be better with passwords&lt;/h2&gt;&lt;p&gt;The number one thing you can do to improve your security online is to always use a different, random password each time you create a new account on the web. Not everyone has the same tight security as Fastmail, and you don’t want a hacker that gains access to your local library to now also have the password to your email and your bank!&lt;/p&gt;&lt;p&gt;But coming up with random passwords is hard, and memorising them even harder, which is why these days we strongly recommend you use a password manager to create and remember your passwords for you.&lt;/p&gt;&lt;h2 id=&quot;what-is-a-password-manager&quot; tabindex=&quot;-1&quot;&gt;What is a password manager?&lt;/h2&gt;&lt;p&gt;A password manager is a digital vault, similar to how the banks of yesteryear stored jewels and high-value items for their customers. The vault securely stores your passwords in an encrypted format that cannot be accessed by anyone but you. When required, it will create unique, complex passwords and auto-fill them across your various applications.&lt;/p&gt;&lt;p&gt;We like and use &lt;a href=&quot;https://1password.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1Password&lt;/a&gt;, which keeps your passwords securely in sync across all your devices and integrates with Fastmail’s &lt;a href=&quot;/features/masked-email/&quot;&gt;Masked Email&lt;/a&gt; feature. However, there are other good alternatives too, and a built-in one in every browser these days — find one that works for you and use it!&lt;/p&gt;&lt;p&gt;As well as remembering your passwords for you, password managers help protect you against phishing by only auto-filling your password when you’re at the genuine website for the account.&lt;/p&gt;&lt;h2 id=&quot;the-zero-password-future&quot; tabindex=&quot;-1&quot;&gt;The zero password future&lt;/h2&gt;&lt;p&gt;Today’s password managers don’t just store passwords, they can also store something better — a passkey.&lt;/p&gt;&lt;p&gt;In 2024, Fastmail introduced passkey support to provide you with even greater online security. A passkey is a highly secure cryptographic key that works like a digital handshake between your password manager and the website you are logging in to. This ensures it’s definitely you logging in, and — just as importantly — definitely the real site you are logging in to! We wrote a great blog post if you want to &lt;a href=&quot;/blog/introducing-passkeys/&quot;&gt;learn why passkeys are better than passwords&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Online scams like phishing emails are increasingly hard to distinguish from legitimate messages. Scammers, or even AI agents, can now convincingly impersonate your trusted contacts and companies. As a result, even technically savvy individuals can unknowingly give away passwords. Passkeys provide the strongest protection against these threats.&lt;/p&gt;&lt;h2 id=&quot;how-does-fastmail-work-with-1-password&quot; tabindex=&quot;-1&quot;&gt;How does Fastmail work with 1Password?&lt;/h2&gt;&lt;p&gt;In 2021, Fastmail partnered with 1Password to bring Masked Emails to our customers. This lets you create a unique email address for each account you have online, keeping your real email address private. Working together, you can use Fastmail with 1Password to achieve the greatest protection online through private and secure email addresses and passwords every time you sign up to a new site.&lt;/p&gt;&lt;p&gt;For more information on how to use 1Password with Fastmail, you can read our help article &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060590513-Using-1Password-on-Fastmail-web-interface&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content>
        </entry><entry>
            <title>When many isn’t better than one: Managing your life with Fastmail and Morgen</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/when-many-isn-t-better-than-one-managing-your-life-with-fastmail-and-morgen/' />
			<id>https://www.fastmail.com/blog/when-many-isn-t-better-than-one-managing-your-life-with-fastmail-and-morgen/</id>
			<updated>2026-01-11T08:00:00Z</updated><author>
				<name>Bek Fraser</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;We all know that checking three different calendar apps before agreeing to coffee with a friend is simply not fun. Or realizing mid-dentist appointment that you’re also supposed to be on a work call. Or playing calendar Tetris across multiple screens just to figure out when you can squeeze in that thing you actually &lt;em&gt;want&lt;/em&gt; to do.&lt;/p&gt;&lt;p&gt;We’ve all been sold the idea that more is better — more apps, more tools, more ways to “organise our lives.” But when it comes to calendars, more usually means chaos.&lt;/p&gt;&lt;p&gt;Here’s the truth: you don’t need multiple calendars. You only need one place to see them all.&lt;/p&gt;&lt;h2 id=&quot;the-calendar-multiplication-problem&quot; tabindex=&quot;-1&quot;&gt;The calendar multiplication problem&lt;/h2&gt;&lt;p&gt;Calendars are a reality. There’s your work calendar (probably Outlook or Google, because that’s what IT chose). Your personal Fastmail calendar. The shared family calendar where someone keeps scheduling things without asking. Maybe a volunteer committee calendar. Oh, and that sports league schedule that lives in yet another app.&lt;/p&gt;&lt;p&gt;Your reality — managing five separate windows into your life, and none of them talk to each other.&lt;/p&gt;&lt;p&gt;The mental math alone is exhausting. “Okay, I’m free at 2 pm on Tuesday… wait, no, that’s just in my work calendar. Let me check my personal… and the family one… actually, can I get back to you?”&lt;/p&gt;&lt;h2 id=&quot;one-view-to-rule-them-all&quot; tabindex=&quot;-1&quot;&gt;One view to rule them all&lt;/h2&gt;&lt;p&gt;This is exactly why we partnered with Morgen. Because they get it: the problem isn’t that you have multiple calendars. The problem is looking at them separately.&lt;/p&gt;&lt;p&gt;Morgen lets you bring all your calendars together — your Fastmail calendar, your work calendar, even that random shared calendar from your book club — into one unified view. Suddenly, you’re not playing calendar detective anymore. You can actually &lt;em&gt;see&lt;/em&gt; your life.&lt;/p&gt;&lt;p&gt;And here’s the best part: Morgen has the same values as Fastmail, and for this reason, treats your Fastmail calendar as a first-class citizen. No hacks, no workarounds, no “well, technically you could…”— just straightforward integration that actually works.&lt;/p&gt;&lt;h2 id=&quot;time-blocking-the-adult-version-of-planning-your-day&quot; tabindex=&quot;-1&quot;&gt;‘Time Blocking’: The adult version of planning your day&lt;/h2&gt;&lt;p&gt;Remember when you used to plan out your day with a paper planner? Color-coding your classes, blocking out study time, actually having a sense of what you were doing when?&lt;/p&gt;&lt;p&gt;Time blocking is basically that, but for grown-ups with too many responsibilities.&lt;/p&gt;&lt;p&gt;The concept is simple: instead of letting your calendar fill up with whatever comes at you, you proactively block time for what matters. Focus work. Exercise. That project you keep saying you’ll get to. Time with actual humans you care about.&lt;/p&gt;&lt;p&gt;The problem? Time blocking only works if you can see your &lt;em&gt;entire&lt;/em&gt; schedule. And that’s where most people give up — it’s too hard to coordinate across multiple calendars.&lt;/p&gt;&lt;p&gt;With Morgen and Fastmail, time blocking becomes ridiculously easy:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Drag tasks straight onto your calendar&lt;/strong&gt; instead of maintaining a separate to-do list, you’ll never look at&lt;/li&gt; &lt;li&gt;&lt;strong&gt;See all your commitments at once&lt;/strong&gt; so you know what time you actually have available&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Block out focus time&lt;/strong&gt; that appears across all your calendars, so people stop booking meetings during your deep work hours&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Balance the work-life thing&lt;/strong&gt; by actually seeing both work and life in the same place&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;It’s not revolutionary. It’s just finally… possible.&lt;/p&gt;&lt;h2 id=&quot;your-data-your-rules&quot; tabindex=&quot;-1&quot;&gt;Your data, your rules&lt;/h2&gt;&lt;p&gt;Here’s where it gets important: both Fastmail and Morgen are built on the simple idea that your data belongs to you. Not to advertisers. Not to data brokers. You.&lt;/p&gt;&lt;p&gt;When you connect your Fastmail account to Morgen, you’re using OAuth — which is tech-speak for “secure connection that respects your privacy.” You control what gets shared. Neither company will sell or distribute your data. Your calendar stays yours.&lt;/p&gt;&lt;p&gt;In a world where most “free” productivity apps are monetizing your every click, this actually matters.&lt;/p&gt;&lt;h2 id=&quot;making-it-ridiculously-simple&quot; tabindex=&quot;-1&quot;&gt;Making it ridiculously simple&lt;/h2&gt;&lt;p&gt;Here’s how the Fastmail-Morgen integration makes your life easier:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;All your calendars in one place&lt;/strong&gt;: Work, personal, shared — they all show up together so you can finally see what you’re working with&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Smart scheduling&lt;/strong&gt;: Morgen’s booking pages check your availability across &lt;em&gt;all&lt;/em&gt; your calendars, eliminating the “wait, let me check my other calendar” dance&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Tasks that become time&lt;/strong&gt;: Stop maintaining a to-do list separately from your calendar - drag tasks onto your schedule and actually make them happen&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Works everywhere&lt;/strong&gt;: Mac, Windows, Linux, iOS, Android - your unified calendar view goes wherever you do&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;getting-started-takes-only-minutes&quot; tabindex=&quot;-1&quot;&gt;Getting started takes only minutes&lt;/h2&gt;&lt;ol&gt; &lt;li&gt;Sign up and download &lt;a href=&quot;https://www.morgen.so/fastmail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Morgen&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Connect your Fastmail account&lt;/li&gt; &lt;li&gt;Add any other calendars you’re juggling&lt;/li&gt; &lt;li&gt;Start actually seeing your whole life in one place&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;That’s it. No complex setup, no IT degree required — just a few clicks to get started.&lt;/p&gt;&lt;h2 id=&quot;the-real-bottom-line&quot; tabindex=&quot;-1&quot;&gt;The Real Bottom Line&lt;/h2&gt;&lt;p&gt;More calendars don’t make you more organized. They make you more stressed.&lt;/p&gt;&lt;p&gt;What you need isn’t another app or another system, or another calendar. You need to see everything in one place so you can make actual decisions about your time.&lt;/p&gt;&lt;p&gt;That’s what Morgen and Fastmail deliver: one view of your entire life, tools that actually help you manage it, and zero compromise on privacy.&lt;/p&gt;&lt;p&gt;Because at the end of the day, managing your life better isn’t about having more tools. It’s about having one clear view of what matters.&lt;/p&gt;</content>
        </entry><entry>
            <title>Understanding email encryption</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/email-encryption/' />
			<id>https://www.fastmail.com/blog/email-encryption/</id>
			<updated>2025-12-17T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;Encrypted email sounds good, but what does it really mean? Email can be encrypted in many different ways, at different times, for different purposes. Each protects against different threats, and may have downsides to be weighed up. Understanding your options helps you make informed choices about your privacy and security.&lt;/p&gt;&lt;h2 id=&quot;what-are-you-protecting-against&quot; tabindex=&quot;-1&quot;&gt;What are you protecting against?&lt;/h2&gt;&lt;p&gt;Before diving into encryption methods, it’s worth asking: what threats actually matter for your email? For most people, the realistic concerns are:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Account compromise&lt;/strong&gt;: phishing or password reuse giving attackers access to your account&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Mistaken spam detection&lt;/strong&gt;: causing important mail to go missing&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Malware&lt;/strong&gt;: getting a virus via email, either linked or as an attachment&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Accidental deletion&lt;/strong&gt;: losing vital data due to human error&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Far less common, although also important, are:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Network eavesdropping&lt;/strong&gt;: someone on your WiFi or ISP intercepting your traffic&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Data breaches&lt;/strong&gt;: attackers gaining access to stored email through a security vulnerability&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Physical theft&lt;/strong&gt;: stolen devices or hard drives exposing your data&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;For a smaller number of people — journalists protecting sources, activists in hostile countries, or those facing sophisticated adversaries — the threat model expands to include insider threats at providers and legal compulsion by governments. Different encryption approaches help protect (or may even hinder protection) against subsets of these threats, and understanding this helps you choose the right balance.&lt;/p&gt;&lt;h2 id=&quot;encrypting-the-connection-to-your-email-provider&quot; tabindex=&quot;-1&quot;&gt;Encrypting the connection to your email provider&lt;/h2&gt;&lt;p&gt;Whenever you connect to your email provider, &lt;a href=&quot;https://en.wikipedia.org/wiki/Transport_Layer_Security&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TLS (Transport Layer Security)&lt;/a&gt; encrypts everything between your device and the server. This protects you from eavesdroppers on your local WiFi network, your ISP, and anyone monitoring network traffic between you and your email service. This standard has been widely adopted and modernised, the latest version being &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8446&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TLS 1.3&lt;/a&gt;, which mandates the strongest ciphers and is the foundation of today’s secure internet.&lt;/p&gt;&lt;p&gt;But encryption alone isn’t enough. You also need assurance you’re actually talking to your email provider and not an imposter. This is where Public Key Infrastructure (PKI) comes in. When you connect to Fastmail or Gmail, the server presents a certificate that’s been digitally signed by a trusted Certificate Authority (CA) like &lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Let’s Encrypt&lt;/a&gt;. Your browser or email client checks this signature against its built-in list of trusted CAs, verifying that the certificate was legitimately issued to that domain. This prevents attackers from intercepting your connection by pretending to be your email provider — even if they can redirect your traffic, they can’t obtain a valid certificate for a domain they don’t control.&lt;/p&gt;&lt;p&gt;The system was strengthened significantly with the introduction of &lt;a href=&quot;https://en.wikipedia.org/wiki/Certificate_Transparency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Certificate Transparency (CT)&lt;/a&gt; logs: public, append-only records of every certificate issued by participating CAs. Domain owners can monitor these logs to detect if someone fraudulently obtains a certificate for their domain, and browsers now require certificates to be logged before trusting them. While no system is perfect — CAs have occasionally been compromised or tricked into issuing bad certificates — PKI combined with Certificate Transparency provides strong, practical assurance that your encrypted connection terminates at the server you intended to reach.&lt;/p&gt;&lt;p&gt;Fastmail fully supports the latest TLS 1.3 standard. We also use &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HTTP Strict Transport Security (HSTS)&lt;/a&gt; to ensure browsers never try connecting to us without TLS. Similarly, we ensure the ports for unencrypted IMAP/POP/SMTP are closed on our servers, so email apps do not try to connect without encryption (this is why &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360058753834-SSL-TLS-and-STARTTLS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;we have always preferred “implicit” TLS over STARTTLS&lt;/a&gt;, which is also now &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8314&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;the recommendation of the IETF&lt;/a&gt;).&lt;/p&gt;&lt;h2 id=&quot;encrypting-email-between-servers&quot; tabindex=&quot;-1&quot;&gt;Encrypting email between servers&lt;/h2&gt;&lt;p&gt;When you send an email, it first goes to your email provider’s server, and then they send it on to the recipient’s email server. The more interesting challenge lies in that transit between different servers.&lt;/p&gt;&lt;p&gt;Server-to-server encryption relies on “opportunistic TLS”, meaning servers attempt encryption but fall back to unencrypted delivery if the receiving server doesn’t support it. Fastmail has supported this for inbound mail &lt;a href=&quot;/blog/opportunistic-ssltls-encryption-on-incoming-emails/&quot;&gt;since April 2009&lt;/a&gt; and for outbound mail since &lt;a href=&quot;/blog/opportunistic-ssltls-encryption-on-outgoing-emails/&quot;&gt;January 2010&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The fundamental limitation is that opportunistic TLS can be defeated by active attackers, commonly referred to as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Man-in-the-middle_attack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;man-in-the-middle (MITM) attack&lt;/a&gt;. A sophisticated adversary positioned between mail servers can strip the STARTTLS command from the initial handshake, forcing an unencrypted connection. There is still enough legitimate mail sent to, or received from, servers that don’t support encryption that mandating it for everyone would lose an unacceptable amount of wanted email.&lt;/p&gt;&lt;p&gt;Additionally, servers don’t verify certificates by default. They accept any valid-looking certificate without confirming it actually belongs to the destination server. This is because verifying the certificate adds no security when an active attacker could just fall back to an unencrypted connection.&lt;/p&gt;&lt;h3 id=&quot;mta-sts-and-dane-attempt-to-close-the-gaps&quot; tabindex=&quot;-1&quot;&gt;MTA-STS and DANE attempt to close the gaps&lt;/h3&gt;&lt;p&gt;Two protocols address these vulnerabilities, though adoption remains limited.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8461&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MTA-STS (Mail Transfer Agent Strict Transport Security)&lt;/a&gt; lets domains declare they support and require STARTTLS encrypted connections, including a valid certificate, when receiving email for addresses at that domain. Senders can check this policy and must refuse to send mail if they are unable to establish an encrypted connection with a valid certificate for the receiving domain.&lt;/p&gt;&lt;p&gt;MTA-STS is reasonably straightforward to deploy: it requires only a DNS record and a policy file hosted over HTTPS. If something goes wrong, the worst case is that some incoming mail gets delayed or bounced — it cannot affect websites or other uses of the domain.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DANE (DNS-Based Authentication of Named Entities)&lt;/a&gt; takes a different approach, publishing TLS certificate information directly in cryptographically-signed DNS records via &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DNSSEC&lt;/a&gt;. This eliminates dependence on certificate authorities and the “trust on first use” weakness of MTA-STS — but at significant operational cost.&lt;/p&gt;&lt;p&gt;DNSSEC is not supported by all domains (it depends on the registry for the top-level domain). More importantly, DNSSEC’s deployment complexity significantly increases the likelihood of a serious outage. Unlike most system failures, DNSSEC errors are uniquely unforgiving: when there’s a routing problem, you fix it and service is restored immediately, but when there’s a DNSSEC problem, broken records can persist in caches across the internet for hours or days.&lt;/p&gt;&lt;p&gt;Even sophisticated operators get this wrong. In October 2023, Cloudflare’s 1.1.1.1 resolver — one of the world’s most popular DNS services — &lt;a href=&quot;https://blog.cloudflare.com/1-1-1-1-lookup-failures-on-october-4th-2023/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;experienced hours of failures&lt;/a&gt; when DNSSEC signatures in their cached root zone expired. In May 2023, New Zealand’s .nz country-code TLD triggered a &lt;a href=&quot;https://internetnz.nz/news-and-articles/dnssec-chain-validation-issue-technical-incident-report/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;multi-day availability incident&lt;/a&gt; during a routine DNSSEC key rollover. There is a &lt;a href=&quot;https://ianix.com/pub/dnssec-outages.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;long history of similar outages&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;At Fastmail we know security is &lt;a href=&quot;/blog/security-confidentiality-integrity-and-availability/&quot;&gt;not just about confidentiality&lt;/a&gt; (no one else can read your email). &lt;a href=&quot;/blog/security-availability/&quot;&gt;Availability&lt;/a&gt; (&lt;em&gt;you&lt;/em&gt; can read your email) and &lt;a href=&quot;/blog/security-integrity/&quot;&gt;integrity&lt;/a&gt; (your email cannot be corrupted or modified by others) are just as important. For this reason, we don’t currently consider the confidentiality benefits of DANE over MTA-STS to be worth the trade-off against increased availability risk. Other major providers such as Google and Yahoo have made similar choices, favouring MTA-STS over DANE. However, we regularly re-evaluate our position on this as the global ecosystem evolves.&lt;/p&gt;&lt;p&gt;For most users, the practical upshot is that email traveling between major providers is almost certainly encrypted in transit. Sophisticated nation-state attackers who can manipulate network traffic may be able to downgrade connections to intercept mail in the absence of DANE or MTA-STS. However, doing so at scale would almost certainly be noticed, and targeting specific individuals is very difficult in high-volume mail flows between major providers.&lt;/p&gt;&lt;h2 id=&quot;encrypting-email-at-rest&quot; tabindex=&quot;-1&quot;&gt;Encrypting email at rest&lt;/h2&gt;&lt;p&gt;Once email reaches a server, encryption at rest protects it while stored on disk. This typically means full-disk encryption using technologies like &lt;a href=&quot;https://en.wikipedia.org/wiki/BitLocker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BitLocker&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LUKS&lt;/a&gt;, or &lt;a href=&quot;https://en.wikipedia.org/wiki/ZFS#Encryption&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ZFS dataset encryption&lt;/a&gt;, using keys held by the email provider.&lt;/p&gt;&lt;p&gt;This architecture protects against specific threats. If someone physically steals a hard drive from a data center, or if decommissioned drives aren’t properly wiped, the data remains unreadable. Encryption at rest defends against physical data center breaches and helps meet compliance requirements for regulations like GDPR and HIPAA.&lt;/p&gt;&lt;p&gt;As the email service holds the keys, legal requests backed by valid court orders can compel providers to decrypt and hand over data. And if attackers gain full control of a server, encryption at rest provides no protection because the keys are already loaded.&lt;/p&gt;&lt;p&gt;Fastmail stores all user data on encrypted disk volumes, including backups, with encryption keys retained solely under our control. We maintain strict access controls with comprehensive logging and auditing. Our &lt;a href=&quot;/policies/transparency-report/&quot;&gt;transparency report&lt;/a&gt; details the number of legal requests we receive and respond to each year.&lt;/p&gt;&lt;h2 id=&quot;the-trade-offs-of-zero-access-encryption&quot; tabindex=&quot;-1&quot;&gt;The trade-offs of zero-access encryption&lt;/h2&gt;&lt;p&gt;Services like Proton Mail and Tuta offer zero-access encryption, where encryption keys are derived from your password on your device, meaning even the provider cannot decrypt your stored email. When you log in, your password decrypts the key locally; the server never sees it.&lt;/p&gt;&lt;p&gt;For users whose threat model prioritises protection against insider threats or government compulsion — and who are willing to accept significant trade-offs in functionality — zero-access encryption is a legitimate choice. It can provide real protection against insider attacks, data breaches, and limit the data handed over in response to court orders. However, it’s important to understand both what it protects against and what it doesn’t, as the additional protections of zero-access encryption are limited in ways that are sometimes understated:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;New mail can still be intercepted&lt;/strong&gt;. Unless the email is end-to-end encrypted (see the next section), the provider will have access to the unencrypted version before encrypting it with the user’s public key. An interception court order or rogue employee could still take a copy at this point.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Not all data is encrypted&lt;/strong&gt;. Email addresses, timestamps, and in Proton Mail’s case, subject lines, stay unencrypted. Who you’re talking to, when, how often, and the summary of what it’s about is often more important than the contents of the email itself.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Only protects against read-only server compromise&lt;/strong&gt;. Most people access their email through the browser. The code that decrypts the email is therefore loaded on demand from the provider’s server — if the server is compromised, the code can be modified to capture your password. Law enforcement may also be able to compel this change for specific users subject to a court order.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;An unencrypted copy exists elsewhere&lt;/strong&gt;. Even if your copy is encrypted, the senders likely retain a fully readable copy of every message you receive.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Zero-access encryption also has significant trade-offs in functionality:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Password loss means data loss&lt;/strong&gt;. Forget your password without a recovery phrase, and your email is gone forever. There’s no “reset password” in the traditional sense. Many years of experience have taught us that people forget or lose their password all the time. Even sophisticated users who swear it would never happen to them have found themselves locked out. Password reset flows can be a weak point in securing an account, but at Fastmail we have a &lt;a href=&quot;/blog/multiple-ways-in-keeping-password-reset-secure/&quot;&gt;very carefully considered&lt;/a&gt; automated system for secure account recovery.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Search capabilities are constrained&lt;/strong&gt;. Encrypted content cannot be seen or indexed on servers. Client-side search indexes are possible, but require downloading all mail to every device — often impractical for large accounts with tens of gigabytes of mail.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Standard email clients don’t work&lt;/strong&gt;. Zero-access encryption prevents the provider directly offering standard IMAP or JMAP protocols. This also makes &lt;strong&gt;migrating to another provider difficult&lt;/strong&gt; — it’s the ultimate vendor lock-in.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Customer support is limited&lt;/strong&gt;. Because the provider cannot access your mail even with your permission, many issues become impossible to help with. Business features like shared mailboxes and compliance tools are often unavailable.&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;end-to-end-encrypted-email&quot; tabindex=&quot;-1&quot;&gt;End-to-end encrypted email&lt;/h2&gt;&lt;p&gt;True end-to-end encryption, where only the sender and recipient can read the message, has existed for email since the 1990s through S/MIME and OpenPGP. Yet neither has achieved meaningful consumer adoption despite decades of availability.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8551&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;S/MIME&lt;/a&gt; uses certificates from Certificate Authorities and is supported by some enterprise email clients. It has some traction in organisations with existing PKI infrastructure where you can find a recipient’s public key in an internal corporate directory, but is rarely used outside of this.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9580.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OpenPGP&lt;/a&gt; uses a decentralized “Web of Trust” where users vouch for each other’s keys. The Web of Trust is practically dead according to security researchers, and PGP suffers from &lt;a href=&quot;https://www.latacora.com/blog/2019/07/16/the-pgp-problem/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;a myriad of design problems&lt;/a&gt;: it leaks metadata, is not forward secure, and lacks practical key rotation. The seminal 1999 study “&lt;a href=&quot;https://people.eecs.berkeley.edu/~tygar/papers/Why_Johnny_Cant_Encrypt/OReilly.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Why Johnny Can’t Encrypt&lt;/a&gt;” found that only 4 of 12 participants could properly encrypt email with PGP within 90 minutes. Follow-up studies in 2006 and 2019 found users still struggling with “finding and verifying other people’s public encryption keys.” Even &lt;a href=&quot;https://www.vice.com/en/article/even-the-inventor-of-pgp-doesnt-use-pgp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PGP inventor Phil Zimmermann had difficulty decrypting an email in 2015 due to version incompatibility&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A fundamental challenge for any end-to-end system is key distribution and rotation. To send an end-to-end encrypted message to someone, you first need to obtain their public key (and be absolutely sure it’s theirs!). There is no infrastructure for doing this at the moment, let alone for rotating keys regularly in accordance with cryptographic best practice. Without key rotation, a compromised key or password means all previously intercepted email can be decrypted.&lt;/p&gt;&lt;p&gt;Contrast all this with &lt;a href=&quot;https://signal.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Signal&lt;/a&gt; or &lt;a href=&quot;https://www.whatsapp.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WhatsApp&lt;/a&gt;, where encryption works automatically because a single organisation controls the full software stack rather than using open protocols. They generate keys automatically when you install the app, discover contacts through phone numbers, and update security protocols universally. Email’s federated architecture — where any client can talk to any server — makes this unified approach impossible.&lt;/p&gt;&lt;p&gt;End-to-end encryption also fundamentally conflicts with features email users depend on. Server-side search becomes impossible when the server can’t read content — even just keeping a searchable archive of all your messages is antithetical to security best practice. Phishing detection and malware prevention (two of the biggest security risks!) require inspecting messages. Multi-device access requires synchronising private keys across devices — a security risk. And crucially, email headers including To, From, and Date cannot be encrypted without breaking email’s basic functionality.&lt;/p&gt;&lt;p&gt;As cryptographer &lt;a href=&quot;https://www.schneier.com/blog/archives/2015/11/testing_the_usa.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bruce Schneier concluded&lt;/a&gt;: “The things we want out of e-mail, and an e-mail system, are not readily compatible with encryption.”&lt;/p&gt;&lt;h2 id=&quot;the-necessary-trade-off-between-privacy-and-security&quot; tabindex=&quot;-1&quot;&gt;The necessary trade-off between privacy and security&lt;/h2&gt;&lt;p&gt;There’s an inherent tension between strong privacy protections and the ability to combat abuse. Spam filters, phishing detection, and malware scanning all require reading email content, or at least work more effectively when they can. A provider that truly cannot see your messages cannot protect you as effectively from threats within them.&lt;/p&gt;&lt;p&gt;This creates real consequences. With server-side content inspection, providers can identify phishing attempts by analysing links and sender patterns. They can quarantine malware before it reaches your inbox. They can detect account compromise by recognising unusual behaviour patterns. These protective capabilities are reduced or eliminated with end-to-end encryption.&lt;/p&gt;&lt;h2 id=&quot;choosing-your-own-balance&quot; tabindex=&quot;-1&quot;&gt;Choosing your own balance&lt;/h2&gt;&lt;p&gt;Email security isn’t binary — it’s a spectrum of trade-offs. Transport encryption (TLS) is now universal and protects against passive surveillance. Encryption at rest guards against physical theft. Zero-access encryption limits insider access but sacrifices functionality. True end-to-end encryption provides the strongest content protection but remains impractical for most users and has significant usability and security issues of its own.&lt;/p&gt;&lt;p&gt;For highly sensitive communications, dedicated messaging apps like &lt;a href=&quot;https://signal.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Signal&lt;/a&gt; offer much stronger guarantees than any email solution can provide. For everyday email, transport encryption combined with encryption at rest provides meaningful protection against the most likely threats while preserving the searchability, spam filtering, and convenience that make email useful.&lt;/p&gt;&lt;p&gt;Here’s the honest truth: if you are a journalist trying to protect a source, or face nation-state adversaries, you probably shouldn’t use email — use an encrypted messaging app instead. But for the vast majority of communications, modern email security offers substantial protection while maintaining the features that make email indispensable.&lt;/p&gt;&lt;h2 id=&quot;fastmail-s-ongoing-approach-to-email-and-encryption&quot; tabindex=&quot;-1&quot;&gt;Fastmail’s ongoing approach to email and encryption&lt;/h2&gt;&lt;p&gt;Fastmail has been doing email for over 25 years. We know that email is your &lt;a href=&quot;/blog/email-is-your-electronic-memory/&quot;&gt;electronic memory&lt;/a&gt; and keeping your messages accessible, private and secure are our top priorities. We keep your emails &lt;a href=&quot;/features/privacy/&quot;&gt;private&lt;/a&gt; and &lt;a href=&quot;/features/security/&quot;&gt;secure&lt;/a&gt; with encryption in transit and on our servers, strong authentication options, and innnovative features like &lt;a href=&quot;/features/masked-email/&quot;&gt;Masked Email&lt;/a&gt;. We keep your email accessible by concentrating on &lt;a href=&quot;/features/reliability/&quot;&gt;rock-solid reliability&lt;/a&gt;, with full multi-data center redundancy, and a &lt;a href=&quot;/features/lightning-quick/&quot;&gt;lightning-fast interface&lt;/a&gt; with &lt;a href=&quot;/features/search/&quot;&gt;powerful search&lt;/a&gt;, capable of handling decades of mail with ease. We regularly evaluate the tradeoffs required to provide the best overall user experience and privacy possible taking into account all the technical and practical options available.&lt;/p&gt;</content>
        </entry><entry>
            <title>Introducing the Fastmail desktop app</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/desktop-app/' />
			<id>https://www.fastmail.com/blog/desktop-app/</id>
			<updated>2025-10-13T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;Fastmail is now available as &lt;a href=&quot;/download/&quot;&gt;a dedicated desktop app for Mac, Windows, and Linux&lt;/a&gt;. It’s the same Fastmail you know and love, now with the focus and convenience of a standalone app.&lt;/p&gt;&lt;p&gt;With our desktop app you can:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Launch Fastmail from your dock or taskbar and find it in your platform’s app switcher.&lt;/li&gt; &lt;li&gt;Make Fastmail your default email client, so email links create a new message directly in Fastmail.&lt;/li&gt; &lt;li&gt;Work whenever, wherever, with full offline support, just like our mobile apps. You can always read your mail, manage your calendar, and write replies — your changes sync back seamlessly when you reconnect.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/desktop-mac-screenshot-TFHw8lzi3I-375.webp 375w, /assets/images/desktop-mac-screenshot-TFHw8lzi3I-750.webp 750w, /assets/images/desktop-mac-screenshot-TFHw8lzi3I-1500.webp 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;Desktop app screenshot on a Mac&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/desktop-mac-screenshot-TFHw8lzi3I-375.png&quot; width=&quot;1500&quot; height=&quot;989&quot; srcset=&quot;/assets/images/desktop-mac-screenshot-TFHw8lzi3I-375.png 375w, /assets/images/desktop-mac-screenshot-TFHw8lzi3I-750.png 750w, /assets/images/desktop-mac-screenshot-TFHw8lzi3I-1500.png 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/p&gt;&lt;p&gt;Whether you’re on macOS, Windows, or your favourite Linux distribution, you’ll find the app feels right at home on your platform, with native notifications, menus, and system integrations.&lt;/p&gt;&lt;p&gt;Getting started is simple. &lt;a href=&quot;/download/&quot;&gt;Download the app&lt;/a&gt; for your platform, sign in with your Fastmail credentials, and you’re ready to go.&lt;/p&gt;</content>
        </entry><entry>
            <title>This blog post was not written with AI</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/not-written-with-ai/' />
			<id>https://www.fastmail.com/blog/not-written-with-ai/</id>
			<updated>2025-10-02T00:00:01Z</updated><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;It’s all the rage right now. Everyone is scrambling to put AI into their products. The uncanny valley is shrinking enough that it’s hard to see how much AI was used to write something.&lt;/p&gt;&lt;p&gt;This isn’t entirely new, auto-complete on my phone already suggests the most likely word when I’m typing. AI writing tools are an extension of this, but they’re also much more capable.&lt;/p&gt;&lt;h2 id=&quot;your-electronic-memory&quot; tabindex=&quot;-1&quot;&gt;Your electronic memory&lt;/h2&gt;&lt;p&gt;I stand by one of the most important truths about email. It’s not only the largest and most diverse social network, &lt;a href=&quot;https://www.fastmail.com/blog/email-is-your-electronic-memory/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;email is your electronic memory&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In the novel 1984, the “Ministry of Truth” has a whole massive department which rewrites history. In a world where there’s enough AI capability to process the entire web and rewrite every page to remove something, the cost of “changing history” is much reduced, so we can expect more of it.&lt;/p&gt;&lt;p&gt;This is where the immutability of email really shines. An email is your copy, and the sender can’t revise it later. This is frustrating when you’ve sent the wrong thing and have to send a separate correction later, but in the long term it’s insanely valuable.&lt;/p&gt;&lt;p&gt;It makes a huge difference to be able to go back and double-check your memory against an email you saw years ago and know that if they disagree, the email is correct. This is already not the case with web pages — they change, and it’s only becoming worse.&lt;/p&gt;&lt;h2 id=&quot;adapting-to-a-changing-world&quot; tabindex=&quot;-1&quot;&gt;Adapting to a changing world&lt;/h2&gt;&lt;p&gt;My son is studying at University now, and he’s one of a few students in his class who refuses to use AI to write his assignments. As he said “what’s the point of paying to be here if I’m not going to build the knowledge and skills for myself, and come out knowing how to do the thing” (near enough… I didn’t write the exact words down in an email, so I’m going off my own fallible memory!) I am so proud of him for having that attitude.&lt;/p&gt;&lt;p&gt;I’m also pleased to see that Fastmail’s staff, and many of our customers, are wary of AI tools.&lt;/p&gt;&lt;p&gt;But they are that, tools. The world is changing, and we need to adapt and understand it.&lt;/p&gt;&lt;h2 id=&quot;our-service-your-data&quot; tabindex=&quot;-1&quot;&gt;Our service, your data&lt;/h2&gt;&lt;p&gt;For our service, we want you to be able to do what you desire with your own email, calendars, and contacts. We will continue to build tools and integrations to make that easier.&lt;/p&gt;&lt;p&gt;You are welcome to operate your Fastmail account with AI tools, so long as that usage doesn’t otherwise breach our &lt;a href=&quot;/policies/terms-of-service/&quot;&gt;Terms of Service&lt;/a&gt;, or degrade the performance of our systems for other customers.&lt;/p&gt;&lt;h2 id=&quot;our-staff-your-privacy&quot; tabindex=&quot;-1&quot;&gt;Our staff, your privacy&lt;/h2&gt;&lt;p&gt;For our staff, we encourage understanding the tools that exist in the world, and how to use them safely. Our policy makes it clear that any use of tools, including tools with AI in them, must follow clear privacy-preserving principles:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Data Protection: All data protection, confidentiality, and privacy policies must be followed (our vendors for things like anti-abuse and support are moving towards using AI for translation, categorization, abuse detection – and we are ensuring that their policies continue to provide protection for our customers)&lt;/li&gt; &lt;li&gt;Accountability for work: Any AI generated writing or code must be reviewed and understood by a human being, and go through our regular second-set-of-eyes processes before being used&lt;/li&gt; &lt;li&gt;Bias awareness: Actively look for biases or hallucinations in AI output&lt;/li&gt; &lt;li&gt;Human authority: Always have a path for appeal to a human from any decision that is made by automated tools&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;the-future&quot; tabindex=&quot;-1&quot;&gt;The future&lt;/h2&gt;&lt;p&gt;Who knows what the future will bring, but we continue to be guided by the principles that we first &lt;a href=&quot;https://www.fastmail.com/blog/fastmails-values/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;publicly articulated in 2016&lt;/a&gt; and have held even longer. The data is yours, and we will be good stewards and good internet citizens, helping enable you to use your data in the ways you choose.&lt;/p&gt;</content>
        </entry><entry>
            <title>The Cache Crash</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/the-cache-crash/' />
			<id>https://www.fastmail.com/blog/the-cache-crash/</id>
			<updated>2025-09-17T00:00:01Z</updated><author>
				<name>Matthew Horsfall</name>
			</author><content xml:lang='en' type='html'>&lt;blockquote&gt; &lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Software_Freedom_Day&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Software Freedom Day&lt;/a&gt; is this week, and we thought we’d mark the occasion with a post about some of our work on open source software that we maintain. Fastmail believes in being a &lt;a href=&quot;https://www.fastmail.com/company/values/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;good internet citizen&lt;/a&gt;, and that belief means we participate in the development of free software, both by sharing software that we write and by contributing to the maintenance of free software that we use.&lt;/p&gt; &lt;p&gt;This is a highly technical post about how we solved a crash in our automated test systems. You do not need to make any changes to your Fastmail account or your email software, and you can ignore this post if it’s not interesting to you!&lt;/p&gt; &lt;/blockquote&gt;&lt;p&gt;What happens when a mysterious bug causes all your tests to fail once or twice a week? We try to solve these mysteries, even if it takes a year.&lt;/p&gt;&lt;p&gt;At Fastmail we have a strong testing culture around our code. When we write a new feature or fix a bug, we include tests to show that our feature works or the bug has actually been fixed. We open a Merge Request, and GitLab CI kicks off a job to run the project’s full test suite. We also have a scheduled job that runs once a day, testing our mainline branch. This checks that the tests haven’t started failing because of changes to the outside world.&lt;/p&gt;&lt;p&gt;The job stands up a virtual machine, installs all of the necessary software, updates configurations, starts the various services that make up Fastmail, and then runs all of the tests. Finally, it shuts down the box and generates some downloadable artifacts with the test results. There are quite a few tests. All told, this process takes about 13 minutes. As a developer, you’re hoping to see this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;File Count: 441
Assertion Count: 22040
    --&amp;gt;  Result: PASSED  &amp;lt;--
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Starting some time in early 2023, we’d occasionally have jobs fail in strange ways. We didn’t see failures around the features we were changing. Instead, we’d see a bunch of random tests would fail with the error “Signal: 6” and “Signal: 11”:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;( STDERR )  job 364    free(): invalid size
&amp;lt; REASON &amp;gt;  job 364    Test script returned error (Signal: 6)
&amp;lt; REASON &amp;gt;  job 364    No plan was declared, and no assertions were made.
&amp;lt; REASON &amp;gt;  job 369    Test script returned error (Signal: 11)
&amp;lt; REASON &amp;gt;  job 369    No plan was declared, and no assertions were made.
&amp;lt; REASON &amp;gt;  job 373    Test script returned error (Signal: 11)
&amp;lt; REASON &amp;gt;  job 373    No plan was declared, and no assertions were made.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That means one process got a SIGABRT and then after that a bunch of processes got SIGSEGV. Something, somewhere, was going wrong with the software’s use of memory. “Use of memory”, though, covers a &lt;em&gt;lot&lt;/em&gt; of what any given program is doing.&lt;/p&gt;&lt;p&gt;At the time, our test runs didn’t have much logging, so we couldn’t really tell what was going on, and we couldn’t easily reproduce the problem. On any given run, either it happened or it didn’t. For a while, I just assumed our VMs were occasionally dodgy somehow. The problem was rare, so we tried to ignore it and just click “Re-run job” as needed.&lt;/p&gt;&lt;p&gt;Soon though, it began to happen more frequently.&lt;/p&gt;&lt;p&gt;I added logging to our builds, and I made sure that the job’s build artifacts would always contain all the logs. With this evidence in hand, we began to see a pattern:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;t1[43556]: segfault at 8 ip 00007fca57fde1ee sp 00007ffdeab5ef00
  error 4 in FastMmap.so[7fca57fda000+6000] likely on CPU 2 (core 2, socket 0)
t3[43580]: segfault at 8 ip 00007fca57fde1ee sp 00007ffdeab5ef00
t9[43685]: segfault at 8 ip 00007fca57fde1ee sp 00007ffdeab5ef00
  error 4 in FastMmap.so[7fca57fda000+6000] likely on CPU 3 (core 3, socket 0)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The crash was always in &lt;code&gt;FastMmap.so&lt;/code&gt;! This comes from &lt;a href=&quot;https://metacpan.org/pod/Cache::FastMmap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cache::FastMmap&lt;/a&gt;, a Perl module of ours that provides an mmap-backed cache for use across processes.&lt;/p&gt;&lt;p&gt;Armed with this information, I modified our builds to collect the on-disk cache files, too. With those, I’d be able to examine what was in the cache when a crash happened. Unfortunately, the cache files were corrupt, leaving me with more questions than answers.&lt;/p&gt;&lt;p&gt;Over the course of months, I started taking a day here and there to dig through this problem – poring over the Cache::FastMmap code, our available logs, and our reproductions using the captured files. I tested and tossed out a &lt;strong&gt;lot&lt;/strong&gt; of theories.&lt;/p&gt;&lt;p&gt;Eventually, I added the ability to capture the core files generated during the test runs, because I realized that when this problem triggered, there was always a &lt;code&gt;SIGABRT&lt;/code&gt; first with the error message seen below, which previously I hadn’t thought of as significant:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;( STDERR )  job 364    free(): invalid size
&amp;lt; REASON &amp;gt;  job 364    Test script returned error (Signal: 6)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It seemed pretty clear that the first crash was corrupting the file, and all of the processes using the cache file after were crashing because of that.&lt;/p&gt;&lt;p&gt;So after adding the core file collector, I sat there clicking “Run job” on my merge request over and over and over and over and over, trying to trigger a crash.&lt;/p&gt;&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/alh-masto-post-KRlL9e_zAe-375.webp 375w, /assets/images/alh-masto-post-KRlL9e_zAe-594.webp 594w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;the author posts about the inability to reproduce the bug&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/alh-masto-post-KRlL9e_zAe-375.png&quot; width=&quot;594&quot; height=&quot;236&quot; srcset=&quot;/assets/images/alh-masto-post-KRlL9e_zAe-375.png 375w, /assets/images/alh-masto-post-KRlL9e_zAe-594.png 594w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/p&gt;&lt;p&gt;Eventually, it worked… but unfortunately I had no debugging symbols in the core files, so it didn’t help. I modified the build &lt;em&gt;again&lt;/em&gt;, including those, and then clicked “Run job” a bunch more.&lt;/p&gt;&lt;p&gt;Finally, I got what I needed, but this was even more confounding. The stack trace showed that the crash happened here:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#3  0x00007f8c429d6195 in mmc_do_expunge (cache=cache@entry=0x56113b842070,
    num_expunge=&amp;lt;optimized out&amp;gt;,
    new_num_slots=&amp;lt;optimized out&amp;gt;,
    to_expunge=0x56113b506210) at mmap_cache.c:800
800	  free(to_expunge);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;to_expunge&lt;/code&gt; was a valid pointer, and nothing else had freed it yet from what I could tell by looking at the source.&lt;/p&gt;&lt;p&gt;This told me that most likely we were writing over memory somewhere where we shouldn’t be, and we were stomping on malloc’s internal structures. Unfortunately, these kind of bugs are really hard to track down unless you can catch them in the act using tools like Valgrind or AddressSanitizer.&lt;/p&gt;&lt;p&gt;I tried various things from there, including modifying the build to output every action taken against the cache file. The logs included a high-resolution timestamps, the action that was taken (fetch, store, delete), and whether it resulted in success or not.&lt;/p&gt;&lt;p&gt;I replayed these recordings (single threaded - not a perfect test since it didn’t imitate inter-process locking or coordinating) with Valgrind and AddressSanitizer, but I got nowhere.&lt;/p&gt;&lt;p&gt;I stared at the code, thought for a while longer, and then did some web searching for the “free(): invalid size” error to see if that could maybe help me pinpoint where a bad write was happening.&lt;/p&gt;&lt;p&gt;That led me to &lt;a href=&quot;https://stackoverflow.com/questions/14795001/negative-indexing-gives-error-upon-freeing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;a post on StackOverflow&lt;/a&gt; that pointed toward negative indexing. After reading that, I tried a few experimental programs until I reproduced the abort:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;macro property token&quot;&gt;&lt;span class=&quot;directive-hash token&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;directive keyword token&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;string token&quot;&gt;&amp;lt;stdlib.h&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;keyword token&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;x &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;calloc&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;y &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;calloc&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;z &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; x&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;num &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;malloc&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;--&lt;/span&gt;z &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;&amp;amp;&lt;/span&gt;num&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;function token&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;y&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;function token&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;$ gcc negative-array-index.c
$ ./a.out
free(): invalid size
Aborted (core dumped)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Could it be that we were underflowing an array? That is, were we accidentally writing to memory below the lower bound of an array we’d allocated? With this theory in mind, I looked through the Cache::FastMmap source to find where we might be underflowing an array, and I spotted this loop in &lt;code&gt;mmc_calc_expunge()&lt;/code&gt;:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt; copy_base_det &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;calloc&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;used_slots&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt; copy_base_det_in &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; copy_base_det &lt;span class=&quot;operator token&quot;&gt;+&lt;/span&gt; used_slots

&lt;span class=&quot;comment token&quot;&gt;//[... more code ...]&lt;/span&gt;

&lt;span class=&quot;comment token&quot;&gt;/* Loop for each existing slot, and store in a list */&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt; slot_ptr &lt;span class=&quot;operator token&quot;&gt;!=&lt;/span&gt; slot_end&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt; slot_ptr&lt;span class=&quot;operator token&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
  MU32 data_offset &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;slot_ptr&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt; base_det &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;S_Ptr&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;p_base&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; data_offset&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  MU32 expire_on&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; kvlen&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;comment token&quot;&gt;/* Ignore if if free slot */&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;data_offset &lt;span class=&quot;operator token&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;comment token&quot;&gt;/* Definitely out if mode == 1 which means expunge all */&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;mode &lt;span class=&quot;operator token&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;copy_base_det_out&lt;span class=&quot;operator token&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; base_det&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;comment token&quot;&gt;/* Definitely out if expired, and not dirty */&lt;/span&gt;
  expire_on &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;S_ExpireOn&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;base_det&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;expire_on &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; now &lt;span class=&quot;operator token&quot;&gt;&gt;=&lt;/span&gt; expire_on&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;copy_base_det_out&lt;span class=&quot;operator token&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; base_det&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;comment token&quot;&gt;/* Track used space */&lt;/span&gt;
  kvlen &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;S_SlotLen&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;base_det&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;function token&quot;&gt;ROUNDLEN&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;kvlen&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;function token&quot;&gt;ASSERT&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;kvlen &lt;span class=&quot;operator token&quot;&gt;&amp;lt;=&lt;/span&gt; page_data_size&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  used_data &lt;span class=&quot;operator token&quot;&gt;+=&lt;/span&gt; kvlen&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;function token&quot;&gt;ASSERT&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;used_data &lt;span class=&quot;operator token&quot;&gt;&amp;lt;=&lt;/span&gt; page_data_size&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;comment token&quot;&gt;/* Potentially in */&lt;/span&gt;
  &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;--&lt;/span&gt;copy_base_det_in &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; base_det&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code allocates a new region of memory based on how many used slots we have (&lt;code&gt;used_slots&lt;/code&gt;) and takes a pointer to the end of that region of memory:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt; copy_base_det &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;calloc&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;used_slots&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt; copy_base_det_in &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; copy_base_det &lt;span class=&quot;operator token&quot;&gt;+&lt;/span&gt; used_slots&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, we loop over every slot in the cache, looking for ones that have data…&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;keyword token&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt; slot_ptr &lt;span class=&quot;operator token&quot;&gt;!=&lt;/span&gt; slot_end&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt; slot_ptr&lt;span class=&quot;operator token&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
  MU32 data_offset &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;slot_ptr&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  MU32 &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt; base_det &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;S_Ptr&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;p_base&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; data_offset&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;…skipping ones that don’t…&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;  &lt;span class=&quot;comment token&quot;&gt;/* Ignore if if free slot */&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;data_offset &lt;span class=&quot;operator token&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;…and finally copying over ones that do have data to the new region of memory, starting at the end of the memory region and working towards the front:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;  &lt;span class=&quot;operator token&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;--&lt;/span&gt;copy_base_det_in &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; base_det&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Later, &lt;code&gt;copy_base_det&lt;/code&gt; ends up in &lt;code&gt;to_expunge&lt;/code&gt; seen above.)&lt;/p&gt;&lt;p&gt;In order for this loop to underflow, the cache would have to have more slots with data in them than &lt;code&gt;used_slots&lt;/code&gt; accounts for.&lt;/p&gt;&lt;p&gt;The only way I could see this happening was if two processes wrote to the cache at the same time, corrupting its accounting. That, though, would mean locking had failed somehow…&lt;/p&gt;&lt;p&gt;To chase that down, I modified the build to log when a lock was acquired, when a write happened, and when the lock was released. I clicked “Run job” wildly, like I was playing the world’s worst Cookie Clicker game… and eventually I got another hit.&lt;/p&gt;&lt;p&gt;This time, there was a smoking gun:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;1739384152.734390 [32464] locked page 3: p_offset: 786432 pns: 179 pfs: 115 size: 262144
1739384152.737327 [32928] locked page 3: p_offset: 786432 pns: 179 pfs: 115 size: 262144
1739384152.738589 [32928] unlocked page 3 (with changes): pns: 179 pfs: 114
1739384152.738776 [32464] unlocked page 3 (with changes): pns: 179 pfs: 115
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;pns&lt;/code&gt; is the number of slots, and &lt;code&gt;pfs&lt;/code&gt; is the number of &lt;em&gt;free&lt;/em&gt; slots.&lt;/p&gt;&lt;p&gt;Above, we see that process 32464 locked the cache, and then before 32464 could write to the cache, process 32928 also locked the cache! That’s supposed to be impossible! Then, the second process wrote an entry to the cache, using up another slot, so it decremented the free slot count. And &lt;em&gt;then&lt;/em&gt;, the first process wrote out its changes. Those &lt;em&gt;didn’t&lt;/em&gt; include the use of any new slots, and so it set the free slot count back up to 115!&lt;/p&gt;&lt;p&gt;&lt;em&gt;This&lt;/em&gt; is what caused &lt;code&gt;mmc_calc_expunge&lt;/code&gt; to underflow – it allocated memory for 64 slots (179 total slots minus 115 free slots) but ended up seeing 65 slots with data that it needed to copy over!&lt;/p&gt;&lt;p&gt;Using the available logs on the machine, I identified the two processes, and found that one of them was a test that I had long suspected was the culprit.&lt;/p&gt;&lt;p&gt;That test uses &lt;a href=&quot;https://metacpan.org/pod/IO::Async::Process&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IO::Async::Process&lt;/a&gt; to fork a child, then run some Perl code in that child. It turns out that IO::Async::Process &lt;em&gt;closes all file descriptors&lt;/em&gt; except STDIN, STDOUT, and STDERR when forking. This is a surprising thing to do when forking – often a parent might intentionally leave file descriptors open for a child to continue using.&lt;/p&gt;&lt;p&gt;A file descriptor is just a number representing an open file or socket (like a network connection).&lt;/p&gt;&lt;p&gt;When Cache::FastMmap first starts up, it opens the cache file for reading/writing:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;keyword token&quot;&gt;int&lt;/span&gt; fh &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;share_file&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; O_RDWR&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;fh &lt;span class=&quot;operator token&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;keyword token&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;_mmc_set_error&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; errno&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string token&quot;&gt;&quot;Open of share file %s failed&quot;&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;share_file&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;fh &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; fh&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It then maps that file into memory:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;mm_var &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;mmap&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;c_size&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; PROT_READ &lt;span class=&quot;operator token&quot;&gt;|&lt;/span&gt; PROT_WRITE&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; MAP_SHARED&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;fh&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Later, when it needs to write out changes to a specific page in the cache, it locks that region of mapped memory by locking specific ranges of bytes within the file &lt;em&gt;using the file descriptor number&lt;/em&gt;:&lt;/p&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;comment token&quot;&gt;/* Setup fcntl locking structure */&lt;/span&gt;
lock&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;l_type &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; F_WRLCK&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
lock&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;l_whence &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constant token&quot;&gt;SEEK_SET&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
lock&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;l_start &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; p_offset&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
lock&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;l_len &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;c_page_size&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;comment token&quot;&gt;/* Lock the page (block till done, signal, or timeout) */&lt;/span&gt;
lock_res &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;fcntl&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;cache&lt;span class=&quot;operator token&quot;&gt;-&gt;&lt;/span&gt;fh&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; F_SETLKW&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&lt;/span&gt;lock&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If another process using Cache::FastMmap with the same file tries to write changes, it will block trying to get the same lock until the current process is finished and unlocks the file.&lt;/p&gt;&lt;p&gt;However, when IO::Async::Process closes all open file descriptors, &lt;code&gt;cache-&amp;gt;fh&lt;/code&gt; becomes invalid, since that number no longer belongs to any open file or socket. Then, later, if the child process opens up any new files or sockets, that number could be reused(!), so when Cache::FastMmap attempts to lock the cache in the child, it succeeds, but locks &lt;em&gt;the wrong file&lt;/em&gt;!&lt;/p&gt;&lt;p&gt;The memory mapped region is not tied to the file descriptor after having been mapped, so any changes written to memory would still overwrite the cache data, and stomp on whatever work any other process is doing at the same time.&lt;/p&gt;&lt;p&gt;We fixed all this by ditching the “fork and run more Perl in the child” and replacing it with “fork and exec a brand new process”. With exec, we replace our running program entirely, so we no longer have the mmaped regions available. We’ve been segfault free ever since! We also added code to a new open source release of Cache::FastMmap, to try to prevent anybody else from going through all this.&lt;/p&gt;&lt;p&gt;What’s really frustrating about this is that I almost cracked it months ago. I had suspected that the fork-and-close-children behaviour of IO::Async::Process was the problem, but when I tested some code manually to trigger the bug, I always got “invalid file descriptor” in the children during locking of the mmaped region… because my tests weren’t opening up new file descriptors. The lock file descriptor wasn’t valid and so the mmaped region couldn’t be be written over.&lt;/p&gt;&lt;p&gt;Alas.&lt;/p&gt;</content>
        </entry><entry>
            <title>Work offline with Fastmail</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/fastmail-works-offline/' />
			<id>https://www.fastmail.com/blog/fastmail-works-offline/</id>
			<updated>2025-08-26T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;The internet is available in more places every day, from subways to aeroplanes. But it’s still not universal, and it always seems to disappear at the most inopportune time. The overwhelmed mobile network cuts out just when you need that concert ticket. You realise you don’t have the address of your hotel just after landing in a country with no roaming agreement.&lt;/p&gt;&lt;p&gt;Today, we’re pleased to announce full offline support for all our customers, in our apps and even on the web. No internet? No problem.&lt;/p&gt;&lt;h2 id=&quot;how-do-i-turn-on-offline-support&quot; tabindex=&quot;-1&quot;&gt;How do I turn on offline support?&lt;/h2&gt;&lt;p&gt;We’ll be automatically enabling offline support for users of our iOS and Android apps progressively over the coming weeks. But if you can’t wait, or want offline support in your web browser, you can turn it on in &lt;a href=&quot;https://app.fastmail.com/settings/offline&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Settings → Offline&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/offline-settings-ui-T35rrNtmGL-375.webp 375w, /assets/images/offline-settings-ui-T35rrNtmGL-750.webp 750w, /assets/images/offline-settings-ui-T35rrNtmGL-1500.webp 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;Screenshot of offline settings&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/offline-settings-ui-T35rrNtmGL-375.png&quot; width=&quot;1500&quot; height=&quot;900&quot; srcset=&quot;/assets/images/offline-settings-ui-T35rrNtmGL-375.png 375w, /assets/images/offline-settings-ui-T35rrNtmGL-750.png 750w, /assets/images/offline-settings-ui-T35rrNtmGL-1500.png 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/p&gt;&lt;p&gt;Depending on the size of your account and the speed of your internet connection, it can take a few minutes or sometimes longer to do the initial sync. To allow the syncing to occur just leave the tab open in the browser (even in the background) until it shows it’s ready. Due to background processing restrictions on mobile platforms, our app can only do this initial sync while running in the foreground, but you can keep using it as normal while this is happening.&lt;/p&gt;&lt;p&gt;By default, we’ll download the contents of recent messages, plus messages you open on your device. You can change this to all messages in the settings. Attachments are only cached for offline use when opened on the device.&lt;/p&gt;&lt;p&gt;A few things to note if you want to enable offline support in your web browser:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;You must tick “Keep me logged in” when logging in to be able to turn on offline support.&lt;/li&gt; &lt;li&gt;Offline support requires a modern browser — if you’re running something we can’t support, you’ll see a banner telling you this on the settings page.&lt;/li&gt; &lt;li&gt;Remember to bookmark your inbox to make it easy to get to. If you usually use a search engine to get to Fastmail, this won’t work without internet. A bookmark lets you open Fastmail’s webmail directly, which will load even when offline.&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;what-if-i-don-t-want-offline-support&quot; tabindex=&quot;-1&quot;&gt;What if I don’t want offline support?&lt;/h2&gt;&lt;p&gt;You can turn it off at any time in &lt;a href=&quot;https://app.fastmail.com/settings/offline&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Settings → Offline&lt;/a&gt;. Turning it off will delete the local mail cache, so you will have to download it again if you change your mind.&lt;/p&gt;&lt;h2 id=&quot;is-there-anything-i-can-t-do-offline&quot; tabindex=&quot;-1&quot;&gt;Is there anything I can’t do offline?&lt;/h2&gt;&lt;p&gt;We wanted to make the online-offline transition seamless, so you mostly shouldn’t need to think about it. Almost everything you can do online you can do offline, such as reading mail, replying, viewing and editing your contacts or calendar, and changing most settings. As soon as you’re online again, it will all sync back to the server.&lt;/p&gt;&lt;p&gt;There are however a few minor differences to be aware of when working offline:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Mail search will not look inside attachments, and will give slightly different results to when online. And of course if you don’t choose to make every message available offline, it won’t be able to match against content it hasn’t downloaded!&lt;/li&gt; &lt;li&gt;Snoozed messages will not move back to the inbox while offline.&lt;/li&gt; &lt;li&gt;Calendar reminders will not show a notification while offline.&lt;/li&gt; &lt;li&gt;You can’t delete attachments from a message you’ve received.&lt;/li&gt; &lt;li&gt;You can’t add or change users or domains, change your plan or update your billing details, or change your security settings.&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;what-s-the-tech-behind-your-offline-support&quot; tabindex=&quot;-1&quot;&gt;What’s the tech behind your offline support?&lt;/h2&gt;&lt;p&gt;Interested in how we made this all work? We wrote up the technical details of &lt;a href=&quot;/blog/offline-architecture/&quot;&gt;the general architecture&lt;/a&gt;, &lt;a href=&quot;/blog/offline-sync/&quot;&gt;how we sync changes back to the server&lt;/a&gt;, and &lt;a href=&quot;/blog/offline-mail-storage/&quot;&gt;how we made offline email fast&lt;/a&gt; when we launched the public beta late last year.&lt;/p&gt;&lt;p&gt;Fastmail remains at the cutting edge of web development, with one of the fastest and most sophisticated apps anywhere on the internet. We’re super proud of this huge step forward in functionality, and our biggest hope is you almost don’t notice it — Fastmail now just works wherever you need it to.&lt;/p&gt;</content>
        </entry><entry>
            <title>The new phishing: How to spot email scams in 2025</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/the-new-phishing-how-to-spot-email-scams-in-2025/' />
			<id>https://www.fastmail.com/blog/the-new-phishing-how-to-spot-email-scams-in-2025/</id>
			<updated>2025-08-17T12:00:00Z</updated><author>
				<name>Dmitri Leonov </name>
			</author><content xml:lang='en' type='html'>&lt;h2 id=&quot;your-inbox-is-the-key-to-almost-everything-you-do-online-no-wonder-scammers-keep-showing-up&quot; tabindex=&quot;-1&quot;&gt;Your inbox is the key to almost everything you do online. No wonder scammers keep showing up&lt;/h2&gt;&lt;p&gt;Think about it: your inbox is where your personal and professional worlds collide, from two-factor authentication codes, to delivery notifications, to medical prescriptions. It’s an archive of ancient calendar invitations and messages from your ex in 2008.&lt;/p&gt;&lt;p&gt;That’s why email is the perfect attack vector for scams. It only takes a few minutes, a free email account, and a list of scraped or purchased email addresses to get malicious messages into thousands of inboxes. And those messages are getting smarter every day.&lt;/p&gt;&lt;p&gt;At SaneBox, we filter millions of emails daily. We’ve seen it all: the suspicious invoices, near-perfect Apple login alerts, mysterious princes earnestly trying to transfer you billions of dollars…and we’ve noticed a worrying trend. Today’s phishing scams are getting smarter, more personalized, and much harder to spot.&lt;/p&gt;&lt;p&gt;Here’s how to recognize the smartest email scams and stay ahead.&lt;/p&gt;&lt;h2 id=&quot;today-s-scams-don-t-always-look-like-scams&quot; tabindex=&quot;-1&quot;&gt;Today’s scams don’t always look like scams&lt;/h2&gt;&lt;p&gt;It used to be easy to spot scams. If the typos didn’t give it away, the message beginning “Dear Esteemed Beneficiary” surely did.&lt;/p&gt;&lt;p&gt;But phishing today has credible language. It’s not just your mom you need to worry about, even the most skeptical, and perennially online, people can be fooled. In fact, &lt;a href=&quot;https://aag-it.com/the-latest-phishing-statistics/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;millennials are more likely to be victims&lt;/a&gt; than their Gen X counterparts!&lt;/p&gt;&lt;p&gt;Phishing emails use AI to generate convincing replies in any language or tone, and they are perfecting the dark art of “brand mimicry” to pose as legit companies. Scammers are playing the long game, too. They might not immediately present you with a suspicious form, or ask for your credit card details. They want to gather just enough breadcrumbs — your employer, your role, maybe some personal intel — to make their next attack even more believable, or to impersonate you. Some email scams you’d never see coming, like &lt;a href=&quot;https://www.nytimes.com/2025/06/29/arts/design/lady-gaga-art-dispute.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;impersonating Lady Gaga&lt;/a&gt; to buy your painting!&lt;/p&gt;&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-375.webp 375w, /assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-750.webp 750w, /assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-1500.webp 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;Why even tech-savvy users fall for scams&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-375.png&quot; width=&quot;1500&quot; height=&quot;689&quot; srcset=&quot;/assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-375.png 375w, /assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-750.png 750w, /assets/images/fb-screenshot-2x-6-KDvUmZf7zZ-1500.png 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/p&gt;&lt;h2 id=&quot;why-even-tech-savvy-users-fall-for-scams&quot; tabindex=&quot;-1&quot;&gt;Why even tech-savvy users fall for scams&lt;/h2&gt;&lt;p&gt;Don’t feel bad if you can’t spot anything wrong with this email. Other than the spacing and address, it’s almost a complete replica of a legitimate email. Here’s why these emails work so well:&lt;/p&gt;&lt;h3 id=&quot;1-they-hijack-our-trust&quot; tabindex=&quot;-1&quot;&gt;1. They hijack our trust&lt;/h3&gt;&lt;p&gt;Well-loved companies like Facebook, Apple, and Spotify spend years conditioning us to associate their logo, colors, and tone with a warm fuzzy feeling of trust. They’ve done such an incredible job that &lt;a href=&quot;https://hbr.org/2013/07/your-brain-at-work&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;our favorite brands light up our brains&lt;/a&gt; with the same emotional force as our families and partners! Your guard is down, and you’re more likely to click.&lt;/p&gt;&lt;h3 id=&quot;2-they-exploit-urgency-and-fomo&quot; tabindex=&quot;-1&quot;&gt;2. They exploit urgency and FOMO&lt;/h3&gt;&lt;p&gt;“You’ve been tagged in a document.” “Your subscription failed.” These messages use social engineering to stir up anxiety. When we see something urgent and immediately actionable, like verifying a transaction or confirming that a login wasn’t us, our brains are seeking out the dopamine rush of completing a task. Except the task we just rushed to complete was clicking on a scam.&lt;/p&gt;&lt;h3 id=&quot;3-we-re-experiencing-cognitive-overload&quot; tabindex=&quot;-1&quot;&gt;3. We’re experiencing cognitive overload&lt;/h3&gt;&lt;p&gt;We’re skimming our emails, juggling tabs, and looking for quick wins in our inbox. Cognitive overload makes us more impulsive, and the urge to “just handle it” is exactly what phishers exploit. As long as we have a noisy inbox, we’ll be vulnerable to scams.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;SaneBox Tip:&lt;/strong&gt; Our &lt;strong&gt;SaneLater filter&lt;/strong&gt; ensures only important emails go to your inbox. This reduces overwhelm, so you’re less likely to make hasty decisions and fall for a scam.&lt;/p&gt;&lt;h2 id=&quot;email-scam-red-flags-to-watch-for-in-2025&quot; tabindex=&quot;-1&quot;&gt;Email scam red flags to watch for in 2025&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Subtle Domain Spoofing:&lt;/strong&gt; Lookalike domains — think subtle changes like saneb0x.com, spot1fy.com — helped create &lt;a href=&quot;https://www.enterprisesecuritytech.com/post/the-rise-of-lookalike-domains-how-subtle-spoofs-are-supercharging-cybercrime&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;$12.5B of losses from impersonation scams in 2024&lt;/a&gt;. Hover before you click. Watch for &lt;a href=&quot;https://www.cycognito.com/learn/drps/spoofed-domains.php&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;homograph attacks&lt;/a&gt;, too — which use the visual similarity of characters in different scripts to create URLs that look almost identical to ones you trust.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Sneaky attachments:&lt;/strong&gt; “Invoice754332_PDF.exe for your recent service” uses a classic trick to make us believe we’re opening a PDF. At first glimpse, it looks like a regular invoice but it’s actually an executable file that can run malicious code on your machine. We’re conditioned to open attachments that reference money; don’t fall for it.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Vague personalization:&lt;/strong&gt; “Quick question about {Your Employer} last payment.” Scammers use just enough detail to make a message sound legitimate, without proving they know who you are. Do not feed them more intel, no matter how minor it seems. Even something as simple as “I no longer work there,” or “is this meant for Brenda’s team?” gives scammers ammo for future scams.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;SaneBox Tip:&lt;/strong&gt; If it seems suspicious, &lt;strong&gt;drag the message to the BlackHole&lt;/strong&gt; and you’ll never hear from that sender again.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Suspicious Timing:&lt;/strong&gt; A request to update your credit card information on Christmas Day? A Friday night invoice demanding payment before the weekend? Phishing scams are often timed to strike when you’re distracted (or tipsy!) and less likely to scrutinize them. If the timing primes you to act with urgency, all the more reason to slow down.&lt;/p&gt;&lt;h2 id=&quot;your-email-scam-defense-stack-sane-box-fastmail&quot; tabindex=&quot;-1&quot;&gt;Your email scam defense stack: SaneBox + Fastmail&lt;/h2&gt;&lt;p&gt;Fighting against email phishing isn’t just about vigilance, it’s about stacking the right tools so you don’t have to battle alone.&lt;/p&gt;&lt;h3 id=&quot;fastmail&quot; tabindex=&quot;-1&quot;&gt;Fastmail:&lt;/h3&gt;&lt;ul&gt; &lt;li&gt;All data is encrypted to the highest level&lt;/li&gt; &lt;li&gt;Strict Transport Security header protecting all modern browsers against SSL stripping&lt;/li&gt; &lt;li&gt;Regular internal security audits&lt;/li&gt; &lt;/ul&gt;&lt;h3 id=&quot;sane-box&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;SaneBox:&lt;/strong&gt;&lt;/h3&gt;&lt;ul&gt; &lt;li&gt;AI filtering that’s based on your actual behavior&lt;/li&gt; &lt;li&gt;One-click unsubscribe to banish senders to the BlackHole&lt;/li&gt; &lt;li&gt;No ads, no tracking — ever&lt;/li&gt; &lt;/ul&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Dmitri Leonov&lt;/strong&gt; is the CEO of &lt;a href=&quot;https://www.sanebox.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SaneBox&lt;/a&gt;, an AI-powered email tool that helps people save time and stay focused. He has over 20 years of experience growing startups, leading strategy, and building high-performing teams.&lt;/p&gt;</content>
        </entry><entry>
            <title>Better themes, better navigation, better search</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/better-themes-better-search/' />
			<id>https://www.fastmail.com/blog/better-themes-better-search/</id>
			<updated>2025-08-05T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;Today we released another significant update to the Fastmail web and mobile apps, introducing more beautiful theming and faster navigation on desktop, plus a powerful new search tool to help you find the email you’re looking for.&lt;/p&gt;&lt;h2 id=&quot;beautiful-new-themes&quot; tabindex=&quot;-1&quot;&gt;Beautiful new themes&lt;/h2&gt;&lt;p&gt;We’ve revised the Fastmail UI to make it more balanced, more focused, and pull in more color from your theme. Head over to the settings and you’ll find many beautiful new default themes to choose from, or make it personal with custom colors.&lt;/p&gt;&lt;p&gt;&lt;video class=&quot;aspect-video relative rounded-lg shadow-card z-50&quot; autoplay loop playsinline muted controls width=&quot;1280&quot; height=&quot;720&quot;&gt; &lt;source src=&quot;/assets/blog/2025-08-05-better-themes-better-search/Colours.webm&quot; type=&quot;video/webm; codecs=vp9,vorbis&quot;&gt; &lt;source src=&quot;/assets/blog/2025-08-05-better-themes-better-search/Colours.mp4&quot; type=&quot;video/mp4&quot;&gt; &lt;/video&gt;&lt;/p&gt;&lt;h2 id=&quot;faster-navigation&quot; tabindex=&quot;-1&quot;&gt;Faster navigation&lt;/h2&gt;&lt;p&gt;The tab bar in our mobile app has allowed switching between your mail, calendar, and other apps in just one tap for years. Now, webmail gets quick app switching too. Just like on mobile you can choose which apps to show in the settings, or turn them all off to hide the navigation bar entirely.&lt;/p&gt;&lt;p&gt;For our customers with multiple accounts, it’s now much faster to move between them with our new quick user switching in the top right. Log in to more than one account in our mobile app and you’ll see the quick user switcher appear there too.&lt;/p&gt;&lt;p&gt;Finally, our calendar on desktop now has faster navigation between day/week/month view. Want something more unusual? Click on the selected tab again to customise the number of days/weeks on show.&lt;/p&gt;&lt;p&gt;&lt;video class=&quot;aspect-video relative rounded-lg shadow-card z-50&quot; autoplay loop playsinline muted controls width=&quot;1280&quot; height=&quot;720&quot;&gt; &lt;source src=&quot;/assets/blog/2025-08-05-better-themes-better-search/Navigation.webm&quot; type=&quot;video/webm; codecs=vp9,vorbis&quot;&gt; &lt;source src=&quot;/assets/blog/2025-08-05-better-themes-better-search/Navigation.mp4&quot; type=&quot;video/mp4&quot;&gt; &lt;/video&gt;&lt;/p&gt;&lt;h2 id=&quot;fine-tune-your-search&quot; tabindex=&quot;-1&quot;&gt;Fine-tune your search&lt;/h2&gt;&lt;p&gt;Our new search refinement toolbar lets you quickly add, remove, or change filters to narrow down your search.&lt;/p&gt;&lt;p&gt;We already have intelligent autocomplete to start you off, but sometimes the first attempt doesn’t quite give you the results you’re looking for. Our new toolbar makes it faster to quickly add useful filters to find the email you want.&lt;/p&gt;&lt;p&gt;Have you ever done a search and found your results are being dominated by a particular sender you’re not interested in, or from a folder you know doesn’t have what you need? We intelligently offer to exclude these with just a few clicks, based on the previous search results.&lt;/p&gt;&lt;p&gt;We hope you find this useful in getting the most out of Fastmail’s powerful search.&lt;/p&gt;&lt;p&gt;&lt;video class=&quot;aspect-video relative rounded-lg shadow-card z-50&quot; autoplay loop playsinline muted controls width=&quot;1280&quot; height=&quot;720&quot;&gt; &lt;source src=&quot;/assets/blog/2025-08-05-better-themes-better-search/Search.webm&quot; type=&quot;video/webm; codecs=vp9,vorbis&quot;&gt; &lt;source src=&quot;/assets/blog/2025-08-05-better-themes-better-search/Search.mp4&quot; type=&quot;video/mp4&quot;&gt; &lt;/video&gt;&lt;/p&gt;&lt;h2 id=&quot;and-more&quot; tabindex=&quot;-1&quot;&gt;And more&lt;/h2&gt;&lt;p&gt;Mistakes happen. That’s why Fastmail has &lt;a href=&quot;/features/undo-everything/&quot;&gt;multi-level undo/redo support&lt;/a&gt; for almost every action. But after the initial notification disappeared, you had to know the keyboard shortcut. Now you can undo/redo from the actions menu in your inbox as well.&lt;/p&gt;&lt;p&gt;Fastmail files makes it easy to host a basic website. Now we’ve added simple editing of plain text files, so you can make basic changes without leaving the app, or create a new text file on the go.&lt;/p&gt;</content>
        </entry><entry>
            <title>Fastmail joins the Internet Society</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/fastmail-joins-the-internet-society/' />
			<id>https://www.fastmail.com/blog/fastmail-joins-the-internet-society/</id>
			<updated>2025-06-20T00:00:01Z</updated><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;At Fastmail one of &lt;a href=&quot;/company/values/&quot;&gt;our values&lt;/a&gt; is “we are a good internet citizen”. A key driving principle behind our work on &lt;a href=&quot;/blog/ten-years-of-jmap/&quot;&gt;JMAP&lt;/a&gt; is our commitment to keeping email open, and not just another proprietary messaging platform.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://www.internetsociety.org/mission/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Internet Society’s mission&lt;/a&gt; of making the internet be for everyone is right in line with our own goals and values, and Fastmail are proud to announce that we have joined them as a &lt;a href=&quot;https://www.internetsociety.org/about-internet-society/organization-members/list/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bronze level member&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We look forward to participating in more of the Internet Society’s outreach and advocacy, as well as our ongoing commitments to open source and open standards work.&lt;/p&gt;</content>
        </entry><entry>
            <title>Addressing Privacy Fatigue</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/addressing-privacy-fatigue/' />
			<id>https://www.fastmail.com/blog/addressing-privacy-fatigue/</id>
			<updated>2025-06-15T11:00:00Z</updated><author>
				<name>Bek Fraser</name>
			</author><content xml:lang='en' type='html'>&lt;h2 id=&quot;privacy-fatigue-is-a-real-feeling-here-s-how-to-make-it-manageable&quot; tabindex=&quot;-1&quot;&gt;Privacy fatigue is a real feeling — here’s how to make it manageable&lt;/h2&gt;&lt;h6 id=&quot;supporting-the-office-of-australian-information-commissioner-privacy-awareness-week-2025-privacy-it-s-everyone-s-business&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Supporting the Office of Australian Information Commissioner Privacy Awareness Week 2025: Privacy — It’s Everyone’s Business&lt;/strong&gt;&lt;/h6&gt;&lt;p&gt;Do you ever feel that when you start reading about privacy, you’re suddenly overwhelmed by endless lists of tips such as enable two-factor authentication (huh?), check app permissions (what for?), review privacy policies (from where?), use encrypted messaging (is this another language?), switch browsers (truly?), audit your social media settings and even get off social media (will I lose all my friends?). The list goes on and on.&lt;/p&gt;&lt;p&gt;We know the problem isn’t that people don’t care about privacy. It’s that the privacy ecosystem has become so complex that it’s genuinely a scary place to start. When every digital choice feels like it requires a PhD in cybersecurity to evaluate properly, it’s natural to…give up.&lt;/p&gt;&lt;p&gt;So, in support of this year’s OAIC Privacy Awareness Week, I want to give you some of my tips, in everyday language that I hope will help this topic appear less overwhelming.&lt;/p&gt;&lt;h2 id=&quot;step-1-the-foundation-high-impact-low-effort&quot; tabindex=&quot;-1&quot;&gt;Step 1: The foundation (High Impact, low effort)&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Use a password manager:&lt;/strong&gt; While there are many reasons why we have partnered with 1Password; remembering all of your passwords can feel unachievable. Most browsers will have a built-in option these days, however, using tools such as 1Password provides additional options, such as sharing password vaults between families and syncing easily between devices and different browsers. A password manager will securely manage your passwords, removing all of the pain you would be feeling otherwise, and it works for all technical skill levels!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Use strong, unique passwords.&lt;/strong&gt; As I have already mentioned, using a password manager such as 1Password means you can build out unique and strong passwords. Even moving to the level of using a passphrase. This protects you across every service you use, and these modern password managers make it nearly effortless. They truly are worth the investment.&lt;/p&gt;&lt;h2 id=&quot;step-2-cleaning-up-moderate-impact-some-effort&quot; tabindex=&quot;-1&quot;&gt;Step 2: Cleaning up (Moderate impact, some effort)&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Review and clean up your social media privacy settings.&lt;/strong&gt; Not because social media is all that bad, but because the default settings are usually designed to share more than you might want. This isn’t about ruling people out of your life, it’s about giving you back some control of who or what you want to let in! Don’t forget to also check your recent followers list to remove or block the unknowns. Tim234567jur89 may not be the same person you think they are!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Also, be selective about app permissions.&lt;/strong&gt; You probably don’t need to give that flashlight app access to your contacts or location history for it to work. Providing apps with additional permissions that are unnecessary for their operation allows the builders of that app to gather additional information. This information may be used for marketing purposes, such as targeted advertising to you based on your behaviours. Consider all permission requests and if they are not necessary for it to operate, is it truly required?&lt;/p&gt;&lt;h2 id=&quot;step-3-aiming-for-protection-excellence-varying-level-of-impact-greater-effort&quot; tabindex=&quot;-1&quot;&gt;Step 3: Aiming for protection excellence (Varying level of impact, greater effort)&lt;/h2&gt;&lt;p&gt;I’m definitely far from a technical expert here, but this is where I know encrypted messaging and regular security audits come in. Depending on what products you choose, the level of protection provided by the way they encrypt your data will vary. Personally, this is where I see a difference in using Fastmail over other email products; I have greater control of my data, and Masked Email addresses mean I can have even better control of my digital presence.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;So, this is where I do recommend to switch to a privacy respecting email provider such as Fastmail.&lt;/strong&gt; Your email is the hub of your digital life - it’s connected to every account, contains your most sensitive communications, and often stores years of personal history. It even stores this sort of information about your family and friends too. Moving to an email service that doesn’t scan your messages or sell your data is probably the single most impactful privacy decision you can make. For you and for anyone you ever have or will communicate with digitally!&lt;/p&gt;&lt;h2 id=&quot;making-privacy-sustainable&quot; tabindex=&quot;-1&quot;&gt;Making privacy sustainable&lt;/h2&gt;&lt;p&gt;I feel that the key to overcoming privacy overwhelm is to remember that privacy protection is an ongoing focus, not just a one stop destination. You don’t need to achieve perfect privacy security to benefit from better privacy habits. Every small step you take reduces your exposure and puts you more in control of your digital life.&lt;/p&gt;&lt;p&gt;Start with one change that matters to you. Get comfortable with it. Then consider what might make sense next. Privacy isn’t about living in digital isolation - it’s about making intentional choices about how your personal information is used.&lt;/p&gt;&lt;p&gt;At Fastmail, we’ve structured our entire business model around the principle of privacy matters. We’re a paid service so we never have to sell ads or your personal data. You pay us, so you’re our priority, not advertisers. We don’t scan your emails to build advertising profiles. We don’t sell your data to third parties. We make money when you pay us for a top quality service you value that we constantly improve on - it’s that simple.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;Fastmail provides fast, private email that respects your privacy and puts you in control. No ads, no data mining, no nonsense. Learn more about how we’re different at &lt;a href=&quot;http://fastmail.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fastmail.com&lt;/a&gt;.&lt;/p&gt;</content>
        </entry><entry>
            <title>Introducing the twom database format</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/introducing-twom/' />
			<id>https://www.fastmail.com/blog/introducing-twom/</id>
			<updated>2025-06-13T00:00:01Z</updated><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;I wrote back in December about my ideas for a new &lt;a href=&quot;https://en.wikipedia.org/wiki/Skip_list&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;skiplist&lt;/a&gt; based database format for the &lt;a href=&quot;https://www.cyrusimap.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cyrus IMAP&lt;/a&gt; server.&lt;/p&gt;&lt;p&gt;I spent a bunch of time over the next month writing just that. It’s called twom, “two” for the dual level0 pointers (the same as twoskip) and “m” for &lt;a href=&quot;https://en.wikipedia.org/wiki/Mmap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mmap&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Multiversion_concurrency_control&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mvcc&lt;/a&gt;. I had planned to pronounce it “tomb” (for “it has tombstone records”) but have wound up usually saying “two em” for clarity. Maybe as this is posted on a Friday 13th we can call it “tomb” just for today.&lt;/p&gt;&lt;p&gt;All the code was in a &lt;a href=&quot;https://github.com/cyrusimap/cyrus-imapd/pull/5157&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;merged pull request&lt;/a&gt;. The database itself is just a single standalone C language source file and associated header file. There’s also a cyrusdb interface wrapper, and a copy of &lt;a href=&quot;https://github.com/Cyan4973/xxHash/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;xxHash&lt;/a&gt; directly in our source tree as well:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;brong@elg:~/src/cyrus-imapd$ wc -l lib/*twom* lib/xxhash.h
   421 lib/cyrusdb_twom.c
  3298 lib/twom.c
   157 lib/twom.h
  7091 lib/xxhash.h
 10967 total
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Amusingly, xxhash.h takes more space than anything else (and the lion’s share of the CPU usage as well, despite being much faster than crc32 as used in twoskip).&lt;/p&gt;&lt;h2 id=&quot;why-a-new-database-format&quot; tabindex=&quot;-1&quot;&gt;Why a new database format?&lt;/h2&gt;&lt;p&gt;As I wrote &lt;a href=&quot;https://www.fastmail.com/blog/twoskip-and-more/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;during our advent series&lt;/a&gt;, twoskip has served us well, but it had a couple of major performance issues - particularly with our current ZFS on NVMe architecture. Retro-fitting some improvements to twoskip would have been possible, but it couldn’t do the most important thing (MVCC reads) because the on-disk format didn’t have the right structure.&lt;/p&gt;&lt;p&gt;We wanted MVCC because you could then repack an entire database without holding a lock the whole time, and replay the log at the end. I discovered that I didn’t even need an exclusive lock at all to do a repack, it could all be done with short-lived readlocks.&lt;/p&gt;&lt;p&gt;Overall these changes made twom faster. We don’t have performance data to show how much faster because we changed too many things at the same time to isolate it, but one simple repack test of a giant file showed repacks that had been 35 minutes with the twoskip file taking just over a minute with twom. A rather massive improvement! And even better, you could write to the file during the repack without losing data, while twoskip would have held an exclusive lock the entire time.&lt;/p&gt;&lt;p&gt;These are the major changes:&lt;/p&gt;&lt;h3 id=&quot;xx-hash&quot; tabindex=&quot;-1&quot;&gt;xxHash&lt;/h3&gt;&lt;p&gt;The performance of xxHash vs CRC32 over small amounts of data is much better.&lt;/p&gt;&lt;p&gt;Twoskip and twom formats both hash blocks of about 40 bytes for the tracking pointers, and our keys and values are quite short in a lot of Cyrus formats too.&lt;/p&gt;&lt;p&gt;A faster hash function for small amounts of data is a big win. We chose xxHash for its friendly license and great performance.&lt;/p&gt;&lt;h3 id=&quot;mvcc-repacks&quot; tabindex=&quot;-1&quot;&gt;MVCC repacks&lt;/h3&gt;&lt;p&gt;This is massive. We have databases in the tens of gigabytes on the largest accounts, and when one of those chose to “checkpoint” - rewrite to remove stale data, it could lock an account for 30 minutes. This is obviously unacceptable.&lt;/p&gt;&lt;p&gt;The same repack taking one minute and allowing reads plus a thousand or so opportunities for writes during that repack is a completely different story; speaking of which…&lt;/p&gt;&lt;h3 id=&quot;starvation-free-locking&quot; tabindex=&quot;-1&quot;&gt;Starvation-free locking&lt;/h3&gt;&lt;p&gt;I can’t believe I got all the way to testing this thing to discover how little I knew about fcntl locking.&lt;/p&gt;&lt;p&gt;TIL: &lt;a href=&quot;https://stackoverflow.com/questions/27625597/how-to-implement-a-writer-preferring-read-write-lock-for-nix-processes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fcntl isn’t fair&lt;/a&gt;. Releasing the lock didn’t magically let a waiting writer proceed, the same process would often pick up the lock again without giving another process a chance. On busy files, writers could entirely starve.&lt;/p&gt;&lt;p&gt;We decided to use a two-offset locking strategy within the single database file, so writers can queue up waiting while all the readers are busy, and be ensured of their place in the line.&lt;/p&gt;&lt;p&gt;The git history will show I didn’t get this right the first time, and had to do a patch while testing across our fleet on just the statuscache file, an ephemeral database with high churn. The great thing about testing with statuscache is that users won’t notice if it breaks, since any error will just cause the status to be re-computed!&lt;/p&gt;&lt;h3 id=&quot;mmap-for-reading-and-writing&quot; tabindex=&quot;-1&quot;&gt;MMAP for reading and writing&lt;/h3&gt;&lt;p&gt;Twoskip (and all the other Cyrus internal formats, like cache and index) uses &lt;code&gt;mmap&lt;/code&gt; for read but &lt;code&gt;write&lt;/code&gt; for writes. On most operating systems this is fine, they share a common cache, but it seemed simpler and nicer to use mmap for both reads and writes rather than creating in-memory structures to copy over.&lt;/p&gt;&lt;p&gt;And yes, we did read and watch &lt;a href=&quot;https://www.youtube.com/watch?v=1BRGU_AS25c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;the arguments against it&lt;/a&gt; (video)!&lt;/p&gt;&lt;p&gt;Of course we’re using msync to get reliable commits, and twom still has robust transactions with the same 3-syncs-per-commit pattern that made twoskip so solid.&lt;/p&gt;&lt;p&gt;MMAP also reduces syscalls. With twoskip we had to make multiple seek and write syscalls for each update, as we rewrote the backpointers (the average record has level 2, so needs to write 2 different backpointers plus the record itself).&lt;/p&gt;&lt;p&gt;This means that a single twoskip transaction writing to a single key/value pair makes an average of 19 syscalls (2 &lt;code&gt;fcntl&lt;/code&gt;, 2 &lt;code&gt;fstat&lt;/code&gt;, 6 &lt;code&gt;lseek&lt;/code&gt;, 4 &lt;code&gt;write&lt;/code&gt;, 2 &lt;code&gt;writev&lt;/code&gt;, and 3 &lt;code&gt;fdatasync&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;Even with the more complex locking, the equivalent twom change makes half as many (4 &lt;code&gt;fcntl&lt;/code&gt;, 2 &lt;code&gt;fstat&lt;/code&gt;, and 3 &lt;code&gt;msync&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;But even better - a transaction which updates multiple key/value pairs adds an additional average of 6 syscalls on twoskip (3 &lt;code&gt;lseek&lt;/code&gt;, 2 &lt;code&gt;write&lt;/code&gt;, 1 &lt;code&gt;writev&lt;/code&gt;) per record, while twom has no additional syscalls per transaction, no matter how many changes are made. This is particularly obvious during a repack, where the initial transaction on the new file contains every record in the database!&lt;/p&gt;&lt;h3 id=&quot;pre-emptive-allocation&quot; tabindex=&quot;-1&quot;&gt;Pre-emptive allocation&lt;/h3&gt;&lt;p&gt;Every time twom needs to make the file bigger, it extends it by 25% and then fills in the empty space. This seems a good tradeoff between low numbers of truncate and mmap operations, while not making files insanely large. It doesn’t actually use that space on most filesystems, the file remains sparse until writes fill the space.&lt;/p&gt;&lt;p&gt;This was by far the biggest performance increase (and the one we tested) - we had noticed with twoskip that a large amount of CPU was going on &lt;code&gt;munmap&lt;/code&gt; and &lt;code&gt;mmap&lt;/code&gt; calls, as with every new record the file became longer than the mapped space, and had to be re-mapped before the next write.&lt;/p&gt;&lt;p&gt;Twom decouples the file size from the committed size, which can leave junk from aborted transactions on the tail of the file, but the header length never gets updated until the third msync, so nothing ever reads that junk, and it gets overwritten as new commits come in.&lt;/p&gt;&lt;h3 id=&quot;just-straight-posix&quot; tabindex=&quot;-1&quot;&gt;Just straight POSIX&lt;/h3&gt;&lt;p&gt;The twom library is written to be standalone. It doesn’t use any of the supporting libraries from Cyrus, opting to do fcntl locking and mmap manipulation directly. This allows it to keep a list of active transactions with their own mmaps so pointers remain valid; and for many other optimisations. Many things from twoskip were tightened up and simplified by not relying on other libraries.&lt;/p&gt;&lt;p&gt;This makes twom easily portable, and since it’s written from whole cloth (and I’m the only author on twoskip as well) I was able to put it under the &lt;a href=&quot;https://creativecommons.org/publicdomain/zero/1.0/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CC0&lt;/a&gt; public domain license; though obviously if you want to use xxHash you need to follow its &lt;a href=&quot;https://github.com/Cyan4973/xxHash/blob/dev/LICENSE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2-Clause BSD license&lt;/a&gt; as well.&lt;/p&gt;&lt;p&gt;Twom databases are a single file, containing an ordered key-value list. It’s transactional, with single threaded exclusive writers and multiple parallel readers. Twom files can be accessed by multiple un-related programs concurrently, so long as they all obey the locking rules.&lt;/p&gt;&lt;p&gt;I plan to lift the code out into its own repository at some point, and test it against all the other usual suspects in the key-value database space. The &lt;code&gt;lib/cyrusdb_twom.c&lt;/code&gt; file is just a lightweight wrapper around the twom functions to convert cyrusdb semantics to twom style, and to convert error codes on the way back.&lt;/p&gt;&lt;h2 id=&quot;ok-what-does-it-look-like&quot; tabindex=&quot;-1&quot;&gt;OK what does it look like?&lt;/h2&gt;&lt;p&gt;Cyrus comes with a tool ‘cyr_dbtool’ which can be used to interact with any database formats, so here’s some interactions with a new DB, setting and deleting some records, and showing prefix iterators and dump output.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom set a b
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom show
a       b
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom dump
UUID: uuid=fe720dec-4525-4e5d-a3c4-da665f3b0b40
FNAME: fname=/tmp/test.db
CHECKSUM ENGINE: XXH64
HEADER: v=1 g=1 fl=10000000 num=(1/1) sz=(00000000/000001B0/00000170) ml=1
00000060 DUMMY kl=0 dl=0 lvl=31 ()
        00000170 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000
00000170 ADD kl=1 dl=1 lvl=1 (a)
        00000000 00000000
00000198 COMMIT start=00000170
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom set xxa hello
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom set xxb world
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom show xx
xxa     hello
xxb     world
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom delete xxa
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom set xxa hi
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom dump
UUID: uuid=fe720dec-4525-4e5d-a3c4-da665f3b0b40
FNAME: fname=/tmp/test.db
CHECKSUM ENGINE: XXH64
HEADER: v=1 g=1 fl=10000000 num=(3/5) sz=(00000048/000002C8/00000170) ml=3
00000060 DUMMY kl=0 dl=0 lvl=31 ()
        00000170 00000000
        000001F8 000001F8 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000
00000170 ADD kl=1 dl=1 lvl=1 (a)
        00000280 00000250
00000198 COMMIT start=00000170
000001B0 ADD kl=3 dl=5 lvl=1 (xxa)
        000001F8 00000000
000001E0 COMMIT start=000001B0
000001F8 ADD kl=3 dl=5 lvl=3 (xxb)
        00000000 00000000
        00000000 00000000
00000238 COMMIT start=000001F8
00000250 DELETE ancestor=000001B0
00000268 COMMIT start=00000250
00000280 REPLACE kl=3 dl=2 lvl=1 (xxa)
        00000250 &amp;lt;-
        00000000 000001F8
000002B0 COMMIT start=00000280
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s a version 1 file, generation 1 (never been repacked), flags just means “using XXH64”, 3 commits, 5 records, some interesting sizes (last repack, current size, estimated repack size) - with the highest skiplevel of 3.&lt;/p&gt;&lt;p&gt;Let’s repack it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom repack
brong@elg:~/src/cyrus-imapd$ /usr/cyrus/bin/cyr_dbtool -n /tmp/test.db twom dump
UUID: uuid=fe720dec-4525-4e5d-a3c4-da665f3b0b40
FNAME: fname=/tmp/test.db
CHECKSUM ENGINE: XXH64
HEADER: v=1 g=2 fl=10000000 num=(3/1) sz=(00000000/00000218/00000200) ml=2
00000060 DUMMY kl=0 dl=0 lvl=31 ()
        00000170 00000000
        00000198 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        00000000 00000000 00000000 00000000 00000000 00000000
00000170 ADD kl=1 dl=1 lvl=1 (a)
        00000198 00000000
00000198 ADD kl=3 dl=2 lvl=2 (xxa)
        000001C8 00000000
        000001C8
000001C8 ADD kl=3 dl=5 lvl=2 (xxb)
        00000000 00000000
        00000000
00000200 COMMIT start=00000170
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All the tombstones removed, and the file is back in order. Any new writes will stitch themselves into the various linked lists by updating the back pointers. Finally, let’s look at the raw file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;00000000  a1 02 8b 0d 74 77 6f 6d  66 69 6c 65 00 00 00 00  |....twomfile....|
00000010  fe 72 0d ec 45 25 4e 5d  a3 c4 da 66 5f 3b 0b 40  |.r..E%N]...f_;.@|
00000020  01 00 00 00 00 00 00 10  02 00 00 00 00 00 00 00  |................|
00000030  03 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 00  00 02 00 00 00 00 00 00  |................|
00000050  18 02 00 00 00 00 00 00  02 00 00 00 8a 57 92 ee  |.............W..|
00000060  01 1f 00 00 00 00 00 00  70 01 00 00 00 00 00 00  |........p.......|
00000070  00 00 00 00 00 00 00 00  98 01 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000160  00 00 00 00 00 00 00 00  6c d0 f8 56 00 00 00 00  |........l..V....|
00000170  02 01 01 00 01 00 00 00  98 01 00 00 00 00 00 00  |................|
00000180  00 00 00 00 00 00 00 00  d8 de f5 3d 84 a4 92 c8  |...........=....|
00000190  61 00 62 00 00 00 00 00  02 02 03 00 02 00 00 00  |a.b.............|
000001a0  c8 01 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001b0  c8 01 00 00 00 00 00 00  05 8c 71 d7 70 cf ff c3  |..........q.p...|
000001c0  78 78 61 00 68 69 00 00  02 02 03 00 05 00 00 00  |xxa.hi..........|
000001d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001e0  00 00 00 00 00 00 00 00  19 cf b0 45 a7 69 06 9d  |...........E.i..|
000001f0  78 78 62 00 77 6f 72 6c  64 00 00 00 00 00 00 00  |xxb.world.......|
00000200  07 00 00 00 00 00 00 00  70 01 00 00 00 00 00 00  |........p.......|
00000210  1d 44 bd 3d 00 00 00 00  00 00 00 00 00 00 00 00  |.D.=............|
00000220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00004000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There’s not much fat in there! There is probably 50 bytes of overhead per record once you factor in trailing nulls on key and value, 8 bytes of header, 8 bytes of checksums, an average of 3 64-bit pointers, and padding out to an 8 byte boundary.&lt;/p&gt;&lt;h2 id=&quot;fastmail-is-running-twom&quot; tabindex=&quot;-1&quot;&gt;Fastmail is running twom&lt;/h2&gt;&lt;p&gt;Since February 12, 2025, all Fastmail email servers have been using the twom backend in all the places they used to use twoskip. The switch was done in three phases over two days.&lt;/p&gt;&lt;p&gt;I’m very happy to have removed one of the places in which Fastmail could fail to live up to its name! No more pauses for database repacks.&lt;/p&gt;&lt;p&gt;I’m hoping that in some future release of Cyrus, the twom backend will be the default - but mostly, once twom was finished I was just glad to take a break from reading hexdumps and do something else for a while!&lt;/p&gt;</content>
        </entry><entry>
            <title>A revamped Fastmail inbox</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/revamped-inbox/' />
			<id>https://www.fastmail.com/blog/revamped-inbox/</id>
			<updated>2025-04-28T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;Today we’re launching a new look for your inbox, in both our webmail and mobile apps. Familiar but refined, the message list is now cleaner and more focused. Avatars help quickly identify who’s in the conversation, and one-click hover actions let you easily archive or delete.&lt;/p&gt;&lt;p&gt;Make it yours with easier customisation, with live updates as you try out different options. Don’t have a Fastmail account yet? Here’s what you’re missing:&lt;/p&gt;&lt;p&gt;&lt;video class=&quot;aspect-video relative rounded-lg shadow-card z-50&quot; autoplay loop playsinline muted controls width=&quot;1280&quot; height=&quot;720&quot;&gt; &lt;source src=&quot;/assets/blog/2025-04-28-revamped-inbox/NewLook.webm&quot; type=&quot;video/webm; codecs=vp9,vorbis&quot;&gt; &lt;source src=&quot;/assets/blog/2025-04-28-revamped-inbox/NewLook.mp4&quot; type=&quot;video/mp4&quot;&gt; &lt;/video&gt;&lt;/p&gt;&lt;h2 id=&quot;and-more&quot; tabindex=&quot;-1&quot;&gt;And more&lt;/h2&gt;&lt;p&gt;We’ve also focused on fixing the niggling small things. We’ve improved support for using a mouse or pencil in our iPad app. Made it easier to quickly create a contact or event while reading a message. Added an option to stop messages from being automatically marked as read when you open them. We’ve even moved the “report phishing” action to be next to “report spam”. Finally.&lt;/p&gt;&lt;p&gt;On mobile you can also now choose what apps are shown in the mobile navigation bar at the bottom (or turn them all off to hide it). Missing your quick hover actions? Configure up to four custom swipe actions to fly through your mail on the go. To select a message, just press and hold, or swipe with two fingers.&lt;/p&gt;&lt;p&gt;We hope you enjoy the new look. Thanks to all our beta testers for the feedback they sent in. If you too want to get a sneak peek of what’s coming next at Fastmail, just log in at &lt;a href=&quot;https://betaapp.fastmail.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;beta.fastmail.com&lt;/a&gt;. Coming soon … &lt;a href=&quot;/blog/offline-in-beta/&quot;&gt;full offline support&lt;/a&gt;!&lt;/p&gt;</content>
        </entry><entry>
            <title>Happy International Email Day</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/happy-international-email-day/' />
			<id>https://www.fastmail.com/blog/happy-international-email-day/</id>
			<updated>2025-04-22T14:00:00Z</updated><author>
				<name>Bek Fraser</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;Today, on International Email Day, I’m taking a moment to appreciate the communication tool many of us use and rely on every day. At Fastmail, email isn’t just our business – it’s our passion. The team and I believe email deserves recognition as one of the digital age’s most powerful, flexible, and lasting technologies.&lt;/p&gt;&lt;h2 id=&quot;email-still-going-strong&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Email: Still going strong&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Let’s take a moment to appreciate some history! From the first ever electronic messages in the early 1970s to the modern multimedia communications we send today, email has come a long way. While other technologies have come and gone, email has adapted, thrived, and become a necessary tool for most of us in our day-to-day lives.&lt;/p&gt;&lt;h2 id=&quot;why-is-this-the-case&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Why is this the case?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;I feel it is because email just works. It puts the mailbox owner in control of their communications. Email is based on open standards that anyone can implement. This generates competition and innovation to keep building better email products. Closed platforms will inevitably try and lock you in and then maximise your attention and their advertising revenue. You don’t need to be on the same service to communicate with someone else – email connects everyone globally every day.&lt;/p&gt;&lt;h2 id=&quot;what-makes-email-special&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;What makes email special?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;In a world of passing trends, shortened attention spans, and conflicting priorities, an email received or sent feels refreshingly respectful; things that make email special for me:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;I control the pace: Email waits for me to respond, when I am ready, not the other way around.&lt;/li&gt; &lt;li&gt;It breaks down barriers in communication: It makes me feel connected. Many people have access to an email address.&lt;/li&gt; &lt;li&gt;I can refer to it when I need to: Important information stays accessible and searchable (for when I forget!). Bron, our CEO, wrote about this &lt;a href=&quot;https://www.fastmail.com/blog/email-is-your-electronic-memory/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;here&lt;/a&gt; in 2018.&lt;/li&gt; &lt;li&gt;It’s private: My messages aren’t fodder for algorithms (at least not at Fastmail!).&lt;/li&gt; &lt;li&gt;It’s professional: For work and essential matters, nothing beats email!&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;how-here-at-fastmail-we-are-making-email-better&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;How here at Fastmail, we are making email better&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;At Fastmail, we don’t just provide email; as a team, we’re actively improving it for both our customers and the world. Here’s how:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;We created JMAP:&lt;/strong&gt; Our team was the driving force behind &lt;a href=&quot;https://jmap.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JMAP&lt;/a&gt;, an open API standard for modern mail clients and applications to manage email faster. JMAP significantly improves on existing outdated protocols to provide a better email experience across all devices.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;We prioritise privacy:&lt;/strong&gt; No tracking pixels, no ad targeting, no reading your messages to sell you things.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;We contribute to open source:&lt;/strong&gt; The team regularly contributes code to projects like &lt;a href=&quot;https://www.cyrusimap.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cyrus IMAP&lt;/a&gt;, and have made core parts of our own technology &lt;a href=&quot;https://github.com/fastmail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;available for others to build upon&lt;/a&gt;. We believe that open standards and cooperation make email better for everyone.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;We build thoughtful features:&lt;/strong&gt; From masked email addresses that protect your privacy (one of my favourite features) to powerful filtering rules that organise your inbox, the team are constantly adding tools that put you in control.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Your email is your business, not ours.&lt;/strong&gt;&lt;/p&gt;&lt;h2 id=&quot;what-s-next-for-email&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;What’s next for email?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Email isn’t standing still, and neither are we.&lt;/p&gt;&lt;p&gt;Right now we’re in the final stages of beta testing &lt;a href=&quot;https://www.fastmail.com/blog/offline-in-beta/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;full offline support for the Fastmail app&lt;/a&gt;, as well as refinements to our look and feel. In partnership with others in the industry, we’re also improving the email ecosystem with work on everything from &lt;a href=&quot;https://www.ietf.org/archive/id/draft-ietf-mailmaint-oauth-public-01.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;making it easier to securely set up your email client&lt;/a&gt; to &lt;a href=&quot;https://www.ietf.org/archive/id/draft-gondwana-dkim2-motivation-00.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;improving message authentication to protect your inbox&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Email thrives because it continues to evolve while staying true to its core purpose: connecting people reliably. As an industry leader, I know all of the team at Fastmail are committed to ensuring email remains the versatile, powerful tool we all rely on every day.&lt;/p&gt;&lt;h2 id=&quot;join-us-in-celebrating-email&quot; tabindex=&quot;-1&quot;&gt;&lt;strong&gt;Join us in celebrating email&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Whether you’re a power user with diligently organised folders or someone who simply appreciates sending a quick message to a friend, today is a good day to appreciate how email connects all of us to the people and information that matter.&lt;/p&gt;&lt;p&gt;I am proud to be part of a great team that delivers a service that respects privacy, enhances productivity, and helps all of us communicate on our own terms.&lt;/p&gt;&lt;p&gt;Happy International Email Day from myself and everyone else here at Fastmail!&lt;/p&gt;</content>
        </entry><entry>
            <title>Not OK, Cupid</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/not-ok-cupid/' />
			<id>https://www.fastmail.com/blog/not-ok-cupid/</id>
			<updated>2025-03-21T00:00:01Z</updated><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;I don’t usually like to call out the bad behaviour of specific companies, but the egregious mis-design and lack of acknowledging it justify this case.&lt;/p&gt;&lt;h2 id=&quot;welcome-to-ok-cupid&quot; tabindex=&quot;-1&quot;&gt;Welcome to OkCupid&lt;/h2&gt;&lt;p&gt;A couple of weeks ago, I started seeing many “Welcome to OkCupid” emails, both on my personal address and a couple of related addresses, but also to multiple Fastmail official contact addresses — legal, partnerships, press, etc. Specifically, this list included &lt;code&gt;trash@brong.net&lt;/code&gt; — an address that has never been used to send or receive email and appears in precisely one place — &lt;a href=&quot;https://www.fastmail.com/blog/a-tangled-path-of-workarounds/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;an article on our blog&lt;/a&gt;! It seems quite clear that somebody scraped our website and used the addresses to sign up. I’m aware of at least 10 addresses, but there are likely others that either go to someone else or addresses that no longer exist.&lt;/p&gt;&lt;p&gt;It didn’t stop there, though. I’ve been getting tons of “someone likes you”, “you have an intro,” and even an “IMPORTANT: We removed your photo on OkCupid.” email saying that inappropriate content was posted to “our” account!&lt;/p&gt;&lt;h2 id=&quot;the-real-world-consequences-of-poor-email-validation&quot; tabindex=&quot;-1&quot;&gt;The real-world consequences of poor email validation&lt;/h2&gt;&lt;p&gt;This isn’t just an inconvenience — it has real security implications. Websites that fail to properly validate email ownership can be exploited for malicious purposes. Attackers can use unverified sign-ups to flood inboxes, making it easier to hide critical emails among the noise — something we’ve discussed our own experience of in our post on &lt;a href=&quot;https://www.fastmail.com/blog/when-two-factor-authentication-is-not-enough/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2FA vulnerabilities&lt;/a&gt;. There are established &lt;a href=&quot;https://www.m3aawg.org/sites/default/files/document/M3AAWG_Senders_BCP_Ver3-2015-02.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;best practices&lt;/a&gt; (PDF) for handling email sign-ups responsibly, practices that OkCupid is failing to follow.&lt;/p&gt;&lt;h2 id=&quot;no-way-out&quot; tabindex=&quot;-1&quot;&gt;No way out&lt;/h2&gt;&lt;p&gt;When I tried to unsubscribe using the one-click unsubscribe button in one of the emails, I was met with an error: “Something went wrong, please try again later.”&lt;/p&gt;&lt;p&gt;Curious, I tried to recover a password on one of these accounts (the one with my personal email address) and successfully changed the password. Then, I was asked to confirm my login with a message sent to the number associated with the account. A number I didn’t know. A number that wasn’t mentioned on that page, so I still don’t know anything about it — not even which country it was from.&lt;/p&gt;&lt;p&gt;This raises further security concerns; the attacker could have also caused random recovery numbers to be texted to another poor victim’s phone. Alternatively, they could confirm that my email address is actively monitored, increasing its value for further attacks. Either way, what I couldn’t do was actually close the account.&lt;/p&gt;&lt;h2 id=&quot;whack-a-mole&quot; tabindex=&quot;-1&quot;&gt;Whack-a-mole&lt;/h2&gt;&lt;p&gt;So, I contacted OkCupid’s support. Here’s what they said:&lt;/p&gt;&lt;p&gt;&lt;em&gt;I’ve removed the user from the site and banned the email address to prevent any new accounts from being created. That should resolve the issue, but if you encounter anything like this again in the future, please don’t hesitate to reach out, and we’ll address it right away.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;So, I need to contact support manually for each new email address. This is neither scalable nor acceptable; people don’t have this amount of time.&lt;/p&gt;&lt;p&gt;Furthermore, my email address is now on another random blocklist somewhere on the internet, where I have no control and no way to unblock it. I don’t anticipate wanting to use OkCupid’s service, but if I did in the future, I would have to go through another dance to get the address unlocked again — or more likely, treat that particular email address as soiled and create another one.&lt;/p&gt;&lt;h2 id=&quot;not-ok&quot; tabindex=&quot;-1&quot;&gt;Not OK&lt;/h2&gt;&lt;p&gt;So I say, not OK, OkCupid. Not OK.&lt;/p&gt;&lt;p&gt;The usefulness of email depends on responsible behaviour from all service providers. Companies that engage in shady or outright inappropriate practices make the internet worse for everyone.&lt;/p&gt;&lt;p&gt;OkCupid’s failure to implement even the &lt;a href=&quot;https://en.wikipedia.org/wiki/Opt-in_email#Confirmed_opt-in_(COI)/double_opt-in_(DOI)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;simplest form&lt;/a&gt; of email validation is unacceptable. Until they address these issues properly (not through the support response provided here), they remain part of the problem, not the solution.&lt;/p&gt;&lt;h2 id=&quot;could-we-have-avoided-this&quot; tabindex=&quot;-1&quot;&gt;Could we have avoided this?&lt;/h2&gt;&lt;p&gt;In this case, we published those addresses online. There’s always a risk of receiving spam when you do that, one could even reasonably say “we were asking for it”. We expected spam. If you want to reduce your risk of being spammed, it helps to not publish your email address on the public web!&lt;/p&gt;&lt;p&gt;What we we didn’t was expect a relatively reputable service being used to facilitate us being spammed.&lt;/p&gt;&lt;p&gt;One great protection is using different address for each different organisation you deal with — that way if your address leaks (or they sell it), you know where the breach happened, and you can more easily block just the problem messages.&lt;/p&gt;&lt;p&gt;Fastmail’s masked email feature is a great way to implement this strategy. Masked emails are designed, particularly when integrated with a password manager, to make it very easy to create new addresses, and track where they are expected to be used.&lt;/p&gt;&lt;p&gt;Being a good internet citizen is one of &lt;a href=&quot;https://www.fastmail.com/company/values/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Fastmail’s core values&lt;/a&gt;. We require verification for sending identities, ensuring that only legitimate users can send from an address they claim they own. This is the level of responsibility every email provider should uphold, and we applaud the others who also do.&lt;/p&gt;</content>
        </entry><entry>
            <title>The evolution of the advanced fee scam</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/the-evolution-of-the-advanced-fee-scam/' />
			<id>https://www.fastmail.com/blog/the-evolution-of-the-advanced-fee-scam/</id>
			<updated>2025-02-14T05:00:00Z</updated><author>
				<name>Aric Archebelle-Smith</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;As one of Fastmail’s customer support agents, part of my job is making sure that our customers are well-informed about rising trends in fraud so that they can be sure to steer clear of them. While our customers tend to be tech-savvy enough to spot the average scam email from a mile away, online scammers grow increasingly more sophisticated every year.&lt;/p&gt;&lt;p&gt;I recently attended the 62nd General Meeting of the &lt;a href=&quot;https://www.m3aawg.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Messaging, Malware, Mobile Anti-Abuse Working Group (M3AAWG)&lt;/a&gt; in Toronto. There, I spoke with others in the email and anti-abuse industry about the increase in advanced fee scams they’d observed in the years since the onset of the COVID-19 pandemic.&lt;/p&gt;&lt;p&gt;Advanced fee scams are not a new type of scam, but scammers have begun running a much more sophisticated version of this old-school scam. One that can convince even those who know to be cautious when navigating the internet.&lt;/p&gt;&lt;p&gt;Historically, advanced fee scams involved scammers promising the victim some sort of too-good-to-be-true opportunity or reward. The only catch is that the the victim has to pay a fee before they can receive the promised reward or opportunity. Generally, the scammer claims this fee is just to cover processing fees, background checks, training materials, or some other reasonable sounding expense. They assure the victim that they’ll be reimbursed for this expense down the road. Once the victim pays the fee, the scammer goes silent and the victim realizes that they’ve been conned.&lt;/p&gt;&lt;p&gt;Until recently, advanced fee scams were your garden variety “Nigerian prince” scam that savvy internet users quickly learned to avoid. Someone would offer the victim a large payoff if the victim could just cover the relatively small wire transfer or bank processing fees. For most email users, this type of con was easy to detect and most people knew to watch out for them.&lt;/p&gt;&lt;p&gt;Advanced fee scams have recently evolved to masquerade as hiring and work-from-home opportunities, targeting people who are looking for work in an already highly competitive job market. The scammers will pose as hiring managers or recruiters, and will even go so far as to reach out to victims over legitimate hiring websites, such as LinkedIn.&lt;/p&gt;&lt;p&gt;The victim is led to believe that they are being considered for a job or internship opportunity, but they’ll be asked to pay a fee as part of the hiring process. In some cases, the victim is given a link to the company’s preferred online vendor, where they are told to purchase the items they’ll need for the job. The scammer tells the victim that they’ll be reimbursed for these purchases later. However, the link takes the victim to a fake webstore where the payment is taken, but no goods are ever sent. At this point, the scammer stops responding to the victim.&lt;/p&gt;&lt;p&gt;More frequently, the scammers ask the victim to pay a small fee to cover some other aspect of the hiring process. Generally, the scammer will claim this is an application fee or something similar. Of course, the scammer stops responding to the victim’s messages as soon as they receive the payment.&lt;/p&gt;&lt;p&gt;In some cases, scammers will even conduct actual phone or video interviews with the victim as part of the phony hiring process. There’s no way to know how this data is being used by the attackers without insider knowledge.&lt;/p&gt;&lt;p&gt;This combination of fraudulent hiring and advanced fee scams allows attackers to collect both money and personally identifying information from vulnerable populations.&lt;/p&gt;&lt;p&gt;I recommend the following precautions to avoid becoming the victim of one of these scams:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Confirm the legitimacy of any jobs you are interested in applying for by verifying that the position is listed on the company’s website.&lt;/li&gt; &lt;li&gt;Make sure that any emails you receive from a hiring manager are actually coming from the company’s domain or from the domain of a legitimate staffing agency. Double check that there are no typos or &lt;a href=&quot;https://itservices.wp.st-andrews.ac.uk/2024/03/12/identifying-fraudsters-using-cyrillic-characters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;look-alike characters&lt;/a&gt; in the domain.&lt;/li&gt; &lt;li&gt;Take the same precautions with any URLs that are shared with you via email or on hiring sites. Scammers can set up convincing look-alike websites, but you can check the URL to verify that you are being directed to the company’s legitimate website.&lt;/li&gt; &lt;li&gt;Even if a message appears to be sent from a company’s actual domain, there’s a chance that the message could be spoofed, meaning the scammer forged the email’s “From” address to make it look like it came from a certain person or company. Chances are that these messages would get flagged as spam, but it’s still a good idea to confirm that a message hasn’t been spoofed by checking the headers of the message. Fastmail makes it easy to view the full headers of a message. Simply click the &lt;strong&gt;Actions&lt;/strong&gt; drop down and select &lt;strong&gt;Show raw message&lt;/strong&gt; to see the full headers of the message and verify that the message passed &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/1500000280461-Sender-authentication&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;sender authentication checks&lt;/a&gt;. If you’re not familiar with how to read email headers, you can always reach out to Fastmail’s friendly and knowledgeable &lt;a href=&quot;https://support.fastmail.com/support/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;support team&lt;/a&gt; to help confirm a message’s legitimacy.&lt;/li&gt; &lt;li&gt;If a job opportunity seems too good to be true, or you’re told that you’ve been accepted for a position almost immediately with little to no interview process, chances are the hiring manager or recruiter that you’re talking with is actually a scammer.&lt;/li&gt; &lt;li&gt;If at any point in the interview process the recruiter asks to stop communicating via email and asks you to contact them on Telegram, WhatsApp, or any other end-to-end encrypted communication platform, they are almost certainly trying to scam you.&lt;/li&gt; &lt;li&gt;If the company requires payment from you for a job opportunity, we ultimately recommend that you do not proceed. It’s extraordinarily rare for a legitimate company to require payment from you for a job opportunity.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;As these scams become more pervasive, it’s crucial that those on the job market educate themselves on the potential scams that are out there. Knowing how to recognize and avoid these fraudulent job listings can ensure you don’t waste your time, lose money, or divulge your personal data to scammers.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 24: Twenty five years of Fastmail</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/twenty-five-years-of-fastmail/' />
			<id>https://www.fastmail.com/blog/twenty-five-years-of-fastmail/</id>
			<updated>2024-12-24T00:00:01Z</updated><author>
				<name>Rob Mueller</name>
			</author><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the twenty-fourth and final post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/ten-years-of-jmap/&quot;&gt;Dec 23: Ten years of JMAP&lt;/a&gt;. Thanks for reading, see you again next year.&lt;/p&gt;&lt;p&gt;As we conclude this year’s Advent posts, we are reflecting back over 25 years! Fastmail was founded in 1999, to fill a gap which existed at the time — in the space between ISPs, slow and ad-riddled free email services, and clunky, bloated Enterprise systems, there was no professional email service for a small business or sophisticated email user.&lt;/p&gt;&lt;p&gt;So we built one! Fastmail: a slick, professional, web-based email service.&lt;/p&gt;&lt;p&gt;In the 25 years since, we have seen many changes in the email landscape and the world around us. The advent of Gmail and conversations as a standard email model. The rise of encryption focused services like Protonmail (with the &lt;a href=&quot;https://www.fastmail.com/features/security/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;pros and cons&lt;/a&gt; of storing email as opaque, unsearchable blobs). Highly opinionated “reinventions” of email like Hey. And of course the multiple premature announcements that email was dead, to be replaced by the latest new craze.&lt;/p&gt;&lt;p&gt;We were purchased by Opera Software in 2010, but after some changes in Opera’s strategic direction, thankfully a handful of the staff managed to buy the company back in 2013. We then purchased another email service Pobox in 2015, who had been running an email service even longer than us. We have recently finished merging their product into our system; who knew it was going to take so long to integrate everything!&lt;/p&gt;&lt;p&gt;Through all of this we’ve been grateful to have such loyal customers. We regularly hear from customers how much they appreciate the Fastmail service. Our fantastic customer support. The continuous, thoughtful, and well designed improvements to our product. The high performance and reliability of our service. The ongoing commitment to integrity, privacy, and longevity.&lt;/p&gt;&lt;p&gt;The result is that we have a greater than 90% annual renewal rate, and an ongoing stream of new customers from the word of mouth recommendations of existing customers. We have and continue to grow every year in a sustainable and deliberate way. We’re insanely grateful for this. We get to focus on making email better for our customers, to work with and build cool technology — with really smart colleagues. We can solve complex problems, build well-designed solutions, and improve email standards without having to always hustle for the next sale.&lt;/p&gt;&lt;p&gt;It’s an enviable position to be in. Email remains the largest open federated communication network on the internet. Not controlled by a single company. Not part of any walled garden that can change at any time. Through open standards, email allows you to choose the best provider and to move your email where is best for you. As we said in our first post of this series, we will continue to “Make email better”, for our customers and for everyone.&lt;/p&gt;&lt;p&gt;We love our work, and the customers who trust us with their email and make this all possible. So cheers to you, Fastmail’s customers. We get to make email better, the product you use and the ecosystem we all operate in, while having fun and working on interesting problems with great people.&lt;/p&gt;&lt;p&gt;Here’s to another 25 years.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 23: Ten years of JMAP</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/ten-years-of-jmap/' />
			<id>https://www.fastmail.com/blog/ten-years-of-jmap/</id>
			<updated>2024-12-23T00:00:01Z</updated><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the twenty-third post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/why-we-use-our-own-hardware/&quot;&gt;Dec 22: Why we use our own hardware at Fastmail&lt;/a&gt;. The final post is &lt;a href=&quot;/blog/twenty-five-years-of-fastmail/&quot;&gt;Dec 24: Twenty five years of Fastmail&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Exactly 10 years ago, we &lt;a href=&quot;/blog/jmap-a-better-way-to-email/&quot;&gt;announced JMAP on our blog&lt;/a&gt;, along with a &lt;a href=&quot;https://youtu.be/8qCSK-aGSBA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;video by baby-faced Bron and Neil&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;JMAP: A better way to email. We knew it would be a long road, but we’re really glad we did it and created an open standard rather than staying with our own custom protocol.&lt;/p&gt;&lt;h2 id=&quot;some-moments-along-the-way&quot; tabindex=&quot;-1&quot;&gt;Some moments along the way&lt;/h2&gt;&lt;p&gt;We started by workshopping the idea around the industry. I did a &lt;a href=&quot;https://www.youtube.com/watch?v=yyXlUR1hbr4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;lightning talk at OSCON in 2014&lt;/a&gt;, our first attempt to find developers who could give us feedback on our design. By far the best find was &lt;a href=&quot;https://rjbs.cloud/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Ricardo Signes&lt;/a&gt;, Pobox developer, who I met at a bar on the last day! This led to us acquiring the product and (our main goal) acqui-hiring Rik, who is now one of the company owners, as well as a JMAP enthusiast!&lt;/p&gt;&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/oscon-bron-rik-91PNJqGmRJ-375.webp 375w, /assets/images/oscon-bron-rik-91PNJqGmRJ-750.webp 750w, /assets/images/oscon-bron-rik-91PNJqGmRJ-1154.webp 1154w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;Bron and Rik at OSCON in 2014&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/oscon-bron-rik-91PNJqGmRJ-375.png&quot; width=&quot;1154&quot; height=&quot;893&quot; srcset=&quot;/assets/images/oscon-bron-rik-91PNJqGmRJ-375.png 375w, /assets/images/oscon-bron-rik-91PNJqGmRJ-750.png 750w, /assets/images/oscon-bron-rik-91PNJqGmRJ-1154.png 1154w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/p&gt;&lt;p&gt;Neil and I attended &lt;a href=&quot;https://web.archive.org/web/20150908015219/http://inboxlove.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Inbox Love&lt;/a&gt; in the Bay Area in 2014 as well. This gave us a chance to meet some of our technical peers in the big companies, relationships which we have continued to foster over the years. This hasn’t led to everyone dropping everything and implementing our protocols, but it has led to some collaborative design and ongoing conversations, and I believe its has prevented a proliferation of other protocols since people point to JMAP instead of inventing a new thing themselves. We also were told “go to the IETF”, but the IETF seemed big and scary and we didn’t know how, so that took a while.&lt;/p&gt;&lt;p&gt;Instead, we joined &lt;a href=&quot;https://www.calconnect.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CalConnect&lt;/a&gt; and started working on Calendar formats and standards, while promoting JMAP more generally. Eventually we made more contacts in the IETF, and finally in 2017 went to our first IETF meeting in Chicago. At this point, the &lt;a href=&quot;https://datatracker.ietf.org/wg/jmap/history/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JMAP working group&lt;/a&gt; was born.&lt;/p&gt;&lt;p&gt;In the crucible of the IETF, we made major changes. The authentication was removed. Method names were split into &lt;code&gt;Object/action&lt;/code&gt; and a ton of smaller changes were made. The &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8620.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Core&lt;/a&gt; and &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8621.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mail&lt;/a&gt; JMAP specifications were published in 2019, and then we got to work on the rest of the stack.&lt;/p&gt;&lt;p&gt;JMAP &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9610.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Contacts&lt;/a&gt; was published just last week, and JMAP &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-jmap-calendars/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Calendars&lt;/a&gt; is very close to being published. I’m also keen to add &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-jmap-filenode/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Filenode&lt;/a&gt; support, but we want to get more experience with other filesystem providers before we standardize that (it’s currently based very closely on Fastmail’s own custom Node objects for our filestorage feature).&lt;/p&gt;&lt;h2 id=&quot;what-s-next&quot; tabindex=&quot;-1&quot;&gt;What’s next&lt;/h2&gt;&lt;p&gt;We created JMAP because we could see that without it, the email world was going to become more insular, with the only modern standards for email access being proprietary. With Calendars and Contacts, we’re bringing the same easy-to-use JSON objects under a single protocol.&lt;/p&gt;&lt;p&gt;We started the &lt;a href=&quot;https://makebetter.email/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Make Better Email&lt;/a&gt; conference last year, focused on improving the authentication workflow and also promoting JMAP usage. It’s a very small, invite-only conference where we do deep technical design work on improving interoperability and discoverability between clients and services. It was in Philadelphia last year, London this year, and we expect to be in Philadelphia again next year — likely in mid November after &lt;a href=&quot;https://www.ietf.org/meeting/124/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IETF 124&lt;/a&gt; so we don’t cross over Halloween. If you think you’d be a useful addition to the meeting, pop us an email via the link at the bottom of the site.&lt;/p&gt;&lt;p&gt;Some work products of the previous conferences have been:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-jenkins-oauth-public/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;An OAuth profile for open-protocol clients&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-jenkins-emailpush/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;A profile for JMAP push&lt;/a&gt; allowing you get some of the benefits of JMAP’s push capability without having to do a full JMAP implementation&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-mailmaint-autoconfig/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;A specification for auto-discovery of configuration information&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Over the past year some of us have also been working in the server-to-server space with &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-gondwana-dkim2-motivation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;an idea&lt;/a&gt; that may wind up replacing or enhancing DKIM.&lt;/p&gt;&lt;p&gt;And finally, next year we will be investing a lot more effort into making the &lt;a href=&quot;https://www.cyrusimap.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cyrus IMAP&lt;/a&gt; server not just a reference implementation for JMAP, but much easier to both develop and run.&lt;/p&gt;&lt;p&gt;In 10 years time, I hope to post about how Cyrus and JMAP have taken over the world, but I’ll also happily settle for them having both improved Fastmail’s product immeasurably, having plenty of happy customers, and continuing to help make email better for everybody through our work.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 22: Why we use our own hardware at Fastmail</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/why-we-use-our-own-hardware/' />
			<id>https://www.fastmail.com/blog/why-we-use-our-own-hardware/</id>
			<updated>2024-12-22T00:00:01Z</updated><author>
				<name>Rob Mueller</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the twenty-second post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/fastmail-in-a-box/&quot;&gt;Dec 21: Fastmail in a box&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/ten-years-of-jmap/&quot;&gt;Dec 23: Ten years of JMAP&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;why-we-use-our-own-hardware&quot; tabindex=&quot;-1&quot;&gt;Why we use our own hardware&lt;/h2&gt;&lt;p&gt;There has recently been talk of &lt;a href=&quot;https://www.google.com/search?q=Cloud+Repatriation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cloud repatriation&lt;/a&gt; where companies are moving from the cloud to on premises, with some particularly &lt;a href=&quot;https://basecamp.com/cloud-exit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;noisy examples&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Fastmail has a long history of using our &lt;a href=&quot;https://www.fastmail.com/blog/standalone-mail-servers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;own&lt;/a&gt; &lt;a href=&quot;https://www.fastmail.com/blog/getting-the-most-out-of-hardware/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hardware&lt;/a&gt;. We have over two decades of experience running and optimising our systems to use our own &lt;a href=&quot;https://en.wikipedia.org/wiki/Bare-metal_server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;bare metal&lt;/a&gt; servers efficiently.&lt;/p&gt;&lt;p&gt;We get way better cost optimisation compared to moving everything to the cloud because:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;We understand our short, medium and long term usage patterns, requirements and growth very well. This means we can plan our hardware purchases ahead of time and don’t need the fast dynamic scaling that cloud provides.&lt;/li&gt; &lt;li&gt;We have in house operations experience installing, configuring and running our own hardware and networking. These are skills we’ve had to maintain and grow in house since we’ve been doing this for 25 years.&lt;/li&gt; &lt;li&gt;We are able to use our hardware for long periods. We find our hardware can provide useful life for anywhere from 5-10 years depending on what it is and when in the global technology cycle it was bought, meaning we can amortise and depreciate the cost of any hardware over many years.&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;Yes, that means we have to do more ourselves, including planning, choosing, buying, installing, etc, but the tradeoff for us has and we believe continues to be significantly worth it.&lt;/p&gt;&lt;h2 id=&quot;hardware-over-the-years&quot; tabindex=&quot;-1&quot;&gt;Hardware over the years&lt;/h2&gt;&lt;p&gt;Of course over the 25 years we’ve been running Fastmail we’ve been through a number of hardware changes. For many years, our IMAP server storage platform was a combination of &lt;a href=&quot;https://www.urbandictionary.com/define.php?term=Spinning%20Rust&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;spinning rust&lt;/a&gt; drives and &lt;a href=&quot;https://www.areca.com.tw/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ARECA RAID controllers&lt;/a&gt;. We tended to use faster 15k RPM SAS drives in &lt;a href=&quot;https://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RAID1&lt;/a&gt; for our hot meta data, and 7.2k RPM SATA drives in &lt;a href=&quot;https://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RAID6&lt;/a&gt; for our main email blob data.&lt;/p&gt;&lt;p&gt;In fact it was slightly more complex than this. Email blobs were written to the fast RAID1 SAS volumes on delivery, but then a separate archiving process would move them to the SATA volumes at low server activity times. Support for all of this had been added into &lt;a href=&quot;https://www.cyrusimap.org&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cyrus&lt;/a&gt; and our tooling over the years in the form of separate “meta”, “data” and &lt;a href=&quot;https://www.cyrusimap.org/3.8/imap/reference/admin/locations/archive-partitions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;“archive”&lt;/a&gt; partitions.&lt;/p&gt;&lt;h2 id=&quot;moving-to-nv-me-ssds&quot; tabindex=&quot;-1&quot;&gt;Moving to NVMe SSDs&lt;/h2&gt;&lt;p&gt;A few years ago however we made our biggest hardware upgrade ever. We moved all our email servers to a new &lt;a href=&quot;https://www.supermicro.com/en/aplus/system/2u/2113/as-2113s-wn24rt.cfm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2U AMD platform&lt;/a&gt; with pure &lt;a href=&quot;https://www.solidigm.com/products/data-center.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NVMe SSDs&lt;/a&gt;. The density increase (24 x 2.5&amp;quot; NVMe drives vs 12 x 3.5&amp;quot; SATA drives per 2U) and performance increase was enormous. We found that these new servers performed even better than our initial expectations.&lt;/p&gt;&lt;p&gt;At the time we upgraded however NVMe RAID controllers weren’t widely available. So we had to decide on how to handle redundancy. We considered a RAID-less setup using raw SSDs drives on each machine with synchronous application level replication to other machines, but the software changes required were going to be more complex than expected.&lt;/p&gt;&lt;p&gt;We were looking at using classic Linux &lt;a href=&quot;https://en.wikipedia.org/wiki/Mdadm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mdadm RAID&lt;/a&gt;, but the &lt;a href=&quot;https://en.wikipedia.org/wiki/RAID#Atomicity&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;write hole&lt;/a&gt; was a concern and the &lt;a href=&quot;https://docs.kernel.org/driver-api/md/raid5-cache.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;write cache&lt;/a&gt; didn’t seem well tested at the time.&lt;/p&gt;&lt;p&gt;We decided to have a look at &lt;a href=&quot;https://arstechnica.com/information-technology/2020/05/zfs-101-understanding-zfs-storage-and-performance/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ZFS&lt;/a&gt; and at least test it out.&lt;/p&gt;&lt;p&gt;Despite some of the cyrus on disk database structures being fairly hostile to &lt;a href=&quot;https://en.wikipedia.org/wiki/ZFS#Copy-on-write_transactional_model&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ZFS Copy-on-write&lt;/a&gt; semantics, they were still incredibly fast at all the IO we threw at them. And there were some other wins as well.&lt;/p&gt;&lt;h2 id=&quot;zfs-compression-and-tuning&quot; tabindex=&quot;-1&quot;&gt;ZFS compression and tuning&lt;/h2&gt;&lt;p&gt;When we rolled out ZFS for our email servers we also enabled &lt;a href=&quot;https://freebsdfoundation.org/wp-content/uploads/2021/05/Zstandard-Compression-in-OpenZFS.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;transparent Zstandard compression&lt;/a&gt;. This has worked very well for us, saving about 40% space on all our email data.&lt;/p&gt;&lt;p&gt;We’ve also recently done some additional calculations to see if we could tune some of the parameters better. We sampled 1 million emails at random and calculated how many blocks would be required to store those emails uncompressed, and then with &lt;a href=&quot;https://klarasystems.com/articles/tuning-recordsize-in-openzfs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ZFS record sizes&lt;/a&gt; of 32k, 128k or 512k and zstd-3 or zstd-9 compression options. Although ZFS &lt;a href=&quot;https://en.wikipedia.org/wiki/ZFS#ZFS&#39;s_approach:_RAID-Z_and_mirroring&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RAIDz2&lt;/a&gt; seems conceptually similar to classic RAID6, the way it &lt;a href=&quot;https://ibug.io/blog/2023/10/zfs-block-size/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;actually stores blocks of data&lt;/a&gt; is quite different and so you have to take into account volblocksize, how files are split into logical recordsize blocks, and number of drives when doing calculations.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;               Emails: 1,026,000
           Raw blocks: 34,140,142
 32k &amp;amp; zstd-3, blocks: 23,004,447 = 32.6% saving
 32k &amp;amp; zstd-9, blocks: 22,721,178 = 33.4% saving
128k &amp;amp; zstd-3, blocks: 20,512,759 = 39.9% saving
128k &amp;amp; zstd-9, blocks: 20,261,445 = 40.7% saving
512k &amp;amp; zstd-3, blocks: 19,917,418 = 41.7% saving
512k &amp;amp; zstd-9, blocks: 19,666,970 = 42.4% saving
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This showed that the defaults of 128k record size and zstd-3 were already pretty good. Moving to a record size of 512k improved compression over 128k by a bit over 4%. Given all meta data is cached separately, this seems a worthwhile improvement with no significant downside. Moving to zstd-9 improved compression over zstd-3 by about 2%. Given the CPU cost of compression at zstd-9 is about 4x zstd-3, even though emails are immutable and tend to be kept for a long time, we’ve decided not to implement this change.&lt;/p&gt;&lt;h2 id=&quot;zfs-encryption&quot; tabindex=&quot;-1&quot;&gt;ZFS encryption&lt;/h2&gt;&lt;p&gt;We always enable &lt;a href=&quot;https://en.wikipedia.org/wiki/Data_at_rest#Encryption&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;encryption at rest&lt;/a&gt; on all of our drives. This was usually done with &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LUKS&lt;/a&gt;. But with ZFS this was &lt;a href=&quot;https://arstechnica.com/gadgets/2021/06/a-quick-start-guide-to-openzfs-native-encryption/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;built in&lt;/a&gt;. Again, this reduces overall system complexity.&lt;/p&gt;&lt;h2 id=&quot;going-all-in-on-zfs&quot; tabindex=&quot;-1&quot;&gt;Going all in on ZFS&lt;/h2&gt;&lt;p&gt;So after the success of our initial testing, we decided to go all in on ZFS for all our large data storage needs. We’ve now been using ZFS for all our email servers for over 3 years and have been very happy with it. We’ve also moved over all our database, log and backup servers to using ZFS on NVMe SSDs as well with equally good results.&lt;/p&gt;&lt;h2 id=&quot;ssd-lifetimes&quot; tabindex=&quot;-1&quot;&gt;SSD lifetimes&lt;/h2&gt;&lt;p&gt;The flash memory in SSDs has a finite life and &lt;a href=&quot;https://en.wikipedia.org/wiki/Flash_memory#Write_endurance&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;finite number of times it can be written to&lt;/a&gt;. SSDs employ increasingly complex &lt;a href=&quot;https://en.wikipedia.org/wiki/Wear_leveling&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;wear levelling&lt;/a&gt; algorithms to spread out writes and increase drive lifetime. You’ll often see the quoted endurance of an enterprise SSD as either an absolute figure of “Lifetime Writes”/“Total bytes written” like 65 PBW (petabytes written) or a relative per-day figure of “Drive writes per day” like 0.3, which you can convert to lifetime figure by multiplying by the drive size and the drive expected lifetime which is often assumed to be 5 years.&lt;/p&gt;&lt;p&gt;Although we could calculate IO rates for existing &lt;a href=&quot;https://en.wikipedia.org/wiki/Hard_disk_drive&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HDD&lt;/a&gt; systems, we were making a significant number of changes moving to the new systems. Switching to a COW filesystem like ZFS, removing the special casing meta/data/archive partitions, and the massive latency reduction and performance improvements mean that things that might have taken extra time previously and ended up batching IO together, are now so fast it actually causes additional separated IO actions.&lt;/p&gt;&lt;p&gt;So one big unknown question we had was how fast would the SSDs wear in our actual production environment? After several years, we now have some clear data. From one server at random but this is fairly consistent across the fleet of our oldest servers:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# smartctl -a /dev/nvme14
...
Percentage Used:                    4%
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this rate, we’ll replace these drives due to increased drive sizes, or entirely new physical drive formats (such &lt;a href=&quot;https://www.snia.org/forums/cmsi/knowledge/formfactors&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;E3.S&lt;/a&gt; which appears to finally be gaining traction) long before they get close to their rated write capacity.&lt;/p&gt;&lt;p&gt;We’ve also anecdotally found SSDs just to be much more reliable compared to HDDs for us. Although we’ve only ever used &lt;a href=&quot;https://www.micron.com/products/storage/ssd/data-center-ssd/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;datacenter&lt;/a&gt; &lt;a href=&quot;https://www.solidigm.com/products/data-center.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;class&lt;/a&gt; SSDs and &lt;a href=&quot;https://www.seagate.com/www-content/datasheets/pdfs/exos-7-e8-data-sheet-DS1957-1-1709US-en_US.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HDDs&lt;/a&gt; failures and replacements every few weeks were a regular occurrence on the old fleet of servers. Over the last 3+ years, we’ve only seen a couple of SSD failures in total across the entire upgraded fleet of servers. This is easily less than one tenth the failure rate we used to have with HDDs.&lt;/p&gt;&lt;h2 id=&quot;storage-cost-calculation&quot; tabindex=&quot;-1&quot;&gt;Storage cost calculation&lt;/h2&gt;&lt;p&gt;After converting all our email storage to NVMe SSDs, we were recently looking at our data backup solution. At the time it consisted of a number of older 2U servers with 12 x 3.5&amp;quot; SATA drive bays and we decided to do some cost calculations on:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;Move to cloud storage.&lt;/li&gt; &lt;li&gt;Upgrade the HD drives in existing servers.&lt;/li&gt; &lt;li&gt;Upgrade to SSD NVMe machines.&lt;/li&gt; &lt;/ol&gt;&lt;h3 id=&quot;1-cloud-storage&quot; tabindex=&quot;-1&quot;&gt;1. Cloud storage:&lt;/h3&gt;&lt;p&gt;Looking at various providers, the per TB per month price, and then a yearly price for 1000Tb/1Pb (prices as at Dec 2024)&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/s3/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Amazon S3&lt;/a&gt; - $21 -&amp;gt; $252,000/y&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/r2/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cloudflare R2&lt;/a&gt; - $15 -&amp;gt; $180,000/y&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://wasabi.com/pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Wasabi&lt;/a&gt; - $6.99 -&amp;gt; $83,880/y&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://www.backblaze.com/cloud-storage/pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Backblaze B2&lt;/a&gt; - $6 -&amp;gt; $72,000/y&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/s3/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Amazon S3 Glacier Instant Retrieval&lt;/a&gt; - $4 -&amp;gt; $48,000/y&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/s3/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Amazon S3 Glacier Deep Archive (12 hour retrieval time)&lt;/a&gt; - $0.99 -&amp;gt; $11,880/y&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Some of these (e.g. Amazon) have potentially significant bandwidth fees as well.&lt;/p&gt;&lt;p&gt;It’s interesting seeing the spread of prices here. Some also have a bunch of weird edge cases as well. e.g. “The S3 Glacier Flexible Retrieval and S3 Glacier Deep Archive storage classes require an additional 32 KB of data per object”. Given the large retrieval time and extra overhead per-object, you’d probably want to store small incremental backups in regular S3, then when you’ve gathered enough, build a biggish object to push down to Glacier. This adds implementation complexity.&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;em&gt;Pros&lt;/em&gt;: No limit to amount we store. Assuming we use S3 compatible API, can choose between multiple providers.&lt;/li&gt; &lt;li&gt;&lt;em&gt;Cons&lt;/em&gt;: Implementation cost of converting existing backup system that assumes local POSIX files to S3 style object API is uncertain and possibly significant. Lowest cost options require extra careful consideration around implementation details and special limitations. Ongoing monthly cost that will only increase as amount of data we store increases. Uncertain if prices will go down or not, or even go up. Possible significant bandwidth costs depending on provider.&lt;/li&gt; &lt;/ul&gt;&lt;h3 id=&quot;2-upgrade-hdds&quot; tabindex=&quot;-1&quot;&gt;2. Upgrade HDDs&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.seagate.com/au/en/products/enterprise-drives/exos-x/x24/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Seagate Exos 24 HDs&lt;/a&gt; are 3.5&amp;quot; 24T HDDs. This would allow us to triple the storage on existing servers. Each HDD is about $500, so upgrading one 2U machine would be about $6,000 and have storage of 220T or so.&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;em&gt;Pros&lt;/em&gt;: Reuses existing hardware we already have. Upgrades can be done a machine at a time. Fairly low price&lt;/li&gt; &lt;li&gt;&lt;em&gt;Cons&lt;/em&gt;: Will existing units handle 24T drives? What’s the rebuild time on drive failure look like? It’s almost a day for 8T drives already, so possibly nearly a week for a failed 24T drive? Is there enough IO performance to handle daily backups at capacity?&lt;/li&gt; &lt;/ul&gt;&lt;h3 id=&quot;3-upgrade-to-new-hardware&quot; tabindex=&quot;-1&quot;&gt;3. Upgrade to new hardware&lt;/h3&gt;&lt;p&gt;As we know, SSDs are denser (2.5&amp;quot; -&amp;gt; 24 per 2U vs 3.5&amp;quot; -&amp;gt; 12 per 2U), more reliable, and now higher capacity - &lt;a href=&quot;https://www.solidigm.com/products/data-center/d5/p5336.html#form=U.2%2015mm&amp;amp;cap=61.44TB&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;up to 61T per 2.5&amp;quot; drive&lt;/a&gt;. A single 2U server with 24 x 61T drives with 2 x 12 RAIDz2 = 1220T. Each drive is &lt;a href=&quot;https://www.newegg.com/solidigm-61-44tb-d5-p5336/p/N82E16820318031&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;about $7k&lt;/a&gt; right now, prices fluctuate. So all up 24 x $7k = $168k + ~$20k server =~ $190k for &amp;gt; 1000T storage one-time cost.&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;em&gt;Pros&lt;/em&gt;: &lt;strong&gt;Much&lt;/strong&gt; higher sequential and random IO than HDDs will ever have. Price &amp;lt; 1 year of standard S3 storage. Internal to our WAN, no bandwidth costs and very low latency. No new development required, existing backup system will just work. Consolidate on single 2U platform for all storage (cyrus, db, backups) and SSD for all storage. Significant space and power savings over existing HDD based servers&lt;/li&gt; &lt;li&gt;&lt;em&gt;Cons&lt;/em&gt;: Greater up front cost. Still need to predict and buy more servers as backups grow.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;One thing you don’t see in this calculation is datacenter space, power, cooling, etc. The reason is that compared to the amortised yearly cost of a storage server like this, these are actually reasonably minimal these days, on the order of $3000/2U/year. Calculating person time is harder. We have a lot of home built automation systems that mean installing and running one more server has minimal marginal cost.&lt;/p&gt;&lt;h3 id=&quot;result&quot; tabindex=&quot;-1&quot;&gt;Result&lt;/h3&gt;&lt;p&gt;We ended up going with the the new 2U servers option:&lt;/p&gt;&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/nvme-imap-servers-AqR6yL3DlW-375.webp 375w, /assets/images/nvme-imap-servers-AqR6yL3DlW-750.webp 750w, /assets/images/nvme-imap-servers-AqR6yL3DlW-1500.webp 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;NVME IMAP Servers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/nvme-imap-servers-AqR6yL3DlW-375.png&quot; width=&quot;1500&quot; height=&quot;559&quot; srcset=&quot;/assets/images/nvme-imap-servers-AqR6yL3DlW-375.png 375w, /assets/images/nvme-imap-servers-AqR6yL3DlW-750.png 750w, /assets/images/nvme-imap-servers-AqR6yL3DlW-1500.png 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;The 2U AMD NVMe platform with ZFS is a platform we have experience with already&lt;/li&gt; &lt;li&gt;SSDs are much more reliable and much higher IO compared to HDDs&lt;/li&gt; &lt;li&gt;No uncertainty around super large HDDs, RAID controllers, rebuild times, shuffling data around, etc.&lt;/li&gt; &lt;li&gt;Significant space and power saving over existing HDD based servers&lt;/li&gt; &lt;li&gt;No new development required, can use existing backup system and code&lt;/li&gt; &lt;li&gt;Long expected hardware lifetime, controlled upfront cost, can depreciate hardware cost&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;So far this has worked out very well. The machines have bonded 25Gbps networks and when filling them from scratch we were able to saturate the network links streaming around 5Gbytes/second of data from our IMAP servers, compressing and writing it all down to a RAIDz2 zstd-3 compressed ZFS dataset.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Running your own hardware might not be for everyone and has distinct tradeoffs. But when you have the experience and the knowledge of how you expect to scale, the cost improvements can be significant.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 21: Fastmail in a box</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/fastmail-in-a-box/' />
			<id>https://www.fastmail.com/blog/fastmail-in-a-box/</id>
			<updated>2024-12-21T00:00:01Z</updated><author>
				<name>Andrew Davis</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the twenty-first post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/how-fastmail-uses-fastmail/&quot;&gt;Dec 20: How Fastmail uses Fastmail!&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/why-we-use-our-own-hardware/&quot;&gt;Dec 22: Why we use our own hardware at Fastmail&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;They say everybody has a testing environment. Some people are just lucky enough enough to have a separate environment for production. At Fastmail, every staff member can get their own isolated testing and development sandbox. We call this Fastmail-In-A-Box, or more commonly just “fminabox”.&lt;/p&gt;&lt;p&gt;Like many technologists, I learn most by fiddling with things, often breaking them along the way and putting them back together again. With fminabox, we give everyone their own world to break apart and put back together, risk free. This makes it an invaluable place for new hires to cut their teeth, and for existing staff to come up to speed in an area of Fastmail’s stack they haven’t worked on before.&lt;/p&gt;&lt;p&gt;Fminabox is a complete Fastmail deployment on a single host. This includes Cyrus for IMAP storage, Postfix for incoming and outgoing mail, MySQL for non-mail data, our JMAP web API, and all the frontend assets. It also runs the ancillary services we use to monitor Fastmail such as Prometheus, all managed by the same configuration and service management system we use in production. This allows for fast, iterative development with very little waiting time between making a change and seeing the effect, while eliminating most “it worked on my machine” bugs.&lt;/p&gt;&lt;p&gt;We use &lt;a href=&quot;https://www.packer.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hashicorp Packer&lt;/a&gt; to create fminabox, following the provisioning scripts we use in production as closely as possible. Who hasn’t made a change to a system where it works going from state N → N+1, but then discovered weeks later that it’s broken when bootstrapping from nothing? Each night we build a new image from scratch. This allows us to catch those types of failures, and to do so while the changes are still front-of-mind in the developers that made them.&lt;/p&gt;&lt;p&gt;Any staff member can tell our chatbot &lt;a href=&quot;https://github.com/fastmail/Synergy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Synergy&lt;/a&gt; to &lt;code&gt;box create&lt;/code&gt;, and Synergy will handle provisioning a VM in the cloud, set up DNS, and provide VPN configuration upon request. Fastmail continues to eschew the public cloud in favour of our own hardware to run our product, but it turns out the public cloud is really useful for creating test environments.&lt;/p&gt;&lt;p&gt;Fminabox is also a key part of our testing workflow. Fastmail has thousands of tests, from simple sanity compile checks to complex integration tests between systems. We use fminabox with our CI/CD pipeline so every change is automatically tested before it is merged. This was the ultimate progression from developers just running a handful of tests manually, to overnight runs, to fully integrated continuous testing.&lt;/p&gt;&lt;p&gt;As new needs arise, we continue to evolve the infrastructure. A few years ago I was making an improvement to our tooling that balances users between machines in our &lt;a href=&quot;https://www.fastmail.com/blog/building-a-backup-system-for-cyrus/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cyrus backup system&lt;/a&gt;. At the time, fminabox only had a single target that all users were backed up to, so my first step was to add support for multiple backup targets. Only then did I feel comfortable that I could properly test any changes to the tooling.&lt;/p&gt;&lt;p&gt;I’m not the only user, so I asked some other Fastmail staff members “what’s your favourite feature of fminabox?”, and here’s what they had to say.&lt;/p&gt;&lt;blockquote&gt; &lt;p&gt;Fastmail is able to send via externally authenticated submission via OAuth, but Fastmail is also an OAuth provider and provides authenticated SMTP submission via OAuth. We were able to update our test suite to do full end-to-end OAuth authentication with ourself, send an email back to ourselves, and see that this entire path works.&lt;/p&gt; &lt;/blockquote&gt;&lt;p&gt;—Rob Mueller, CTO&lt;/p&gt;&lt;blockquote&gt; &lt;p&gt;The best thing about fminabox is that it’s cheap and disposable. If I mess it up, I throw it away and make a new one and act like nothing happened. (The previous solution took hours to create a new box.)&lt;/p&gt; &lt;/blockquote&gt;&lt;p&gt;—Ricardo Signes, Head of Special Projects&lt;/p&gt;&lt;blockquote&gt; &lt;p&gt;Although it only takes 5 minutes to setup, inaboxes provide a fully contained sandbox that includes all of our code ready to test. Minutes to build, seconds to tear down.&lt;/p&gt; &lt;/blockquote&gt;&lt;p&gt;—Marcus Love, System Engineer&lt;/p&gt;&lt;p&gt;And that’s the story of fminabox. It isn’t perfect but it’s pretty damn good and it helps enable my colleagues to get their work done.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 20: How Fastmail uses Fastmail!</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/how-fastmail-uses-fastmail/' />
			<id>https://www.fastmail.com/blog/how-fastmail-uses-fastmail/</id>
			<updated>2024-12-20T00:00:01Z</updated><author>
				<name>Anju Manohar</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the twentieth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/offline-mail-storage/&quot;&gt;Dec 19: Building offline: mail storage&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/fastmail-in-a-box/&quot;&gt;Dec 21: Fastmail in a box&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;At Fastmail, our features are designed to make email management seamless and efficient. But how do our own staff use these tools in their daily lives? While all the features are incredibly helpful, there will always be some personal favorites for one.&lt;/p&gt;&lt;p&gt;We asked team members to share their favorite Fastmail features and how they’ve customized them to fit their workflows. As support staff, we’re familiar with every feature, but each of us uses them in unique ways—and you might just discover a hidden gem in this article by seeing how we put them to work. And here’s what they had to say.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Vysakh: Mastering Inbox Zero and Organization&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;“I enjoy maintaining an Inbox Zero approach, so I organize my emails into folders for various services.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Vysakh takes organization to the next level with &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/1500000280301-Setting-up-and-using-folders&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Folders&lt;/strong&gt;&lt;/a&gt; and &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/1500000278122-Mail-rules&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Rules&lt;/strong&gt;&lt;/a&gt;. He creates folders for specific categories like “Bank” (with subfolders for each bank) and “Purchases” (with subfolders for each website he purchases from like Amazon and Flipkart).&lt;/p&gt;&lt;p&gt;He also raves about &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060591213-Searching-your-mail#:~:text=Saved%20searches,for%20the%20same%20thing%20later.&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Saved Searches&lt;/strong&gt;&lt;/a&gt;, calling them an &lt;em&gt;“underrated feature”&lt;/em&gt;:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;A saved search for &lt;strong&gt;Unread Emails&lt;/strong&gt; allows him to see all unread messages across folders.&lt;/li&gt; &lt;li&gt;Another for &lt;strong&gt;Emails Delivered Today&lt;/strong&gt; is perfect for quickly finding new emails, even if they’re in Spam or Trash.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;For self-organization, Vysakh uses &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060591053-Plus-addressing-and-subdomain-addressing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Plus Addressing&lt;/strong&gt;&lt;/a&gt; to save important documents by emailing them to himself with a folder-specific alias like &lt;code&gt;username+Docs@fastmail.tld&lt;/code&gt;. He adds, &lt;em&gt;“Fastmail supports searching inside attachments, so finding them later is super easy.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Vysakh also highlights the efficiency of &lt;strong&gt;Keyboard Shortcuts&lt;/strong&gt; for email navigation and &lt;strong&gt;Customizable Notification Actions&lt;/strong&gt; in the Android app, which let him manage emails without opening the app.&lt;/p&gt;&lt;p&gt;I must say, he’s truly a pro-user of all our power features!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Merlin: Timing and Efficiency Made Easy&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Merlin’s favorite is &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/4686576659727-Scheduled-Send&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Schedule Send&lt;/strong&gt;&lt;/a&gt;, which allows drafting emails at their convenience and sending them at the perfect time. I couldn’t agree more—it’s like having a personal assistant keeping your inbox in check!&lt;/p&gt;&lt;p&gt;She goes on to say, &lt;em&gt;“My second favorite is Snooze—it lets me come back to emails when I have time to act on them.”&lt;/em&gt; Smart, right? Fastmail features truly help you work smarter, not harder.&lt;/p&gt;&lt;p&gt;Merlin also loves &lt;strong&gt;Mail rules&lt;/strong&gt; to keep her emails organized effortlessly and appreciates their simplicity, saying, &lt;em&gt;&amp;quot;It is a cinch to use even for beginners.”&lt;/em&gt;&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Maya: A Domain for Every Interest&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Maya’s love for email personalization shines through her extensive use of &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360058753394-Custom-domains-with-Fastmail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Domains&lt;/strong&gt;&lt;/a&gt; and &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060591073-How-to-set-up-aliases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Aliases&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;&lt;em&gt;“I have a whole bunch of domains just for fun—some professional, some for hobbies like photo essays and writing.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Her go-to feature is &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/4406536368911-Masked-Email&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Masked Email&lt;/strong&gt;&lt;/a&gt; combined with a custom domain. Whenever she signs up for a service, she creates a unique masked address, assigning each one to a folder. This setup lets her instantly identify breaches, unsubscribe from unwanted notifications, and organize her inbox for easy prioritization.&lt;/p&gt;&lt;p&gt;Maya also uses:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Pins&lt;/strong&gt; to flag important emails in folders.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Snooze&lt;/strong&gt; for emails she wants to revisit later without cluttering her inbox.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Tamper-Proof Retention&lt;/strong&gt;, ensuring emails she’s deleted can still be recovered if needed.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;She admits with a laugh, &lt;em&gt;“It’s probably not something a non-business user like me should need, but it’s handy!”&lt;/em&gt;&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Thu: Seamless Sharing and Catchall Convenience&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Thu finds &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/1500000277942-Catch-all-wildcard-aliases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Catchall aliases&lt;/strong&gt;&lt;/a&gt; and &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060590733-Sharing-mail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Mail sharing&lt;/strong&gt;&lt;/a&gt; to be game-changers. &lt;em&gt;“The catchall is super handy for creating new email addresses anytime I want”.&lt;/em&gt; She’s even set up a Fastmail account for her partner with shared aliases, mail, and calendars. Excitedly, she adds, &lt;em&gt;“The sharing function works very well with integration to Apple devices and Gmail account,”&lt;/em&gt; making collaboration a breeze. You can almost feel her joy through those words—don’t you? I bet you do!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Leslie: Staying Organized with Pins&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Leslie swears by the &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/1500000280341-Pin-important-messages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Pin&lt;/strong&gt;&lt;/a&gt; and &lt;strong&gt;Keep pinned on top&lt;/strong&gt; features to stay on top of essential emails. &lt;em&gt;“I use this on my staff account quite a lot because we get various emails with a bit of information in them. When I go through my emails every morning, I will pin the emails that I want to go back and read and also emails about support trends that are occurring. This allows me to quickly refer back to the emails since I’m seeing them at the top of the list in my inbox.”&lt;/em&gt; Leslie clearly has a knack for using this feature to keep important information right at her fingertips—guess I know who to turn to for a quick reference!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Yassar: Power User of Labels and Searches&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;For Yassar, &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360058753554-Setting-up-and-using-labels&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Labels&lt;/strong&gt;&lt;/a&gt; are indispensable. &lt;em&gt;“I love organizing everything in my mailbox, and I can’t imagine working without labels!”&lt;/em&gt; If you’re a fan of staying organized, you’re probably nodding in agreement right now.&lt;/p&gt;&lt;p&gt;But Yassar doesn’t stop there, his other go-to features include:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;strong&gt;Masked Emails&lt;/strong&gt; for secure online shopping.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Aliases&lt;/strong&gt; to classify emails and route them to specific folders such as &lt;code&gt;documents@mydomain.com&lt;/code&gt; or &lt;code&gt;gmail@mydomain.com&lt;/code&gt;.&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Saved Search&lt;/strong&gt; for quick filtering of emails without creating a label for everything.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Yassar’s approach combines security, organization, and speed, showing how powerful these tools can be for anyone looking to optimize their email experience!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Jed: Streamlining Inbox Management with Keyboard Shortcuts&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Jed relies heavily on &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360058753534-Keyboard-shortcuts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Keyboard Shortcuts&lt;/strong&gt;&lt;/a&gt; as one of his favorite Fastmail features:&lt;/p&gt;&lt;p&gt;&lt;em&gt;“As someone who lets a lot of email build up before I get around to sorting them, I like how quickly I can label, archive, and delete large numbers of messages using these shortcuts.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Wow! That’s brilliant—such a simple feature, yet so powerful! He’s turned what could be a daunting task into a seamless process. I’m definitely going to start using these more, and you should too!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Jess: Memos, Snoozing, and Smart Searching&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Jess praises &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/1500001969861-Conversations#:~:text=conversations%20help%20page.-,Memos,-Use%20memos%20to&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Memos&lt;/strong&gt;&lt;/a&gt; for saving key details from lengthy emails. With a grin, she shares, &lt;em&gt;“I just used it to note a coupon hidden in a long marketing email.”&lt;/em&gt; Who else can relate? I know I can—if you’re like me, always hunting for those elusive discount codes, Memos are here to save the day!&lt;/p&gt;&lt;p&gt;She’s also a big fan of the &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360058753634-Snoozing-mail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Snooze&lt;/strong&gt;&lt;/a&gt; feature, which she frequently uses for bills or shipping notifications that need attention later.&lt;/p&gt;&lt;p&gt;Lastly, Jess makes extensive use of &lt;strong&gt;Search&lt;/strong&gt; to streamline her inbox. The &lt;code&gt;is:unread&lt;/code&gt; search helps her capture all unread emails across labels, enabling her to quickly sort through them and get closer to Inbox Zero. That’s an amazing tip—you’ll get a unified inbox with all unread emails in one place!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;Aric: Sending at the Perfect Time&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Scheduled send&lt;/strong&gt; is one of the features Aric relies on the most, and it’s no surprise why, as he shares: “&lt;em&gt;Working with teams across the globe and working on a non-traditional schedule, I often find myself sending mail outside of people’s general working hours. Using scheduled send allows me to send mail so that my message is one of the first things the recipient sees when they check their inbox.&amp;quot;&lt;/em&gt; Impressive—that explains why his emails always seem to land at just the right time!&lt;/p&gt;&lt;hr&gt;&lt;p&gt;It’s been such a joy hearing from our team members about their favorite features. Inspired by them, how can I not share mine? I’m certainly not stepping back—here are my favorites.&lt;/p&gt;&lt;p&gt;One feature I find myself using a lot is the &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360058752514-Logged-in-sessions#:~:text=wish%20to%20end.-,View%20all%20logins%20in%20the%20last%204%20weeks,-At%20the%20bottom&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Login Log&lt;/strong&gt;&lt;/a&gt;, which is crucial for maintaining the privacy and security of my account. It lets me instantly review login activities whenever I spot something suspicious. The interface is simple and intuitive, showing logins from the Fastmail web UI, Fastmail app, and third-party apps, along with any failed login attempts. This feature gives me the confidence to monitor and act swiftly when needed—no more panic attacks when something looks off!&lt;/p&gt;&lt;p&gt;Next up is the &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060591213-Searching-your-mail#:~:text=searches%20with%20syntax.-,Advanced%20search,-If%20you%27re%20having&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Advanced Search&lt;/strong&gt;&lt;/a&gt;, a powerful yet user-friendly tool. For someone new to search tools and struggling to remember search syntax, this feature would be a real lifesaver. I use it all the time to refine my searches by selecting criteria from the available fields—whether it’s finding emails with specific attachment types, emails within a date range, or so much more. It’s fast, easy, and incredibly efficient! If you haven’t tried it yet, give it a go today—you’ll be amazed at how simple it is!&lt;/p&gt;&lt;p&gt;Then comes the &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/10351615144335-Passkeys&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Passkey&lt;/strong&gt;&lt;/a&gt; feature making my life easier and logging into my account across different devices both simpler and more secure.&lt;/p&gt;&lt;p&gt;Honestly, I love all the features! They’ve taken me—once a self-proclaimed email management avoider (guilty as charged!)—and turned me into a full-fledged Inbox enthusiast.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;A big thank you to my team for sharing their insights. It’s been great learning how everyone makes the most of these tools!&lt;/p&gt;&lt;p&gt;That wraps up an exciting dive into these amazing features and creative ways to use them—I hope you’ve found some inspiration to explore and make them your own!&lt;/p&gt;&lt;p&gt;Still not on Fastmail? Now’s the perfect time! Get your whole family on board with the Family plan and enjoy an &lt;a href=&quot;https://app.fastmail.com/signup/?discount=MjAsMTIsMTczNTY0NzQ0MCxhbWFub2hhciwsZjMyN2RlNGU4ZDVmZTM4MmVmNDM3YzI2YzRhNDFhY2EyYmZmMDA4MA&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;exclusive 20% off your first year&lt;/a&gt;—don’t miss out!&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 19: Building offline: mail storage</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/offline-mail-storage/' />
			<id>https://www.fastmail.com/blog/offline-mail-storage/</id>
			<updated>2024-12-19T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the nineteenth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/offline-sync/&quot;&gt;Dec 18: Building offline: syncing changes back to the server&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/how-fastmail-uses-fastmail/&quot;&gt;Dec 20: How Fastmail uses Fastmail!&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Yesterday, we looked at &lt;a href=&quot;/blog/offline-sync/&quot;&gt;how we store changes you make offline&lt;/a&gt; so we can accurately and efficiently sync them back to the server when you come online. Today, we’ll discuss why email is special, and what else we do to make this super fast, with support for full-text search offline.&lt;/p&gt;&lt;h2 id=&quot;why-offline-email-is-hard&quot; tabindex=&quot;-1&quot;&gt;Why offline email is hard&lt;/h2&gt;&lt;p&gt;As discussed earlier, because we use JMAP for all of our APIs, once we can implement generic offline support and have it work for everything (currently 56 data types and counting in our app!). However, mail is special. And the reason it’s special is purely the volume of data.&lt;/p&gt;&lt;p&gt;Most web apps severely underestimate how small their data is. In almost all cases, you will be more efficient and way faster to just suck it all into memory and do a linear filter pass whenever you need to query it. This is the difference between response as-you-type autocomplete and frustrating loading spinners on each key stroke. Even for users with 10,000 contacts this is only a few megabytes of data — perfectly cacheable.&lt;/p&gt;&lt;p&gt;Email is different though. We have users with millions of messages. Even with attachments handled separately in JMAP, each message could have hundreds of kilobytes of HTML as the body. But we expect opening a mailbox to load a listing pretty much instantly, and searches to be fast too. To make this work, we have to add a number of tricks to our standard offline approach.&lt;/p&gt;&lt;h2 id=&quot;splitting-the-data&quot; tabindex=&quot;-1&quot;&gt;Splitting the data&lt;/h2&gt;&lt;p&gt;The first trick is to split the data into two separate &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;object stores&lt;/a&gt;:&lt;/p&gt;&lt;ol&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;EmailMetadata&lt;/strong&gt;: this stores just the data that’s not parsed from the email content, like the id, thread id, keywords it has, and mailboxes it’s in. This keeps it small, but crucially also contains all the mutable data. This is treated like our standard JMAP object store for a data type.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;&lt;strong&gt;EmailContent&lt;/strong&gt;: this stores the email content; who it was sent from/to, the subject, body, list of attachments (but not the attachment data itself) etc.&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt;&lt;p&gt;Due to the volume of data, we can’t load everything at once. We page in the data in stages instead:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;We fetch a list of just the ids and create placeholder entries in the EmailMetadata object store.&lt;/li&gt; &lt;li&gt;We page in the metadata and basic headers (like to/from/subject) for all messages in batches. This gives us everything we need to show the listing for any folder or label.&lt;/li&gt; &lt;li&gt;We page in the body for pinned and recent messages, or everything if the user has selected this option in settings, again in batches.&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;This split is useful, because for most queries we can get away with just loading the metadata into memory, not the content. This is a big saving in time and memory when deserialising the objects from the underlying datastore.&lt;/p&gt;&lt;h2 id=&quot;efficient-mailbox-querying&quot; tabindex=&quot;-1&quot;&gt;Efficient mailbox querying&lt;/h2&gt;&lt;p&gt;A linear pass through all the metadata is surprisingly tractable, even for large mailboxes, however it’s slower than we want for common queries (like opening your inbox). This is where we introduce a couple of extra custom indexes — separate object stores we are careful to update in lock step with any changes to our data.&lt;/p&gt;&lt;p&gt;The first of these is &lt;strong&gt;EmailMailboxes&lt;/strong&gt;. This stores an entry for each addition or removal of a message from a folder/label, allowing us to both very efficiently compute the list of messages/conversations in a particular mailbox, and also calculate a delta update to the query when making changes.&lt;/p&gt;&lt;p&gt;The key for this object store is:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;MAILBOX_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;constant token&quot;&gt;REMOVED_MODSEQ&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;constant token&quot;&gt;ADDED_MODSEQ&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The values look like:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;EMAIL_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;constant token&quot;&gt;THREAD_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;constant token&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;constant token&quot;&gt;IS_UNREAD&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whenever a message is added to a mailbox, a new entry is created. &lt;code&gt;ADDED_MODSEQ&lt;/code&gt; is the current “updated” moseq of the message, and &lt;code&gt;REMOVED_MODSEQ&lt;/code&gt; is 0.&lt;/p&gt;&lt;p&gt;If the message is removed from the mailbox, the old entry is deleted, and a new one added with the same &lt;code&gt;ADDED_MODSEQ&lt;/code&gt;, but &lt;code&gt;REMOVED_MODSEQ&lt;/code&gt; set to the new “updated” modseq of the message.&lt;/p&gt;&lt;p&gt;From this, we can quickly get the list of current messages in a particular mailbox by doing a range query for entries with keys that start: &lt;code&gt;[MAILBOX_ID, 0]&lt;/code&gt;. The values include the date and thread id, allowing us to do the most common sort, and remove duplicates for the same thread id, without having to even fetch the metadata objects for the emails.&lt;/p&gt;&lt;h2 id=&quot;delta-query-updates&quot; tabindex=&quot;-1&quot;&gt;Delta query updates&lt;/h2&gt;&lt;p&gt;JMAP has a way for a client to &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ask for what’s changed in a query&lt;/a&gt;. This allows it to more efficiently update its local store and uses less bandwidth. With the EmailMailboxes index, we can also implement this. First we fetch the entries for the current messages as before, but then we also fetch the entries for messages that have been removed since our last state (this is a range query between &lt;code&gt;[MAILBOX_ID, sinceModSeq + 1]&lt;/code&gt; and &lt;code&gt;[MAILBOX_ID, max_int]&lt;/code&gt;). We sort these entries together according to the sort order the user has requested, normally date descending:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;mailboxRecords&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;parameter token&quot;&gt;a&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt;
        b&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt; a&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;EMAIL_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;&amp;lt;&lt;/span&gt; b&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;EMAIL_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; a&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;EMAIL_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;&gt;&lt;/span&gt; b&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;EMAIL_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;||&lt;/span&gt;
        a&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;ADDED_MODSEQ&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt; b&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;ADDED_MODSEQ&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then we can iterate through to calculate what has been added or removed from the query, like so. (“Exemplar” is our term for the email that’s representing a thread when &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8621.html#section-4.4.3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;the “collapseThreads” argument&lt;/a&gt; is true.)&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;keyword token&quot;&gt;let&lt;/span&gt; index &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; seenExemplar &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; collapseThreads &lt;span class=&quot;operator token&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;class-name token&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; seenOldExemplar &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; collapseThreads &lt;span class=&quot;operator token&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;class-name token&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;let&lt;/span&gt; uptoHasBeenFound &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;let&lt;/span&gt; total &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; added &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; removed &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;keyword token&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; record &lt;span class=&quot;keyword token&quot;&gt;of&lt;/span&gt; mailboxRecords&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; isDeleted &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;record&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;REMOVED_MODSEQ&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// Created and deleted after our previous state? Ignore.&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; isNew &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; record&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;ADDED_MODSEQ&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;&gt;&lt;/span&gt; sinceModSeq&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;isNew &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isDeleted&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;comment token&quot;&gt;// Is this message the current exemplar?&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;let&lt;/span&gt; isNewExemplar &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;let&lt;/span&gt; isOldExemplar &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; emailId &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; record&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;EMAIL_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; threadId &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; record&lt;span class=&quot;punctuation token&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;constant token&quot;&gt;THREAD_ID&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;isDeleted &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;collapseThreads &lt;span class=&quot;operator token&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;seenExemplar&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;threadId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        isNewExemplar &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        index &lt;span class=&quot;operator token&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        total &lt;span class=&quot;operator token&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;collapseThreads&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
            seenExemplar&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;threadId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// Was this message an old exemplar?&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// 1. Must not have been added to mailbox after the client&#39;s state&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// 2. Must have been removed from mailbox before the client&#39;s state&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// 3. Must not have already found the old exemplar.&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;isNew &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;collapseThreads &lt;span class=&quot;operator token&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;seenOldExemplar&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;threadId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        isOldExemplar &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;collapseThreads&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
            seenOldExemplar&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;threadId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;isOldExemplar &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;isNewExemplar&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        removed&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;emailId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;isOldExemplar &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isNewExemplar&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// If the message has been moved out and back in again&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// we&#39;ll have separate mailbox records for added/removed&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// so not detect it&#39;s both the old and new exemplar;&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// check for that here.&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; removedIndex &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; isMutableSort &lt;span class=&quot;operator token&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; removed&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;emailId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;removedIndex &lt;span class=&quot;operator token&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
            removed&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;removedIndex&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
            added&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
                index&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;literal-property property token&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; emailId&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;comment token&quot;&gt;// Special case for mutable sorts (based on isFlagged/isUnread)&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;isMutableSort &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isOldExemplar &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isNewExemplar&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// Has the isUnread/isFlagged status of the message/thread&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// (as appropriate) possibly changed since the client&#39;s state?&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// If so, we need to remove the exemplar from the client view&lt;/span&gt;
        &lt;span class=&quot;comment token&quot;&gt;// and add it back in at the correct position.&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; mayHaveMoved &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; collapseThreads
            &lt;span class=&quot;operator token&quot;&gt;?&lt;/span&gt; threadChanged&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;threadId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; emailChanged&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;emailId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;mayHaveMoved&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
            removed&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;emailId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
            added&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
                index&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;literal-property property token&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; emailId&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// If this is the last message the client cares about, we can stop&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// here and just return what we&#39;ve calculated so far. We already&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// know the total count for this message list as we keep it pre&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// calculated and cached in the Mailbox object.&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// However, if the sort is mutable we can&#39;t break early, as&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// messages may have moved from the region we care about to lower&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// down the list.&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;isMutableSort &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;!&lt;/span&gt;isNew &lt;span class=&quot;operator token&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; emailId &lt;span class=&quot;operator token&quot;&gt;===&lt;/span&gt; upToId&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        uptoHasBeenFound &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;keyword token&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;mail-search&quot; tabindex=&quot;-1&quot;&gt;Mail search&lt;/h2&gt;&lt;p&gt;Fastmail supports an &lt;a href=&quot;https://www.fastmail.help/hc/en-us/articles/360060591213-Searching-your-mail&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;extremely powerful set of search operators&lt;/a&gt;, allowing for &lt;a href=&quot;https://www.fastmail.com/features/search/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;fast, precise searching&lt;/a&gt;. We support almost all of it offline, with a few caveats discussed below.&lt;/p&gt;&lt;p&gt;To make full-text search work and be performant, we need to build another index. If you have hundreds of thousands of messages, it would be unusably slow to scan through all of them looking for a word, phrase or email address.&lt;/p&gt;&lt;p&gt;Our index is stored in another &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt; object store called &lt;strong&gt;EmailSearch&lt;/strong&gt;. The key for each entry is &lt;code&gt;[token, emailId]&lt;/code&gt;. The token is usually a word or other sequence of letters and numbers extracted from the email. We also have special token variations to represent a list-id or email addresses found in the headers. We create an entry in EmailSearch for each such token we find in the email. The value encodes where the token was found (e.g. in the &lt;code&gt;To&lt;/code&gt; header, or the message body), and the index(es) of the token so we can do &lt;a href=&quot;https://en.wikipedia.org/wiki/Phrase_search&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;phrase searches&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We decided to index the content on the device, rather than download the indexes from the server. This ensured our search index would be completely in sync with the cached messages you have on your device, and we could index and make searchable messages and memos you wrote while you were offline.&lt;/p&gt;&lt;p&gt;However, this does mean the offline search works a little differently to our server-based search, so may return slightly different results (although we think both will do a great job in most cases). In particular:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Our offline search doesn’t index any text inside attachments. When online you can search for content in attached PDFs, spreadsheets, and other documents.&lt;/li&gt; &lt;li&gt;Our offline search doesn’t do &lt;a href=&quot;https://en.wikipedia.org/wiki/Stemming&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;stemming&lt;/a&gt;. Stemming tries to reduce a word to its common root, so if you search in English for &lt;code&gt;bus&lt;/code&gt; you would also match emails containing &lt;code&gt;buses&lt;/code&gt;, but not &lt;code&gt;business&lt;/code&gt;. Stemming requires language analysis of the email content and custom stemming algorithms for each language, and we decided the extra complexity and code download size was not currently worth it for our offline search. Instead, our offline search does prefix matching by default, so &lt;code&gt;bus&lt;/code&gt; will still match &lt;code&gt;buses&lt;/code&gt; but also &lt;code&gt;business&lt;/code&gt;. Of course, if you wrap the term in quotes (like &lt;code&gt;&amp;quot;bus&amp;quot;&lt;/code&gt;) it will only look for exact matches, just like with server-based search.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;And of course, the search index will only contain messages you have downloaded for offline, which might not be everything in your account. We therefore try to do a search on the server first and only fallback to the local search if you are offline.&lt;/p&gt;&lt;h2 id=&quot;search-tokenisation&quot; tabindex=&quot;-1&quot;&gt;Search tokenisation&lt;/h2&gt;&lt;p&gt;To create our index we have to be able to extract the tokens from a sequence of text. We have users around the world, so we knew we had to handle multilingual text and scripts. In the end, we settled on a simple but effective tokenisation algorithm:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;We normalise the string into Unicode &lt;a href=&quot;https://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NFKD normal form&lt;/a&gt;. This will decompose diacritics to make it easy to strip them, and replace various variations of letters and numbers (such as typographic ligatures, or subscript numbers) with the baseline equivalent.&lt;/li&gt; &lt;li&gt;We divide the string into segments according to the &lt;a href=&quot;https://www.unicode.org/reports/tr29/#Word_Boundaries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Unicode text segmentation word boundary algorithm&lt;/a&gt;.&lt;/li&gt; &lt;li&gt;For each segment, we apply the full &lt;a href=&quot;https://www.unicode.org/Public/16.0.0/ucd/CaseFolding.txt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Unicode case folding substitutions&lt;/a&gt; (for example, this will replace uppercase letters with lowercase for Latin text), then we strip every code point that’s not categorised by Unicode as a number, letter, joining punctuation, or emoji.&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;If we have anything left, that’s our token. So to give an example, supposing we had the text:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;The café is über cheap — only $3.60 a ☕️!!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We would end up with the following tokens:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;the
cafe
is
uber
cheap
only
360
a
☕️
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;wrapping-it-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping it up&lt;/h2&gt;&lt;p&gt;We now have the indexes we need for fast, precise search. There’s still a lot of work involved in putting it all together though! When you search for something complex like &lt;code&gt;in:inbox from:@example.com (is:pinned OR &amp;quot;very important&amp;quot;)&lt;/code&gt;, we analyse the query to work out which indexes to use and efficiently combine them to compute the results. The speed will depend on how much mail you have—and how fast your device is!—but we believe it lives up to the Fastmail promise of great search everywhere.&lt;/p&gt;&lt;p&gt;There’s so much interesting tech behind our offline support, but for now I need to stop writing. If you’ve read all of this mini series on how we are making our app work offline: thank you, and I hope you found it interesting! Please give the beta a go, and let us know any feedback you might have. We’re excited to finish polishing this highly requested feature and we hope to ship it to everyone early in the new year.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 18: Building offline: syncing changes back to the server</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/offline-sync/' />
			<id>https://www.fastmail.com/blog/offline-sync/</id>
			<updated>2024-12-18T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the eighteenth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/offline-architecture/&quot;&gt;Dec 17: Building offline: general architecture&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/offline-mail-storage/&quot;&gt;Dec 19: Building offline: mail storage&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Yesterday, we looked at &lt;a href=&quot;/blog/offline-architecture/&quot;&gt;how our offline caching layer fits into our app&lt;/a&gt;, and the way it stores data to efficiently respond to JMAP requests. Today, we’ll dive into how it keeps track of changes the user makes while offline, so it can reconcile this with the server.&lt;/p&gt;&lt;h2 id=&quot;keeping-track-of-changes&quot; tabindex=&quot;-1&quot;&gt;Keeping track of changes&lt;/h2&gt;&lt;p&gt;When a client makes a change offline, we update our local cache and have to keep track of it so we can sync that change back to the server when we come online. There are two main approaches you could take:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;You keep a time-ordered log of every change, then replay the log against the server. One record may appear multiple times in the log if it has multiple modifications applied.&lt;/li&gt; &lt;li&gt;You keep a set of created/updated/destroyed records, along with the current server value. Each record can only appear once, in at most one of these categories. You calculate the difference between the server state and the current state to update the server.&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;The benefit of the first approach is it ensures we maintain any ordering dependencies. The benefit of the second approach is it’s more efficient in terms of both storage and synchronisation speed when there are multiple changes made to the same record.&lt;/p&gt;&lt;p&gt;The Fastmail offline cache uses a hybrid of these approaches to try to get the best of both worlds:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;A log stores (in order) the &lt;code&gt;[data type, account id, id]&lt;/code&gt; of any changes, along with what type of change this is (create/update/destroy).&lt;/li&gt; &lt;li&gt;The record itself stores the last known server state if it’s been updated, stored efficiently as a patch to get back to the server state from the updated state.&lt;/li&gt; &lt;li&gt;If the record is updated a second time, it: &lt;ul&gt; &lt;li&gt;stays in its current position in the log if not yet present on the server (this is a create); or&lt;/li&gt; &lt;li&gt;moves to the end of the log (remove the old entry and add a new one) if it already exists on the server (this is an update/destroy); or&lt;/li&gt; &lt;li&gt;is removed entirely from the log if the change reverted it back to the last-known server state.&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt;&lt;p&gt;If we’re updating a record that’s not yet been created on the server, we may have to do an update as well as a create, due to an ordering problem. For example, suppose you do the following:&lt;/p&gt;&lt;ol type=&quot;a&quot;&gt; &lt;li&gt;Create Mailbox X&lt;/li&gt; &lt;li&gt;Create Emails A &amp;amp; B in Mailbox X&lt;/li&gt; &lt;li&gt;Create Mailbox Y&lt;/li&gt; &lt;li&gt;Move Mailbox X to be a child of Y&lt;/li&gt; &lt;li&gt;Move Email A to be in Mailbox Y&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;You can’t move (a) later because (b) depends on it. You can’t move (d) earlier because it depends on (c). So if we update a record that’s not yet been created on the server, and we set a property that includes a local id (i.e., it references another object that’s been created locally but not yet synced to the server), we add it as a patch and apply it as an update later.&lt;/p&gt;&lt;p&gt;When loading data from the server, we do not need to look for an entry in the log of changes still to sync. We can just update the server state in the record. If the change is now inert, we’ll delete it from the log when we go to sync it.&lt;/p&gt;&lt;p&gt;For example, suppose we have a mailbox, id &lt;code&gt;1&lt;/code&gt;, with two messages in it, ids &lt;code&gt;A&lt;/code&gt; &amp;amp; &lt;code&gt;B&lt;/code&gt;, and the user does the following (contrived) actions:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Creates a new mailbox: &lt;code&gt;2&lt;/code&gt;&lt;/li&gt; &lt;li&gt;Creates a new child mailbox of that: &lt;code&gt;3&lt;/code&gt;&lt;/li&gt; &lt;li&gt;Moves A and B into mailbox &lt;code&gt;3&lt;/code&gt;&lt;/li&gt; &lt;li&gt;Marks B as read&lt;/li&gt; &lt;li&gt;Moves A back to its original mailbox.&lt;/li&gt; &lt;li&gt;Renames mailbox &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Our log will end up looking like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;    [Mailbox, &amp;quot;#2&amp;quot;, CREATE]
    [Mailbox, &amp;quot;#3&amp;quot;, CREATE]
    [Email, &amp;quot;B&amp;quot;, UPDATE]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because &lt;code&gt;A&lt;/code&gt; is back to its original state, we’ve eliminated it entirely from the log, and do not need to send anything to the server. Because &lt;code&gt;#2&lt;/code&gt; was a create in the log, we do not move it when we renamed it at the end, which is good because otherwise the other changes in the log would both fail as they depend on it. Despite making two changes to &lt;code&gt;B&lt;/code&gt;, we only have to send a single update to the server for it.&lt;/p&gt;&lt;h2 id=&quot;conflicts&quot; tabindex=&quot;-1&quot;&gt;Conflicts&lt;/h2&gt;&lt;p&gt;Suppose you have a shared contact, let’s call him Joe Bloggs. While offline you edit to add his phone number. Meanwhile, a colleague updates his email address. This means when your client comes back online and synchronises the changes, the object it is updating has already changed. This is called a conflict.&lt;/p&gt;&lt;p&gt;For the data types we have to handle, we believe automatic resolution (rather than presenting the conflict to the user and asking them to choose what should happen) is the right way to go. We follow these simple rules:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Last write wins.&lt;/li&gt; &lt;li&gt;All updates are patches.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;This means if the same object is updated by two different people, whichever client writes second will overwrite the data of the one that wrote first. (The first client will then sync this change back so you get a consistent state.) However, since all updates are patches, it will merge the changes unless they apply to the same property on the object. So in the case above, although there were two writes to the same contact, they were updating different properties. One user was updating the “&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9553.html#name-emails&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;emails&lt;/a&gt;”, the other the “&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9553.html#name-phones&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;phones&lt;/a&gt;”. So in this case, both changes would be preserved.&lt;/p&gt;&lt;h2 id=&quot;next-up-mail-storage&quot; tabindex=&quot;-1&quot;&gt;Next up, mail storage&lt;/h2&gt;&lt;p&gt;In this post we looked at how we store changes you make offline so we can accurately and efficiently sync them back to the server when you come online. Like our discussion of data storage yesterday, everything here applies generically to all data types.&lt;/p&gt;&lt;p&gt;Tomorrow, we’ll discuss &lt;a href=&quot;/blog/offline-mail-storage/&quot;&gt;why email is special&lt;/a&gt;, and what else we do to make this super fast in our offline store.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 17: Building offline: general architecture</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/offline-architecture/' />
			<id>https://www.fastmail.com/blog/offline-architecture/</id>
			<updated>2024-12-17T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the seventeenth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/offline-in-beta/&quot;&gt;Dec 16: Offline support now in public beta&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/offline-sync/&quot;&gt;Dec 18: Building offline: syncing changes back to the server&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Yesterday &lt;a href=&quot;/blog/offline-in-beta/&quot;&gt;we announced full offline support for Fastmail&lt;/a&gt; is now available in public beta, both in our app and on the web. Today, and over the next few days, I’ll dive into some of the technical aspects about how we’re making this work.&lt;/p&gt;&lt;p&gt;Making Fastmail work offline has been our most popular feature request for some time (ever since &lt;a href=&quot;/blog/more-swipe-options-on-mobile-let-you-work-faster/&quot;&gt;we added support for custom swipe actions&lt;/a&gt;, our previous top request!), and we wanted to make sure our support was done &lt;em&gt;right&lt;/em&gt;. It should just work, seamlessly, and as far as possible you should be able to do everything you can do online. Open your calendar and update an event, perhaps inviting someone using autocomplete from your contacts. Search your mail and triage it. Write a new note. Add a memo. We want it all to &lt;em&gt;just work&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;When you come online it should seamlessly sync these changes back to the server, and fetch any new mail and other changes.&lt;/p&gt;&lt;h2 id=&quot;general-architecture&quot; tabindex=&quot;-1&quot;&gt;General architecture&lt;/h2&gt;&lt;p&gt;The Fastmail app is very cleanly separated from our server, which was a huge benefit when adding offline support. All data in the app is loaded via a &lt;a href=&quot;https://jmap.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JMAP&lt;/a&gt; API, with all UI rendering and routing happening in the app. This means the data flow looks a bit like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[App] ← JMAP → [Server]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gave us a really well defined boundary on which to build the offline support. If we built something that could handle and respond to the JMAP requests directly on your device then the app would work offline, and we wouldn’t really have to change anything else in it. So the architecture we came up with looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[App] ← JMAP → [Caching layer] ← JMAP → [Server]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking at our new diagram, we can see that we are building two things:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;A JMAP server that can understand the requests the client makes, fetch and write the data from/to a local store, and return a JMAP response.&lt;/li&gt; &lt;li&gt;A JMAP client that can fetch data it doesn’t have from the server, and write back changes made while we were offline.&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;This is actually quite a lot harder than just building a JMAP server! We will often only have partial information, and we have to transparently pass through requests for data we don’t have to the server, and gracefully handle a fallback if we are offline.&lt;/p&gt;&lt;p&gt;The core function our caching layer needs to perform is handling a JMAP request from the client. Each JMAP request is a sequence of method calls. Once we have locally cached data, we may be able to handle the method call entirely locally, but we may always run into one or more that we can’t.&lt;/p&gt;&lt;p&gt;For performance, we want to batch our method calls into a single database transaction where possible. But we can’t hold open a transaction over a network request, so we divide up the execution into phases.&lt;/p&gt;&lt;p&gt;First, we attempt to execute the method calls locally. If all complete, we’re done! If any require us to fallback to the server, we stop and send it everything from that point on, as we want to avoid making multiple HTTP requests, which would be slow.&lt;/p&gt;&lt;p&gt;If the request completed successfully, we process the responses to save any new data into our local datastore. If the request failed, we call the offline fallback methods, which may be able to still return a response to the UI.&lt;/p&gt;&lt;h2 id=&quot;a-separate-thread-for-the-caching-layer&quot; tabindex=&quot;-1&quot;&gt;A separate thread for the caching layer&lt;/h2&gt;&lt;p&gt;The caching layer runs on your device, just like the rest of the app. Because JMAP requests are already asynchronous network calls from the UI, we can easily run the caching layer in a separate OS thread so it never blocks the UI thread, which could cause &lt;a href=&quot;https://en.wiktionary.org/wiki/jank&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jank&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Our app is built using web technology, which allows us, as a small company, to build an app that runs everywhere our users are, with a single code base and feature parity across all platforms. Separate threads are represented as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;workers&lt;/a&gt; in the web API. There are three types of worker:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Worker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Dedicated worker&lt;/a&gt; — this is a worker that is tied to a particular window or tab in your browser. If you have Fastmail open in multiple tabs, each would have to create its own worker.&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Shared worker&lt;/a&gt; — this is a worker that’s shared between tabs or windows, so no matter how many you have there’s only a single instance of this worker.&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Service worker&lt;/a&gt; — this is a special type of shared worker that can intercept network requests and change their response.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;At first glance, a service worker seems the place to handle all of this, and this is what we tried first. However, we soon switched over to using a shared worker instead:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;The service worker is designed to be short lived and only spun up when needed, but we wanted to hold open a persistent &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;EventSource push connection&lt;/a&gt; while the app is open, for instant updates. The shared worker is a better conceptual fit for this.&lt;/li&gt; &lt;li&gt;We don’t need to intercept network requests, as we can just pass the JMAP request object directly to the worker using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;postMessage&lt;/a&gt; API, avoiding some serialisation overhead.&lt;/li&gt; &lt;li&gt;We ran into a bug in iOS where network requests would sometimes not be intercepted even though the service worker was registered when the app was running in the background. This meant we &lt;em&gt;had&lt;/em&gt; to pass the request directly to the worker anyway instead of intercepting it at the network level to ensure we didn’t hit this bug.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;We wanted to use a shared worker rather than a dedicated worker for more efficiency when there were multiple tabs open — we can avoid some contention and locking issues, and ensure we have a single push connection open to the server.&lt;/p&gt;&lt;h2 id=&quot;storing-data-indexed-db&quot; tabindex=&quot;-1&quot;&gt;Storing data: IndexedDB&lt;/h2&gt;&lt;p&gt;The web API for storing large volumes of structured data is &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt;. This lets you create multiple &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;object stores&lt;/a&gt; (the equivalent of SQL tables), which offer simple key-value storage with ordered keys. Indexes can be automatically built based on properties in the object being stored. Transactions ensure data consistency.&lt;/p&gt;&lt;p&gt;The IndexedDB API was unfortunately designed just before promises became ubiquitous in the web world. This means just fetching a record from a store requires code a bit like this:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; store&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function function-variable token&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;callErrorHandler&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function function-variable token&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; record &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;comment token&quot;&gt;// Do something&lt;/span&gt;
&lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is clunky and becomes hard to read and follow. However, we wrote one tiny little wrapper function that converts it into a promise-based API:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;function function-variable token&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;parameter token&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;keyword token&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;class-name token&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;parameter token&quot;&gt;resolve&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt; reject&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;{&lt;/span&gt;
        request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function function-variable token&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
        request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function function-variable token&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator token&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;punctuation token&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using this function, we can rewrite the above fetch like this:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;keyword token&quot;&gt;const&lt;/span&gt; record &lt;span class=&quot;operator token&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword token&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;function token&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;store&lt;span class=&quot;punctuation token&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function token&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// Do something&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Note, if the fetch has an error this will result in an exception being thrown, which is generally handled at a higher layer, so avoids that cluttering our code here at all!)&lt;/p&gt;&lt;p&gt;With this simple addition, I found the IndexedDB API consistent and easy to work with.&lt;/p&gt;&lt;h2 id=&quot;the-standard-object-store-structure&quot; tabindex=&quot;-1&quot;&gt;The standard object store structure&lt;/h2&gt;&lt;p&gt;The consistency of JMAP means we can write one generic implementation and then use it to provide offline support for all our data types. For each data type (such as Calendar, Email, Contact, etc.) we create an object store to store the instances of that type.&lt;/p&gt;&lt;p&gt;When we create the object store we also store a single metadata object in it, using a special key (a zero-byte &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ArrayBuffer&lt;/a&gt;). This stores some important bookkeeping information, in particular the following properties:&lt;/p&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;comment token&quot;&gt;// Do we have the full set of data from the server for this data&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// type?&lt;/span&gt;
&lt;span class=&quot;literal-property property token&quot;&gt;hasAllRecords&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;boolean token&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;comment token&quot;&gt;// State string representing the current server state we have&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// synced with. The store may also contain newer information, but&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// that&#39;s ok as it will still get to the correct state when we&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// update from the old state.&lt;/span&gt;
&lt;span class=&quot;literal-property property token&quot;&gt;serverState&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;string token&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;comment token&quot;&gt;// This is the highest modseq of a record in the store.&lt;/span&gt;
&lt;span class=&quot;literal-property property token&quot;&gt;lastModSeq&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;comment token&quot;&gt;// This is the highest modseq of a record that was destroyed that&#39;s&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// now been removed entirely from the store; we can only calculate&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// changes accurately from this point on.&lt;/span&gt;
&lt;span class=&quot;literal-property property token&quot;&gt;highestPurgedModSeq&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;comment token&quot;&gt;// This is the number of records currently marked destroyed in the&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// store. We keep them there so we can calculate changes. Once we&lt;/span&gt;
&lt;span class=&quot;comment token&quot;&gt;// cross a threshold, we&#39;ll clean up old ones.&lt;/span&gt;
&lt;span class=&quot;literal-property property token&quot;&gt;numDestroyed&lt;/span&gt;&lt;span class=&quot;operator token&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;number token&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation token&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A key concept here is &lt;em&gt;modseq&lt;/em&gt;, which stands for “modification sequence”. It’s a counter we keep per account, per data type. Every time we make a change to a record in our local store we bump the sequence number and assign that as the new “updated” modseq for that record. We also store a “created” modseq on each record, which is the same as the “updated” modseq when the record is first created. These simple bookkeeping properties allow us to efficiently calculate changes, as needed for &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8620.html#section-5.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;the JMAP “/changes” method&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Aside from the metadata object, every other entry in the object store is a record — an instance of that data type.&lt;/p&gt;&lt;p&gt;The key for each record is &lt;code&gt;[account id, id]&lt;/code&gt;, because some data types exist in multiple accounts (e.g. shared contacts and your personal contacts) and ids are only unique within an account. As far as I could see from inspecting the source, string keys are stored as &lt;a href=&quot;https://en.wikipedia.org/wiki/UTF-16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UTF-16&lt;/a&gt; in all major IndexedDB implementations. This is a fairly inefficient encoding, especially as we know JMAP ids can only use the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc4648#section-5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;base64url characters&lt;/a&gt;, so for efficiency we encode this data into an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ArrayBuffer&lt;/a&gt;, making use of this fact.&lt;/p&gt;&lt;p&gt;The value associated with the key is the record itself — an object representing an instance of that data type, as fetched from the server. In addition, we add a few bookkeeping properties, as discussed above:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;The created modseq&lt;/li&gt; &lt;li&gt;The updated modseq&lt;/li&gt; &lt;li&gt;Is the record destroyed?&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;Each object store has an index built automatically based on the updated modseq of the records.&lt;/p&gt;&lt;p&gt;The modseq is used as the “state” string over JMAP. When asked for what’s changed since a particular state, we know:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Only records with a higher modseq have changed (which we can efficiently get from the index).&lt;/li&gt; &lt;li&gt;If the record’s &lt;code&gt;created&lt;/code&gt; modseq is higher than &lt;code&gt;lastModSeq&lt;/code&gt;, it’s new. Otherwise it’s been updated or destroyed (depending on whether the record is now destroyed). If it’s new and also destroyed, we can ignore it entirely.&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;next-up-local-changes&quot; tabindex=&quot;-1&quot;&gt;Next up, local changes&lt;/h2&gt;&lt;p&gt;In this post we looked at the basic overview of how our offline caching layer fits into our app, and the way it stores data to efficiently respond to JMAP requests. Tomorrow, we’ll dive into &lt;a href=&quot;/blog/offline-sync/&quot;&gt;how it keeps track of changes the user makes while offline&lt;/a&gt;, so it can reconcile this with the server.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 16: Offline support now in public beta</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/offline-in-beta/' />
			<id>https://www.fastmail.com/blog/offline-in-beta/</id>
			<updated>2024-12-16T00:00:01Z</updated><author>
				<name>Neil Jenkins</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the sixteenth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/platform-team-working-agreement/&quot;&gt;Dec 15: Platform Team working agreement&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/offline-architecture/&quot;&gt;Dec 17: Building offline: general architecture&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;On an aeroplane, or down a tunnel. Roaming in a foreign land, or just out for a walk in the country. There are still times when we aren’t connected to the internet. As a strong supporter of open standards, you’ve always been able to use Fastmail with any email app you like, many of which work offline. However, many of our customers prefer the Fastmail app (we certainly do!), and offline support there has been our most popular request for some time. As Bron &lt;a href=&quot;/blog/2023-advent-post-bron-gondwana/&quot;&gt;foreshadowed last year&lt;/a&gt;, we’ve been working hard on this big project for some time, and and we’re very pleased to announce it’s now ready for public beta testing.&lt;/p&gt;&lt;h2 id=&quot;what-s-a-beta&quot; tabindex=&quot;-1&quot;&gt;What’s a beta?&lt;/h2&gt;&lt;p&gt;Fastmail runs a beta version of our app to allow our users that like to live on the cutting edge to get early access to new features before they’re finished, and send us feedback while we’re still working on them. Things may be broken occasionally, but generally it’s pretty stable.&lt;/p&gt;&lt;h2 id=&quot;how-do-i-get-the-beta&quot; tabindex=&quot;-1&quot;&gt;How do I get the beta?&lt;/h2&gt;&lt;p&gt;If you are using our iOS or Android apps:&lt;/p&gt;&lt;ol&gt; &lt;li&gt;Check for updates in the app store — make sure you have the latest version.&lt;/li&gt; &lt;li&gt;Go to Settings → Device settings → Show Advanced Settings, and change the “server backend” to “Beta”.&lt;/li&gt; &lt;/ol&gt;&lt;p&gt;If you are using Fastmail on the web, just log in at &lt;a href=&quot;https://betaapp.fastmail.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;betaapp.fastmail.com&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In either case, once on beta you will find a new settings screen — &lt;a href=&quot;https://betaapp.fastmail.com/settings/offline&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Settings → Offline&lt;/a&gt; — where you can toggle on offline support. You must have ticked “Keep me logged in” when you logged in to enable offline support.&lt;/p&gt;&lt;h2 id=&quot;what-can-i-do-offline&quot; tabindex=&quot;-1&quot;&gt;What can I do offline?&lt;/h2&gt;&lt;p&gt;Almost everything! You can read mail, reply, view and edit your contacts/calendar, change settings, etc.…&lt;/p&gt;&lt;p&gt;Probably easier is to describe what &lt;em&gt;won’t&lt;/em&gt; work offline:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Mail search will not look inside attachments, and will give slightly different results to when online. If you don’t choose to make every message available offline, it won’t be able to match against content it hasn’t downloaded!&lt;/li&gt; &lt;li&gt;Snoozed messages will not move back to the inbox while offline.&lt;/li&gt; &lt;li&gt;Calendar reminders will not show a notification.&lt;/li&gt; &lt;li&gt;You can’t delete attachments.&lt;/li&gt; &lt;li&gt;You can’t attach files to a message you are composing.&lt;/li&gt; &lt;li&gt;You can’t add or change users or domains, change your plan or update your billing details, or change your security settings.&lt;/li&gt; &lt;/ul&gt;&lt;h2 id=&quot;how-do-i-send-feedback&quot; tabindex=&quot;-1&quot;&gt;How do I send feedback?&lt;/h2&gt;&lt;p&gt;Found a bug? Got an idea? Something you like, or don’t like? &lt;a href=&quot;/support/&quot;&gt;Let us know&lt;/a&gt;! Our support team will pass all feedback on to the product team. We promise we read and carefully consider it all.&lt;/p&gt;&lt;h2 id=&quot;what-s-the-tech-behind-your-offline-support&quot; tabindex=&quot;-1&quot;&gt;What’s the tech behind your offline support?&lt;/h2&gt;&lt;p&gt;Interested in the technical details? Over the next few days I’m going to dive into how we built offline support into our app, starting with &lt;a href=&quot;/blog/offline-architecture/&quot;&gt;the general architecture&lt;/a&gt;.&lt;/p&gt;</content>
        </entry><entry>
            <title>Dec 15: Platform Team working agreement</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/platform-team-working-agreement/' />
			<id>https://www.fastmail.com/blog/platform-team-working-agreement/</id>
			<updated>2024-12-15T00:00:01Z</updated><author>
				<name>Bron Gondwana</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the fifteenth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/on-call-systems/&quot;&gt;Dec 14: On-call systems&lt;/a&gt;. The next post is &lt;a href=&quot;/blog/offline-in-beta/&quot;&gt;Dec 16: Offline support now in public beta&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Another Sunday, another document from our internal collection!&lt;/p&gt;&lt;p&gt;Over the past couple of years, we’ve been taking the bits that we like from scrum, agile, and all the other buzzword methodologies that are out there. One of the best things that all the good frameworks to is give a space for setting behaviours for ourselves and each other within a team — so you know what to expect from your colleagues, and also what they expect of you!&lt;/p&gt;&lt;p&gt;I posted about our Mission Statement and Guiding Principles already, now we narrow down to look at a single team. Our platform team maintains the infrastructure that the Fastmail product runs on top of.&lt;/p&gt;&lt;p&gt;We started from here:&lt;/p&gt;&lt;h2 id=&quot;principles-and-duties&quot; tabindex=&quot;-1&quot;&gt;Principles and duties&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;We are lifeguards:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;We watch out for upcoming risks and for systems in distress.&lt;/li&gt; &lt;li&gt;Part of our day is keeping a watchful eye and scanning for strangeness and hints of something going down.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;&lt;strong&gt;We are first-aiders:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;When something goes wrong, our first principle is to keep the patient alive&lt;/li&gt; &lt;li&gt;For Fastmail: the patient is the data stored on our systems; the data we store on the behalf of others. This is a higher priority than uptime.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;&lt;strong&gt;We are paramedics:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;We do more than just keep the patient alive! We perform surgery and repairs to the level of our ability.&lt;/li&gt; &lt;li&gt;For more complex things, we stabilise and bring things to the specialists in that system to make more permanent repairs.&lt;/li&gt; &lt;li&gt;We get the service back up for as many as possible, as quickly as possible, without compromising data.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;&lt;strong&gt;We are specialists:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;For the basics of first aid and early response, we all need to become experts - and we all need to be vigilant.&lt;/li&gt; &lt;li&gt;But; we all have our areas of specialty, and it’s OK to lean into that!&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;&lt;strong&gt;The duties of a platformer:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Monitoring - looking at the alert emails, the metrics, etc and making sure things are nominal.&lt;/li&gt; &lt;li&gt;First-aid first - if the platform is in crisis, drop everything and fix it. This is the oncall duty, we all do this.&lt;/li&gt; &lt;li&gt;Janitorial - mop the floors, oil the joints, replace worn parts. We all do this, on rotation and as we see things. We keep the ship ship-shape.&lt;/li&gt; &lt;li&gt;Initiatives - look into possible improvements, experiment with possibilities, and build the future. Everyone should have an initiative they are leading or collaborating on. These make progress in the time we’re not doing first-aid or janitorial work.&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;A platform team shouldn’t be particularly busy. The first-aid and janitorial work should not be a large chunk of your day most of the time.&lt;/p&gt;&lt;p&gt;Everyone on the platform team is an experienced and senior Platform Engineer, though most of the current team are quite new to Fastmail, which is why I’m also embedded with them as the crusty old expert who knows where many of the bodies are buried!&lt;/p&gt;&lt;p&gt;Since the team is split between USA and Australia; we only spend an hour of dedicated synchronous time per week. So we brought everyone together in Melbourne in November, and came up with the following schedule and working agreement:&lt;/p&gt;&lt;p&gt;Over each 4 week period, this includes two set of task list gardening, two technical deep-dive sessions, a retrospective where we can change how we operate as a team! One member of the team runs all the ceremonies for a 4 week period, and hands off after the retrospective.&lt;/p&gt;&lt;h2 id=&quot;working-agreement&quot; tabindex=&quot;-1&quot;&gt;Working Agreement&lt;/h2&gt;&lt;p&gt;We value action, forward progress and take ownership of tasks&lt;/p&gt;&lt;p&gt;We learn from each others successes and mistakes, and use good processes to protect ourselves from human error, always asking how we can be better.&lt;/p&gt;&lt;p&gt;We give and seek feedback, and encourage asking questions (no dumb questions)&lt;/p&gt;&lt;p&gt;We generate discussion and directional buy in, and make time per site for mobbing/pairing and getting clarity on solutions.&lt;/p&gt;&lt;p&gt;We celebrate our successes and show them off to the rest of the company.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;To resolve conflict we:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Check with the customer (the Fastmail product team) - what do they need?&lt;/li&gt; &lt;li&gt;Check with an area expert&lt;/li&gt; &lt;li&gt;Use experiments&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;&lt;strong&gt;To agree things we:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Make solo calls if comfortable; or&lt;/li&gt; &lt;li&gt;Bring it ‘To Discuss’ - the fortnightly deep dive meeting&lt;/li&gt; &lt;/ul&gt;</content>
        </entry><entry>
            <title>Dec 14: On-call systems</title>
			<link rel='alternate' type='text/html' href='https://www.fastmail.com/blog/on-call-systems/' />
			<id>https://www.fastmail.com/blog/on-call-systems/</id>
			<updated>2024-12-14T00:00:02Z</updated><author>
				<name>Luke Erlacher</name>
			</author><content xml:lang='en' type='html'>&lt;p&gt;This is the fourteenth post in the &lt;a href=&quot;/blog/fastmail-advent-2024/&quot;&gt;Fastmail Advent 2024&lt;/a&gt; series. The previous post was &lt;a href=&quot;/blog/moving-fastmail-dns-to-knot/&quot;&gt;Dec 13: It’s knot DNS. There’s no way it’s DNS. It is DNS!&lt;/a&gt; The next post is &lt;a href=&quot;/blog/platform-team-working-agreement/&quot;&gt;Dec 15: Platform Team working agreement&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After the scary interlude for Friday the 13th, here’s a follow-up to our blog about support and on-call.&lt;/p&gt;&lt;p&gt;At Fastmail, we own and operate most of our stack, from networking gear and servers in the hosted datacenters we use, server OS setup, internal services, backups and redundancy, through to user-facing services and mail flow.&lt;/p&gt;&lt;p&gt;In the last post, we introduced our organizational model of how our support and on-call team manage on-call and incidents. In this blog post, we get a little more technical into the systems we use to help us do this.&lt;/p&gt;&lt;h2 id=&quot;how-incidents-are-raised&quot; tabindex=&quot;-1&quot;&gt;How Incidents are raised&lt;/h2&gt;&lt;p&gt;We source alerts from our service stack through a number of mechanisms:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Prometheus alerts for infrastructure and service metrics such as disk usage and replication delay&lt;/li&gt; &lt;li&gt;Logwatchers that alert on things like Cyrus errors&lt;/li&gt; &lt;li&gt;Cron scripts that run regular tests and alert on failures&lt;/li&gt; &lt;li&gt;Alerts from external monitoring&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;We’re currently experimenting with integrating all of these sources into a single observability stack (I’m a big fan of the unified observability model) - maybe you will read about that in next year’s advent blog post series!&lt;/p&gt;&lt;p&gt;But right now, all of these alerts go straight to Pagerduty to create an incident there.&lt;/p&gt;&lt;p&gt;Our support team and any staff member is empowered to raise an incident at any time when they observe issues that indicate an infrastructure or service failure. This is done via slack pings during working hours, and otherwise by paging through our slackops bot.&lt;/p&gt;&lt;h2 id=&quot;pagerduty-alerts&quot; tabindex=&quot;-1&quot;&gt;Pagerduty alerts&lt;/h2&gt;&lt;p&gt;Our Pagerduty on-call rotation and escalation process ensure that incidents are always promptly responded to.&lt;/p&gt;&lt;p&gt;The primary on-call engineer is also responsible for Business As Usual (BAU) tasks such as reviewing non-critical errors and escalations. When incidents have to be escalated past the primary on-call engineer, they go first to the team lead. This allows the on-call engineers that are off rotation to focus on project work during the day, and decompress outside of work hours without worrying about on-call.&lt;/p&gt;&lt;p&gt;In order to minimize the attention load of on-call alerts on the platform engineers, engineers can override the on-call schedule to take on-call when they do large deploys that result in alerts.&lt;/p&gt;&lt;p&gt;We have recently created a dedicated incident discussion channel. Previously, incident communication would take place either in the alerts feed channel, the “ops” channel where we post updates for visibility into changes and deploys, or in a team channel for the team principally responding to the incident.&lt;/p&gt;&lt;p&gt;A dedicated incident discussion channel removes incident discussion noise from other channels. We decided against going with the “create a dedicated channel for every incident” as we think this would create too much churn and reduce visibility.&lt;/p&gt;&lt;h2 id=&quot;outage-notifications&quot; tabindex=&quot;-1&quot;&gt;Outage notifications&lt;/h2&gt;&lt;p&gt;When our service is experiencing issues or outages, we want our users to know as soon as possible, both for their benefit and our support team. As we have support staff on shift 24/7, they handle outage notifications via our status page on https://fastmailstatus.com/, Zendesk banners, and posting on our social channels. During incidents, they act as the communication conduit to our users - ascertaining the extent of outages, ensuring timely updates, and checking that when services are restored this is reflected in user experience.&lt;/p&gt;&lt;h2 id=&quot;pagerduty-nitty-gritty&quot; tabindex=&quot;-1&quot;&gt;Pagerduty nitty-gritty&lt;/h2&gt;&lt;p&gt;Some of the things we do to automate and simplify our life as on-call engineers butt up against the limits of Pagerduty.&lt;/p&gt;&lt;p&gt;For example, it would be nice to have an empty escalation layer as the first layer that during normal times falls through to the next layer instantly. Then people can add themselves in this layer to override the on-call escalation chain for doing deploys. However Pagerduty does not allow this so we have to override the schedules for primary on-call instead.&lt;/p&gt;&lt;p&gt;We also have regular unavailabilities during the week for on-call engineers for scheduled events such as gym training or dance classes. For this, we want to add exclusions to the schedule so that the alerts go to the fallback on-call immediately.&lt;/p&gt;&lt;p&gt;These unavailabilities are different for every individual engineer. Pagerduty doesn’t allow to model this - on-call schedules can have almost arbitrary scheduling through weekdays, but this is per schedule and not per on-call user. So we have to split the schedule and make a separate schedule for every engineer. However, we then can no longer make a rotating schedule - inside one schedule, a user can’t be on for a week and then off for a week - unless that off week is taken by another user.&lt;/p&gt;&lt;p&gt;To work around this we have created (and paid for) a “Blank” dummy user that has no contact methods and is only there to fill the off week for a user.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;/assets/blog/2024-12-14-oncall-systems/pagerduty_screenshot_3x.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-375.webp 375w, /assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-750.webp 750w, /assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-1500.webp 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;img alt=&quot;Pagerduty schedule&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-375.png&quot; width=&quot;1500&quot; height=&quot;514&quot; srcset=&quot;/assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-375.png 375w, /assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-750.png 750w, /assets/images/pagerduty_screenshot_3x-4tkoE_Vw31-1500.png 1500w&quot; sizes=&quot;(max-width: 425px) 375px, 750px&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;As you can see in the picture, there are 3 layers in the schedule: The first layer is a base layer that is a weekly rotation between the fallback engineer and the Blank user.&lt;/p&gt;&lt;p&gt;The second layer has the primary oncall engineer’s shift for the first half of the day, and the third layer has the second half of the day. This is also in a weekly rotation between the oncall engineer and the Blank user.&lt;/p&gt;&lt;p&gt;To understand what’s going on here, imagine that the primary oncall engineer needs to be offline every monday from 4PM to 6PM. For that, we put an 8AM - 4PM shift time for every monday in the second layer, and 6PM to 10PM in the third layer. During the 4PM to 6PM time, there is no on-call user in the second or third layer, so it falls back to the base layer and so the fallback engineer will be active.&lt;/p&gt;&lt;p&gt;Finally, we make a separate schedule for every on-call engineer following the same pattern, but offset by one week.&lt;/p&gt;&lt;h2 id=&quot;what-works-well&quot; tabindex=&quot;-1&quot;&gt;What works well&lt;/h2&gt;&lt;p&gt;I have done overnight on-call at previous companies and that is a lot more stressful. I definitely think being able to split on-call is a lot better for engineers’ quality of life!&lt;/p&gt;&lt;p&gt;We have ops tools to do things like log searching, show replication stats, grafana dashboards, and prometheus alerts that give us quick insights into the state of our infrastructure and services to pin down the root cause of an incident quickly. These could be more comprehensive and better organized but they work well.&lt;/p&gt;&lt;p&gt;We have runbooks for some, but not all, alerts. Where they exist they are quite good and comprehensive and we frequently review and update them after incidents.&lt;/p&gt;&lt;h2 id=&quot;what-could-work-better&quot; tabindex=&quot;-1&quot;&gt;What could work better&lt;/h2&gt;&lt;p&gt;Most of our incidents auto-resolve when the service / monitor returns to nominal service. This is good! We are lucky to have very few flappy alerts that bounce up and down.&lt;/p&gt;&lt;p&gt;But a handful of alerts do not auto-resolve because the alerting mechanism is not stateful. This is confusing for engineers, and it is an extra annoyance to clean up alerts after an incident.&lt;/p&gt;&lt;p&gt;We don’t currently have good categorization and prioritization of alerts. The only way to know whether an alert is important or not is to know from experience, or asking someone with experience. We need to spend more time and be more ruthless in weeding out low-quality alerts!&lt;/p&gt;</content>
        </entry>
</feed>
