Skip to content

Sangye Ince-Johannsen

Hi, I'm Sangye. I'm a public interest environmental lawyer.

Getting Clever with Caddy and Conduwuit / Matrix

A deep dive into We2.ee's Caddy configuration, handling Matrix federation, offering profile and room redirection shortlinks, and combining multiple services on a single domain.

My post earlier this evening discussed generally how Caddy has made self-hosting multiple web services a breeze. Here I want to build on that and look specifically at the Caddy configuration I use for We2.ee, the public Matrix homeserver I run and have posted about already.

The complete Caddyfile entry is available here. Let's look closer at a few key sections. First, you'll see a series of redirection handlers like this:

    handle /about {
        redir https://sij.law/we2ee/ permanent
    }

This one simply redirects we2.ee/about to sij.law/we2ee . This allows me to create authoritative "About" URL on the we2.ee domain but host the actual page here on my blog – saving me having to host a whole separate CMS just for We2.ee, and potentially lending credibility to We2.ee through my professional online presence here.

Next, you'll see some more redirection handlers that rely on regular expressions ("regex"):

    # Handle Matrix-style room redirects
    @matrix_local_room {
        path_regexp ^/@@([^:]+)$
    }
    redir @matrix_local_room https://matrix.to/#/%23{re.1}:we2.ee permanent

    # Handle Matrix-style room redirects with custom domains
    @matrix_remote_room {
        path_regexp ^/@@([^:]+):([^/]+)$
    }
    redir @matrix_remote_room https://matrix.to/#/%23{re.1}:{re.2} permanent

    # Handle Matrix-style user redirects
    @matrix_local_user {
        path_regexp ^/@([^:]+)$
    }
    redir @matrix_local_user https://matrix.to/#/@{re.1}:we2.ee permanent

    # Handle Matrix-style user redirects with custom domains
    @matrix_remote_user {
        path_regexp ^/@([^:]+):([^/]+)$
    }
    redir @matrix_remote_user https://matrix.to/#/@{re.1}:{re.2} permanent

These are particularly efficient—they allow for much shorter links for Matrix rooms and user profiles that redirect to the Matrix.to service. For example:

Next you'll see the handlers for the actual underlying services, Conduwuit and Element:

    # Handle Conduwuit homeserver
    handle /_matrix/* {
        reverse_proxy localhost:8448
    }

    # Handle federation
    handle /.well-known/matrix/server {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{"m.server": "we2.ee"}`
    }

    # Handle client discovery
    handle /.well-known/matrix/client {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{
            "m.homeserver": {"base_url": "https://we2.ee"},
            "org.matrix.msc3575.proxy": {"url": "https://we2.ee"}
        }`
    }

    # Handle MSC1929 Admin Contact Information
    handle /.well-known/matrix/support {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{
            "contacts": [
                {
                    "matrix_id": "@sij:we2.ee",
                    "email_address": "[email protected]",
                    "role": "m.role.admin"
                }
            ],
            "support_page": "https://we2.ee/about"
        }`
    }

    # Handle Element webUI
    handle {
        reverse_proxy localhost:8637
    }

This part of the Caddy configuration block:

  • serves up the actual Matrix homeserver (powered by conduwuit) at We2.ee
  • provides the necessary endpoints for federation
  • provides the MSC1929 endpoint for handling abuse reports, etc.
  • serves up an Element web interface for accessing the Matrix homeserver directly at We2.ee

I hope some of this is useful for folks running their own Matrix homeservers or anyone interested in seeing how Caddy configurations can be structured for more complex setups.

Simplifying Web Services with Caddy

Running multiple web services doesn't have to be complicated. Here's how Caddy makes it simple by handling reverse proxying and HTTPS certificates automatically, plus a script I use to set up new services with a single command.

After my recent posts about We2.ee, Lone.Earth, Earth.Law and that pump calculator project, several folks asked about managing multiple websites without it becoming a huge time sink. The secret isn't complicated—it's a neat tool called Caddy that handles most of the tedious parts automatically.

Understanding Reverse Proxies

Traditionally, web servers were designed with a simple model: one server running a single service on ports 80 (HTTP) and—more recently as infosec awareness increased—443 (HTTPS). This made sense when most organizations ran just one website or application per server. The web server would directly handle incoming requests and serve the content.

But this model doesn't work well for self-hosting. Most of us want to run multiple services on a single machine - maybe a blog like this, a chat service like We2.ee, and a few microservices like that pump calculator. We can't dedicate an entire server to each service—that would be wasteful and expensive—and we can't run them all on ports 80/443 (only one service can use a port at a time).

This is where reverse proxies come in. They act as a traffic director for your web server. Instead of services competing for ports 80 and 443, each service runs on its own port, and the reverse proxy directs traffic for

  • A blog to port 2368
  • A Mastodon instance to port 3000
  • An uptime tracking service to port 3001
  • A code hub to port 3003
  • An encrypted chat service to port 8448
  • DNS-over-HTTPS filter and resolver on 8502
  • A Peertube instance to port 9000
  • An LLM API to port 11434
  • ... etc.

When someone visits any of your websites, the reverse proxy looks at which domain they're trying to reach and routes them to the right service. That's really all there is to it—it's just routing traffic based on the requested domain.

Why Caddy Makes Life Easier

Caddy is a reverse proxy that manages this well and also happens to take care of one of the biggest headaches in web hosting: HTTPS certificates. Here's what my actual Caddy config looks like for this blog:

sij.law {
    reverse_proxy localhost:2368
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
}

This simple config tells Caddy to:

  • Send all traffic for sij.law to the blog running on port 2368
  • Automatically get HTTPS certificates from Let's Encrypt
  • Renew those certificates before they expire
  • Handle all the TLS/SSL security settings

If you've ever dealt with manual certificate management or complex web server configurations, you'll appreciate how much work these few lines are saving.

Making Domain Setup Even Easier

To streamline things further, I wrote a script that automates the whole domain setup process. When I was ready to launch that pump calculator I mentioned in my last post on the open web, I just ran:

cf pumpcalc.sij.ai --port 8901 --ip 100.64.64.11

One command and done—cf creates the DNS record on Cloudflare and points it to the IP of the server running Caddy, creates a Caddy configuration that reverse proxies pumpcalc.sij.ai to port 8901 on my testbench server (which has the Tailscale IP address 100.64.64.11), and handles the HTTPS certification.

🌐
Using Tailscale for the connection means I don't need to expose the underlying service to the public internet—the server doesn't need a public IP address, port forwarding rules, or open firewall ports. I plan to do a deep dive on Tailscale in a future post, but for now just know it adds an important layer of security and simplicity to this setup.

If you want to try this script out yourself, see the more detailed documentation at sij.ai/sij/cf, and by all means have a look at the Python code and see how it works under the hood.

Getting Started

  1. Start by installing Caddy on your server
  2. Create a config for just one website
  3. Let Caddy handle your HTTPS certificates
  4. Add more sites when you're ready

Start small, get comfortable with how it works, and expand when you need to. Ready to dig deeper? The Caddy documentation is excellent, or feel free to reach out with questions.

Thinking Like a Developer, Pt. 1

When our backup pump failed on the homestead, I built a calculator to figure out what we really needed. It’s a small, open-source tool born from necessity and a few iterations with self-hosted AI.

I live on a homestead in southern Oregon, surrounded by the vastness of the Umpqua National Forest in every direction. It’s the kind of place where nature dictates the rhythm of life. We rely on a natural mountain spring for water most days, but when that fails (as nature sometimes does), we turn to a small stream or a mile-long ditch. According to local lore, some intrepid homesteader dug that ditch in the early 1900s to water a single cow.

These water sources connect to a network of pipes, backup pumps, and an unoptimized system that could generously be described as "inventive." When our pump failed recently, we faced an immediate and critical question: how powerful does a replacement pump need to be?


From Problem to Solution

To answer that question, I did what any coder-lawyer-homesteader would do—I wrote a script. Specifically, a pump power calculator that factors in pipe diameter, distance, flow rate, pipe material, and other inputs to calculate the horsepower needed for a given setup. It factors in key considerations like friction head loss, flow velocity, and static head, ultimately providing recommendations with a built-in safety margin. For example, in our setup, with a 4000-foot run of 1" pipe that rises up around 120 feet and delivers around 7.5 gallons per minute, it calculated we needed at least 0.75 HP—but 1 HP if we want a 30% safety margin.

You can try it out for yourself at pumpcalc.sij.ai or embedded at the bottom of this post, and if you’re curious about the code, it’s open source at sij.ai/sij/pumpcalc. I built the calculator in Python using FastAPI for the backend and Jinja2 for templating—simple, reliable tools that get the job done without unnecessary complexity.

This wasn’t a solo endeavor. I leaned on the open-source AI tool Ollama and specifically QwQ, a powerful 32 billion parameter research model that rivals leading commercial AI systems like ChatGPT o1 in reasoning capabilities. QwQ particularly excels at technical problem-solving and mathematical tasks, making it perfect for engineering calculations like this.

🫠
I now avoid AI tools like ChatGPT or Claude because of their environmental impact and privacy concerns. Self-hosted tools like Ollama solve both issues: they’re private and energy-efficient. In fact, consulting Ollama to write this script used just about 6 Wh of additional electricity on my 2021 MacBook Pro—roughly the energy it takes to keep an efficient LED bulb running for half an hour.

The Iterative Process of Coding

Developing this script wasn’t a one-and-done affair. It took five back-and-forth sessions with the AI to:

  1. Factor in relevant variables like pipe roughness and flow rate.
  2. Exclude unnecessary inputs that made the interface clunky.
  3. Add some polish, like the Gruvbox Dark color scheme that now graces the app.

Each iteration made the calculator more useful and user-friendly. By the end, I had something functional, simple, and—dare I say—elegant.



Why Share This?

I’m sharing this as the first in a series of "Thinking Like a Developer" stories, because I believe coding isn’t as mystifying as it might seem. If a lawyer on a homestead with a temperamental water system can write a pump calculator, anyone can. The key is thinking like a developer: break the problem into smaller, solvable pieces, and don’t be afraid to consult tools or collaborators along the way.

This approach to problem-solving—breaking down complex challenges and leveraging coding tools—mirrors how I approach legal technology challenges. I frequently rely on Python and AI libraries to streamline legal work, from document analysis to case law research. Whether it's calculating pump requirements or processing legal documents, the fundamental thinking process remains the same. Who knows? You might find your next project hidden in a problem you didn’t even know you wanted to solve.

Beyond the Hype: What Actually Matters in Productivity Software

The productivity software landscape in 2025 is dominated by a paradox. We have more tools than ever promising to revolutionize how we work, yet finding software that simply works well for critical tasks remains surprisingly difficult. The market is flooded with VC-backed "all-in-one" solutions that promise to be your second brain, your AI assistant, your project manager, and your content creator – all while syncing seamlessly to the cloud and integrating with every workflow imaginable.

These tools seduce us with impressive feature lists. AI integration! Real-time collaboration! Infinite workspaces! Knowledge graphs! The marketing is slick, the interfaces are polished, and the promises are grand. Who wouldn't want a single tool that does everything? The problem is that these solutions often fail precisely when we need them most – during crunch time on a legal brief, in the middle of a research deep-dive, or when racing against a deadline with spotty internet access.

This disconnect between marketing hype and daily reality calls for a fundamental reassessment of how we evaluate productivity software. Instead of asking "What can this tool do?", we need to ask harder questions: Will this tool reliably perform its core function when I'm under pressure? Do I actually own and control my data? Does it respect my operating system's conventions and capabilities? Does it solve real problems, or just create the illusion of productivity?

The answers to these questions often lead us away from the latest all-in-one wonders and toward more focused, thoughtfully designed tools that excel at specific tasks. This isn't about being a luddite or refusing innovation – it's about demanding software that genuinely serves our needs rather than VC growth metrics.

Let's explore what actually matters in productivity software, and why the boring fundamentals often beat exciting promises.

Core Principles for Evaluating Tools

Reliability as Non-Negotiable

Software reliability is like oxygen – you only notice it when it's gone. Nothing destroys trust faster than an app crashing while you're finalizing a court filing, losing your research notes before a deadline, or failing to sync just when you need to reference something on your phone. The psychological cost is real: once software has failed you at a critical moment, you'll forever question whether you can trust it again.

The hidden costs of unreliable software extend far beyond the immediate frustration. You start developing defensive workflows – keeping backup copies, taking extra screenshots, manually exporting more often than you should need to. Your cognitive load increases as you constantly wonder if your work is really saved. You spend time researching alternatives instead of focusing on your actual work. This accumulated friction and anxiety exact a toll on both productivity and peace of mind.

Sometimes, boring is better. OmniOutliner's interface might not win any design awards in 2025, but it has never once lost my work in a decade of writing legal briefs and academic papers. Things' feature set is intentionally constrained, but its rock-solid reliability means I never question whether my tasks are actually captured. These tools prove that reliability isn't just about not crashing – it's about consistently delivering on core promises.

Data Sovereignty

The foundation of any serious productivity tool should be local-first architecture. Your data – your thoughts, research, and work product – should exist first and foremost on your own devices. This isn't just philosophical; it's practical. Local-first means your tools work at full speed regardless of internet connectivity. It means your data isn't held hostage to someone else's servers or business model.

Sync should enhance your workflow, not be a requirement for it. Tools like DEVONthink and Anytype get this right: powerful local functionality with sync as an optional layer. Contrast this with Notion, where even viewing your own notes requires an internet connection. When sync is mandatory, you're not just trusting a company's current practices – you're betting on their long-term viability and ethical behavior.

The risks of closed ecosystems become apparent over time. Companies get acquired, pivot their business models, or shut down entirely. Proprietary formats and cloud-only storage create golden handcuffs – the more you invest in the system, the harder it becomes to leave. This is why robust export capabilities aren't a nice-to-have feature; they're a fundamental right. Your data should be portable, in standard formats, without requiring paid subscriptions or special tools.

Platform Integration

Real macOS optimization goes far beyond a native-looking interface. It's about respecting platform conventions and leveraging system capabilities. Things integrates with macOS Quick Entry and Shortcuts. DEVONthink leverages Spotlight and Services. Hook enables deep linking between native apps. These integrations create workflows that feel natural and efficient.

The proliferation of Electron-based apps has normalized subpar performance and platform-agnostic design. While cross-platform compatibility has its place, tools that fully embrace macOS capabilities offer a fundamentally better experience. Native apps launch faster, use less memory, and respond more smoothly. They support system-wide features like Services, Quick Look, and native window management without awkward workarounds.

System integration points matter because they reduce friction. Universal keyboard shortcuts that work as expected. Services menu integration for quick actions. Proper handling of macOS document states and window management. These details might seem minor in isolation, but they compound into a significantly better user experience.

The performance implications of platform optimization extend beyond raw speed. Native apps tend to be more energy-efficient, crucial for mobile work. They handle system transitions (sleep/wake, network changes) more gracefully. They scale better with larger datasets because they can leverage system capabilities more effectively. When you're dealing with thousands of notes or documents, these optimizations make a real difference in daily use.

The AI Integration Problem

The current wave of AI integration in productivity software often feels more like a game of follow-the-leader than thoughtful feature development. Nearly every app now advertises some form of AI capability, but peek under the hood and you'll find the same OpenAI API calls wrapped in increasingly thin veneers of differentiation.

The OpenAI API Trap

The privacy implications of casual AI integration are staggering. When your note-taking app proudly announces AI features, what they're often really saying is "we're going to send your personal notes, research, and thoughts to OpenAI's servers." For professionals dealing with confidential information – lawyers, researchers, healthcare providers – this creates serious ethical and legal concerns. Even for personal use, do you really want your private journals and notes being used to train third-party AI models?

The cost dependency is equally problematic. Apps that rely on OpenAI's API are essentially reselling compute time with a markup. As these features move from novelty to expected functionality, the costs compound. You're not just paying for your productivity app's subscription – you're indirectly funding their API usage. When OpenAI changes their pricing (which they have, repeatedly), your app's economics change too.

The quality of results from generic AI integration often disappoints. Cookie-cutter implementations of ChatGPT might be good at generating bland summaries or generic suggestions, but they lack the context and specificity that make AI truly useful for productivity work. It's the difference between having a general-purpose chatbot and having AI that actually understands your personal knowledge base and workflow.

Examples of Thoughtful AI Integration

DEVONthink's approach to AI stands in sharp contrast to the current trend. Long before the ChatGPT boom, they developed native machine learning capabilities that run locally on your device. Their AI focuses on specific, valuable tasks: classifying documents, finding relationships between files, suggesting locations for new content. It's AI that serves a purpose rather than chasing a trend.

Noted's integration of Whisper for local audio transcription is another example of AI done right. By running the model locally, they maintain privacy while delivering genuine utility – accurate transcription with timestamp linking that enhances the core note-taking experience. The key is that the AI serves the app's primary purpose rather than existing as a separate feature.

When AI Adds Value vs. Checkbox Features

The distinction between valuable AI integration and checkbox features comes down to three key questions:

  1. Does it enhance the core functionality of the app, or is it bolted on?
  2. Does it respect user privacy and data sovereignty?
  3. Does it solve a specific problem better than non-AI alternatives?

Valuable AI integration looks like DEVONthink's document classification – it makes the core experience better. It looks like Noted's transcription – it solves a specific problem elegantly. Checkbox features look like generic "AI assistants" that merely repackage ChatGPT, or "smart" features that add complexity without corresponding value.

The future of AI in productivity software isn't about having the most AI features – it's about having the right ones, implemented thoughtfully and ethically. As users, we should demand AI integration that respects our privacy, serves genuine needs, and enhances rather than complicates our workflows.

The Case for Specialized Tools

The allure of an all-in-one solution is understandable. The idea of having your entire productivity system in one place sounds efficient. But in practice, tools that try to do everything often end up doing nothing particularly well. The "everything app" becomes the "almost anything, sort of" app.

Why "Do Everything" Often Means "Do Nothing Well"

When apps try to be everything to everyone, they inevitably make compromises. Task management gets buried under a mountain of features. Note-taking becomes cluttered with unnecessary options. Document management becomes an afterthought. The interface grows increasingly complex as features compete for limited screen real estate. What starts as an attempt at simplification through consolidation often ends in cognitive overhead and decreased efficiency.

Success Stories of Focused Applications

Things: Task Management Done Right

Things succeeds precisely because it knows what it isn't. It's not a note-taking app. It's not a calendar. It's not trying to be your second brain. It is, simply and excellently, a task manager. Its design choices reflect this focus: quick entry that never gets in your way, keyboard shortcuts that become muscle memory, and an interface that clarifies rather than complicates. By embracing constraints, Things delivers an experience that feels both powerful and effortless.

DEVONthink: Document Management Mastery

DEVONthink exemplifies the power of doing one thing exceptionally well. It's a document management powerhouse that understands its role in your workflow. Its AI features serve document organization and discovery, not generic chat. Its search capabilities are tuned specifically for document management. Even its interface, while complex, is complex in service of its core purpose. Every feature exists to help you manage, find, and use your documents more effectively.

OmniOutliner: The Power of Purpose-Built Tools

OmniOutliner's focused approach to structured writing and outlining demonstrates why specialized tools endure. It doesn't try to be a full word processor or a note-taking app. Instead, it provides precisely the features needed for serious outlining work: robust keyboard controls, flexible organization, powerful styling options, and rock-solid stability. When you need to outline a legal brief or structure a complex document, its purposeful design proves invaluable.

Integration Between Specialized Tools

The real magic happens when specialized tools work together. Rather than forcing everything into one application, modern workflows can leverage the strengths of multiple focused tools through thoughtful integration.

The Power of Hooks and Automation

Tools like Hook demonstrate how specialized apps can form a cohesive system. By creating bidirectional links between items in different apps – a task in Things, a research document in DEVONthink, an outline in OmniOutliner – Hook lets each app excel at its core function while maintaining connections across your workflow. These connections don't require apps to build complex features outside their expertise; they just need to be good citizens in the larger ecosystem.

Building Workflows Across Apps

The most effective productivity systems often resemble a well-designed toolkit rather than a Swiss Army knife. A task in Things might link to research in DEVONthink, which informs an outline in OmniOutliner, which eventually becomes a draft in Ulysses. Each transition leverages apps' native capabilities – URL schemes, automation support, export options – to create workflows that are both powerful and reliable.

This approach to integration preserves what makes each tool special while creating a whole greater than the sum of its parts. Instead of compromising to fit everything into one app, we can build workflows that use the right tool for each job, connected thoughtfully where it matters.

The Real Cost of "Free" and Cheap Tools

If there's one constant in productivity software, it's that nothing is truly free. When a tool – especially a VC-backed one – offers extensive features at no cost, you're not the customer; you're the product. The real costs of "free" and cheap tools often emerge only after you've invested significant time and data into them.

Lock-in Strategies

The playbook is depressingly familiar: Offer a generous free tier to build dependency, create friction around data export, then gradually restrict features behind paywalls or premium tiers. Notion exemplifies this strategy – it's easy to pour your life into it, but extracting your data in a usable form becomes increasingly difficult as your usage grows. The more you invest in these systems, the higher your switching costs become.

What starts as "free" becomes expensive not in dollars, but in flexibility and control. Your workflows adapt to the tool's limitations. Your data structure conforms to its models. Your team processes build around its constraints. By the time the limitations become painful, extraction costs – in time, effort, and potential data loss – feel prohibitive.

Data Portability Concerns

True data portability goes beyond the ability to export to PDF or download a JSON dump. It means getting your data out in formats you can actually use elsewhere. Compare DEVONthink's extensive export options and standard file formats with Notion's limited export capabilities. One treats your data as yours; the other treats it as theirs.

The most insidious portability issues often surface only when you try to leave. Markdown files that aren't really markdown. Relationships between items that don't survive export. Metadata that vanishes during transfer. These aren't technical limitations – they're business decisions designed to increase retention through friction.

Subscription Models and Sustainability

Not all subscriptions are evil. Teams building quality software need sustainable revenue to continue development and support. The issue isn't paying for software – it's the value proposition and the terms of the relationship.

Setapp offers an interesting counter-model: access to high-quality apps like Ulysses, Hookmark, and many others for a single subscription. The key difference is that these are mature, focused tools with clear value propositions, not platforms trying to own your entire workflow.

The Value Proposition of Quality Software

Quality software justifies its cost through reliability, thoughtful design, and respect for your data and workflows. Things isn't cheap, but its reliability means never losing a task. DEVONthink's price reflects its power as a research tool. OmniOutliner's cost is trivial compared to the value it provides for serious writing and planning work.

The real value proposition extends beyond features:

  • Time saved through reliability and efficiency
  • Peace of mind from knowing your data is yours
  • Professional support when you need it
  • Sustainable development ensuring longevity
  • Respect for platform conventions and capabilities

When evaluating software costs, the question shouldn't be "What's the cheapest option?" but rather "What's the true cost of depending on this tool?" Often, paying for quality software costs less in the long run than dealing with the hidden costs of "free" alternatives.

Building a Sustainable Productivity Stack

Building a productivity system that lasts requires thinking beyond feature lists and current trends. It demands careful consideration of your core workflows, the reliability of your tools, and the long-term viability of your choices.

Evaluating Core Needs

Start by identifying your non-negotiables. What tasks truly matter in your daily work? Where do current tools create friction? What data absolutely must remain under your control? This evaluation often reveals that you need fewer tools than you think, but those tools need to be more robust than you might have assumed.

A sustainable stack typically includes:

  • A rock-solid task manager (like Things)
  • A reliable document management system (like DEVONthink)
  • Purpose-built tools for specific workflows (like OmniOutliner for structured writing)
  • Thoughtful connections between these tools (via Hook or automation)

Investing in Reliability

Reliability isn't just about software not crashing – it's about consistency in your workflow. This means:

  • Tools that work offline first
  • Data that lives on your devices
  • Sync that enhances rather than constrains
  • Export options that respect your ownership
  • Performance that scales with your needs

Planning for Longevity

Consider the long-term viability of your tools. Look for:

  • Companies with sustainable business models
  • Software with clear development roadmaps
  • Tools that use standard formats where possible
  • Apps that respect platform conventions
  • Solutions that can grow with your needs

The Role of Community and Development Approach

A healthy community and transparent development process often indicate a tool's long-term prospects. But "community" doesn't always mean what you think. Obsidian's vast plugin ecosystem might seem attractive, but DEVONthink's years of focused development and professional support often provide more real-world value.

A Call for Better Standards

What Users Should Demand

We need to raise our standards for productivity software:

  • True ownership of our data
  • Local-first architecture with optional sync
  • Clear data export paths
  • Native performance and integration
  • Sustainable business models that align with user interests
  • Privacy-respecting features, especially around AI
  • Transparent pricing without lock-in

What Developers Should Prioritize

Developers need to shift focus from feature bloat to fundamentals:

  • Rock-solid reliability
  • Thoughtful platform integration
  • Standard formats and protocols
  • Clear data portability
  • Sustainable revenue models that don't compromise user interests
  • Ethical AI implementation
  • Performance at scale

The Future of Productivity Software

The future of productivity software isn't about finding the perfect all-in-one solution. It's about building sustainable systems from focused, reliable tools that respect our needs, our data, and our workflows.

We need to move beyond the cycle of VC-backed hype and return to fundamentals: tools that do one thing exceptionally well, respect their users, and stand the test of time. This might mean paying more upfront for quality software, but the alternative – entrusting our work and data to unsustainable "free" solutions – ultimately costs more.

The tools we use shape how we work, think, and create. It's time to demand better. Not more features, not more AI, not more promises – just better, more reliable, more respectful software that helps us do our best work.

Let's vote with our wallets and our attention for the kind of productivity software we actually need, not just what's currently trending on Product Hunt.

we2.ee improvements

GDPR-compliant privacy policy, new logo, profile shortlinks, sliding sync, Element Web, Privatebin

I've been working today on some improvements to we2.ee, the end-to-end-encrypted messaging platform for the people.

Privacy Policy, GDPR, and Terms.

Privacy is really important to me, and I think it shows in our new GDPR-compliant privacy policy. Basically, we collect the minimum needed to run things, encrypt what we can, and try to be totally clear about how data is handled.

I've also added terms because, well, we need some ground rules. While I'm 100% committed to privacy and security, I won't let we2.ee become a place for harm. It's built to protect legitimate privacy interests - not enable abuse or harassment.

The original logo wasn't great. This one is much better:

Here's something neat —we now have shorter profile links. If you use Matrix's matrix.to service, you'll know those URLs can get pretty long. Now you can just use:

  • we2.ee/@user (for we2.ee accounts)
  • we2.ee/@user:homeserver.net (for other Matrix accounts)

Either way, you'll get redirected to matrix.to/#/@user:homeserver.net. This saves 12 characters for we2.ee accounts compared to using matrix.to directly (or 16 characters compared to matrix.org!), and 5 characters for everyone else.

Pretty handy for fitting Matrix profiles into social media bios — especially on the fantastic X-destroying Bluesky (I'm @sij.law over there).

Matrix 2.0 and Sliding Sync.

Really excited about this one — we2.ee now supports sliding sync. It's a key feature of Matrix 2.0 that changes how clients fetch data. Instead of downloading everything at once, clients can just load what they need for what you're actually looking at. Makes everything snappier - login, launch, sync, you name it. Doesn't matter how many rooms you're in either.

This puts us firmly on the Matrix 2.0 roadmap alongside other major improvements like native OIDC authentication and group VoIP. If you're using modern clients like Element X, you'll notice everything feels much more responsive while keeping all the privacy benefits of decentralized, encrypted chat.

Element Web Right There.

we2.ee points to our Element Web instance, which I keep updated with the latest stable release. Element Web is the most full-featured Matrix client out there for browsers, and is a credible alternative to running a dedicated Matrix client. Having this, plus the Matrix homeserver, plus those profile links all on one super short domain is pretty great, if you ask me.

Say Hello to txt.we2.ee!

One last thing — I've set up txt.we2.ee for secure text sharing. It's powered by PrivateBin and works a lot like Pastebin, but with proper end-to-end encryption. Great for sharing code snippets or logs that you want to disappear after being read.

Upcoming we2.ee Roadmap:

  1. Coturn server for more reliable VoIP
  2. Jitsi Meet for videoconferencing
  3. Public Vaultwarden instance
  4. LDAP
  5. SIP gateway for telecom interoperability

That’s all for now—public lands legal work calls.

Cheers,

we2.ee

encrypted chat for the people

Friends, I'm excited to announce the launch of we2.ee: a free, decentralized, end-to-end encrypted messaging platform that puts privacy first. we2.ee is built as a free-standing platform for everyone. Yes, everyone. End-to-end encrypted communications for all!

For the tech-savvy: we2.ee is a Matrix homeserver powered by Conduwuit and Element web.

For everyone else, you've got three easy ways to get started:

  • Use your existing Matrix account via we2.ee
  • Create a new we2.ee account (like @yourname:we2.ee) and use it with any Matrix app (Element, Cinny, FluffyChat, etc.)
  • Do both - sign up, log in, and start sending encrypted messages to anyone on the Matrix network

If you're new to Matrix, it's an open protocol that's been battle-tested and security-audited, trusted by EU-member governments, sensitive NGOs, and privacy advocates alike. Think of it as a more secure and independent alternative to WhatsApp or Telegram, but one where you control your data.

we2.ee runs on dedicated servers in Tallinn, Estonia and Helsinki, Finland - both chosen for their vaunted privacy laws and green energy grid. Every message is end-to-end encrypted (that's the 'e2ee' in we2.ee), ensuring your conversations stay private.

Give it a try at we2.ee, even if some of this sounds like technical mumbo-jumbo. And feel free to tell me how you're using it—or don't, because that's kind of the whole point.

Cheers,

Better legal research using Raycast

Setting up Quicklinks for faster, more productive legal research

Hey folks, I put together another quick legal tech tutorial using macOS software—this time the incredibly powerful and useful Raycast. Raycast is a supercharged launcher and productivity tool that can dramatically streamline legal research workflows through its extensible commands, snippets, and AI capabilities.

As always, I've narrated a screenshare that shows how I use it in my legal practice. The screenshare covers a lot of ground, so let's break it down.

Most of my use cases for legal research involve custom web searches, called Quicklinks in Raycast. I explain how to set them up toward the end of the screenshare. Here are written instructions for convenience:

  1. Create a new Quicklink

Open Raycast Settings (i.e., by searching Raycast Settings within Raycast), switch to the Extensions tab, and click the + button, then Create Quicklink:

You'll now see the window for configuring a new platform for quick searches.

  1. Name the Quicklink

Under name, enter the name of the service you're configuring. You can enter an abbreviation here, e.g. fr for the Federal Register, but note you can separately assign an abbreviation as an alias later—so my approach is to spell out the name of the service in the name field.

  1. Determine the URL scheme

More important is the link field. To determine the correct entry, you should visit the site in question, use the built-in search feature or visit a page on the site you want accessible via Raycast, and examine its URL. Find the relevant variable(s). For instance, URLs for the CFR database Cornell Law School's Legal Information Institute look like this:

https://www.law.cornell.edu/cfr/text/50/402.02

That happens to be the link to the definitions section of the implementing regs for the Endangered Species Act, 50 C.F.R. § 402.02. You'll notice that the URL follows this pattern:

https://www.law.cornell.edu/cfr/text/{Title}/{Part}

And guess what? That's exactly what you enter in the link field in Raycast. Just make sure to wrap the field names in curly brackets, e.g., { and }.

That's it. Easy, right? Easy, and incredibly powerful.

Here are some of the URL schemes for sites I commonly use in my legal research, ready for your use in Raycast:

Federal Register - https://www.federalregister.gov/documents/search?conditions%5Bterm%5D={Query}

Westlaw - https://1.next.westlaw.com/Search/Results.html?query={Query}

U.S. Code (Cornell LII) - https://www.law.cornell.edu/uscode/text/{Title}/{Part}

C.F.R. (Cornell LII) - https://www.law.cornell.edu/cfr/text/{Title}/{Part}

eCFR - https://www.ecfr.gov/search?search%5Bdate%5D=current&search%5Bquery%5D={Query}

FRCP (Cornell LII)- https://www.law.cornell.edu/rules/frcp/rule_{Rule}

FRAP (Cornell LII) - https://www.law.cornell.edu/rules/frap/rule_{Rule}

RECAP (CourtListener) - https://www.courtlistener.com/?q=&type=r&order_by=score%20desc&docket_number={Docket}&court={Court}

I hope that's a helpful starting point for folks checking out Raycast for better legal research on macOS. And if you're a fellow Obsidian user and want to try out the Ultra Note or Reminder extensions I wrote for Raycast, have a look at the code here.

Cheers,

Better document review in DEVONthink with Bates deeplinks and AppleScript.

Hey folks, in this post I'm sharing and explaining a custom script I wrote for DEVONthink that greatly assists me when I'm reviewing large batches of Bates-stamped documents (e.g., administrative records or discovery dumps).

Specifically, this script will:

  1. copy selected text in a PDF to the clipboard;
  2. determine the Bates number of the page the selected text is on;
  3. generate a Markdown-formatted Bates cite for that page linking back to the document, page, and specific passage of text that was selected; and
  4. append this Bates 'deeplink' to the clipboard.

ⓘ About DEVONthink

DEVONthink is comprehensive document management and productivity software exclusive to macOS that has seen continuous updates, improvements, and new features added for over two decades. The developer calls it "your paperless office." That certainly rings true for me—it's been my go-to work and study app since I first started using it in law school 7 years ago.

Example usage.

Suppose I'm reviewing the file FWS 065382–065397.pdf near the end of a 75,000-page administrative record (true story).

Suppose I find something really incriminating on the sixth page of that document—a candid email exchange where a staff biologist wrote: "If we do this, Franklin's Bumble Bee will go extinct." (fictitious example).

If I select that sentence in the PDF and use the keyboard shortcut I've assigned to my script, ⇧ + ⌥ + C, the following is placed in my system clipboard:

"If we do this, Franklin's Bumble Bee will go extinct." [FWS 65387](x-devonthink-item://883C7BE0-A328-4818-A4B5-3AF7E5504135?page=6&start=534&length=53&search=If%20we%20do%20this%2C%20Franklin%27s%20Bumble%20Bee%20will%20go%20extinct.). 

With Markdown rendering, that becomes, "If we do this, Franklin's Bumble Bee will go extinct." FWS 65387.

Now, if I open my Markdown editor of choice—Ulysses, Obsidian, or DEVONthink itself depending on the task at hand (more on that in a future post)—and paste (i.e., ⌘ v), the text I selected, plus the correct Bates cite with a deeplink back to the source sentence in the PDF, is inserted.

I can then at any time simply click the Bates cite and get right back to the exact point in that specific document where the incriminating statement is found.

Deeplinks are neat, right? Let's set it up.

Pre-requisites.

  1. This tutorial assumes you have a macOS computer with Perl and DEVONthink installed. The standard version of DEVONthink will work but I do recommend buying the Pro version, among other reasons, for easier OCR. The agencies I sue often transmit non-OCR'd documents and DEVONthink Pro is a godsend when that occurs.
  2. The documents you're looking at should be Bates-numbered (though my script has a fall-back mode for non-Bates numbered documents—more on that below).
  3. The documents should be named according to their Bates starting number or their Bates range. The script I wrote can handle any of these filename conventions:

FWS 000533.pdf
FWS-000533.pdf
FWS_000533.pdf
NMFS 002561-002985.pdf
BLM 45.pdf
AR_45-62.pdf

As you can see:

  • the agency prefix doesn't matter;
  • the separator between the agency prefix and the pages number(s) can be (space), -, or _; and
  • the filename can include either the Bates number of the document's first page, or the Bates range of the complete document separated by -.

This makes the script flexible enough to cover the file naming conventions I see most often in my legal practice. But I have seen others that my script couldn't feasibly be made to accomodate, for example:

20170912 1813 Redacted.pdf
20170922 0000 CSERC Scoping comment letter transmittal email.pdf
20200828 County of Tuolumne transmittal submission.pdf
Butler and Wooster 2003.pdf
California Resources 2020.pdf
Cayan et al 2008.pdf
Crozier et al 2006.pdf

If you're working with documents that are Bates-stamped but use a different file naming convention, like the example above, the script won't work until you rename the files to a supported naming convention. But when you're wrangling a 2,215-document AR comprising over 75,000 pages (true story), it's infeasible manually rename them. That's why I wrote—

A nifty helper script to handle Bates documents with nonconforming filenames.

This helper script, bates, will batch-rename folders full of documents while preserving their original filenames as metadata. The Python-language helper script is quite complex in its own right— it can handle PDFs with multiple text layers, non-OCR'd PDFs, DRM-protected PDFs, PDFs where Bates stamps are inserted as annotations, and various different Bates stamp formatting conventions.

Find it here →

I've written complete documentation on installing and using bates here, but the basic usage goes like this:

bates "~/Cases/My Big Case/Adminstrative Record" \
    --prefix "BLM AR " \
    --digits 6 \
    --name-prefix "BLM " \
    --log INFO

In this example, the script will take every PDF file in the folder ~/Cases/My Big Case/Administrative Record, and search the first and last page for Bates stamps formatted like BLM AR ######. Once it finds them, it will stash the original file name in the file's Finder comment metadata field, then rename the file BLM {first page Bates number}-{last page Bates number}.

With this naming convention the DEVONthink script will work.

👍
I recently applied this helper script to the 2215-document / 75,000-page AR I already mentioned, and it successfully named every single document—even ones where Bates stamps were illegible because of overlapping text or dark backgrounds. It's quite robust if I may say so myself.
⚠️
This helper script stores the original file name in the Finder comment metadata field, to preserve that information and have it still readily accessible within DEVONthink, but you should nevertheless always work on copies of files as the script may result in destructive changes in edge cases.

Alright, with the prerequisites in place and our documents abiding a compatible filename convention, the actual script this post is about can work. I've written detailed documentation for it here, but it's actually quite simple to set up:

  1. Copy the script from here
  2. Open /Applications/Script Editor.app
  3. Paste in the script and save it (e.g. on your Desktop) as Bates Source Link.scpt
  4. Open DEVONthink 3.app and click the script icon in the menu bar (it looks sorta like §) → Open Scripts Folder, or alternatively open Finder and click Go in the menu bar → Go to folder... and enter ~/Library/Application Scripts/com.devon-technologies.think3
  5. Move the Bates Source Link.scpt to the Menu subfolder you should now see in the Finder window

That's it.

You can use the script now by clicking the script icon (§) in the menu bar and then Bates Source Link.

Assign a keyboard shortcut.

... but clicking the script menu and finding the right script each time becomes a bit cumbersome, right? I thought so, too, so let's configure a keyboard shortcut for it:

  1. In your menu bar, click  → System Settings → Keyboard → Keyboard Shortcuts → App Shortcuts
  2. Select DEVONthink 3.app and click +
  3. Enter exact script name as it appears in the script menu in DEVONthink, i.e., Bates Source Link
  4. Assign your desired shortcut. A good option that doesn't conflict with default shortcuts is ⇧ ⌥ b (Option + Shift + b).
💡
I use the free utility Hyperkey.app to expand my available keyboard shortcut. Using Hyperkey I've assigned ⇪ b (Caps Lock + b) to this script.

Handling non-Bates documents.

Not all documents I work with are Bates stamped, so I made the script handle other documents too.

When a document doesn't follow one of the Bates naming convention detailed above, the script will still work. But instead of determining the Bates number for the active page and formatting a deeplinked Bates cite, it will instead format a deeplinked generic cite like this:

"If we do this, Franklin's Bumble Bee will go extinct." [FWS email thread at 6](x-devonthink-item://883C7BE0-A328-4818-A4B5-3AF7E5504135?page=6&start=534&length=53&search=If%20we%20do%20this%2C%20Franklin%27s%20Bumble%20Bee%20will%20go%20extinct.).

With Markdown rendering, that becomes, "If we do this, Franklin's Bumble Bee will go extinct." FWS email thread at 6.

Conclusion

That's it for this tip, folks. Let me know in the comments if you use DEVONthink and decide to give my script(s) a whirl. And as always, feel free to expand their functionality on my repo at sij.ai. I'd be particularly interested if anyone knows how to handle rich text links in AppleScript, to make this compatible in other apps besides Markdown editors.

Cheers!