MyCase Immigration / Docketwise Integration: 0: Why We Synced · 1: Before/After · 2: OAuth2 · 3: Sync vs Migration · 4: Sync Boundary · 5: Family Graph · 6: Write Patterns · 7: Webhooks · 8: Billing
In August 2023, MyCase and Docketwise were two separate software products serving two overlapping markets.
MyCase: a Rails and React practice management platform for law firms. It handled billing, document management, calendaring, client communications, and trust accounting. It did not handle immigration specifically.
Docketwise: a FastAPI and Vue immigration case management platform. It handled USCIS form preparation, family graph tracking, visa category management, and immigration-specific matter workflows. It did not handle general practice management.
Immigration attorneys needed both. They ran MyCase for billing and client communications, Docketwise for immigration case work, and a manual process to keep the two in sync. When a client updated their contact information in one system, someone had to update the other. When a case status changed in Docketwise, MyCase did not know.
The integration product we shipped in October 2024, "MyCase Immigration, powered by Docketwise," eliminated that manual process. From an attorney's perspective, it is one product: one login, one client list, one place to access both practice management and immigration case work. From an infrastructure perspective, both systems are still running. Neither was replaced.
This post is the map for the series. It covers what the before state was, what the after state is, and what the technical problems were that the following posts cover in detail.
What MyCase attorneys did before
Before the integration, an immigration attorney using both products maintained parallel records.
In MyCase: every client had a Contact record with name, email, and phone. Every matter had a billing structure, deadline tracking, and a document folder. Client intake happened in MyCase. Invoices went out through MyCase. Trust account management was in MyCase.
In Docketwise: the same client appeared as a petitioner or beneficiary with typed party relationships (petitioner, beneficiary, spouse, children). Each case had an associated set of USCIS forms, a visa category, a matter type, and an immigration timeline. Case work happened in Docketwise.
The overlap was exactly Client and Case. When a new immigration client came in, the attorney created records in both systems. When the client changed addresses, they updated both. When a case received a USCIS approval, Docketwise knew; MyCase did not.
What the integration is
The integration surfaces Docketwise inside MyCase. Immigration case work happens through the MyCase UI. Docketwise handles the immigration-specific backend.
The sync boundary covers exactly two entities: Client and Case. An attorney's MyCase client list stays current with Docketwise. An attorney's case list in MyCase shows the Docketwise case alongside the billing and document work from MyCase. Immigration-specific fields (visa category, USCIS form associations, matter type, party role relationships) stay in Docketwise. Practice management fields (billing structure, assigned attorney, document folders, deadlines) stay in MyCase.
Neither system is a replica of the other. They share identity fields on Client and Case. Each owns a distinct field domain.
The two stacks
The integration crossed a stack boundary that could not be avoided.
MyCase: Rails 7 monolith, React frontend, PostgreSQL. Immigration case work surfaces inside the MyCase React application. MyCase's Rails controllers call Docketwise's REST API for immigration reads and writes. Docketwise webhooks post to MyCase to deliver updates.
Docketwise: FastAPI (Python), Vue frontend, its own PostgreSQL. It exposes a REST API that MyCase consumes. It sends webhooks on case and party updates. The Docketwise Vue UI was preserved for attorneys who use Docketwise standalone.
The integration did not consolidate the databases or the frontends. Attorneys on MyCase Immigration see Docketwise data through the MyCase UI. Attorneys on standalone Docketwise see the same data through the Docketwise Vue UI. Both UIs read and write from Docketwise. MyCase is the overlay for attorneys who need both practice management and immigration case work in one place.
The problems the series covers
A series overview post could stop at "we built a sync and it worked." What it cannot answer from that altitude: how did you guard against webhook ordering failures? How did the family graph schema mismatch get resolved? What specifically was hardcoded in billing and what replaced it?
The following seven posts each cover one technical problem in enough depth to explain the actual decisions.
What the series covers
Part 2 covers the OAuth2 integration that existed when we started and why its design was structurally wrong, not just poorly implemented. The Docketwise-centric grant model had the hosting relationship inverted.
Part 3 covers why we chose to build a permanent sync rather than migrate Docketwise data into MyCase. The migration argument sounds compelling until you look at what immigration data actually is.
Part 4 covers how we drew the sync boundary at Client and Case only. What the boundary excluded and the test we used to decide what crossed it.
Part 5 covers the family graph problem. Docketwise models clients as a typed party graph (petitioner, beneficiary, spouse, children with explicit role relationships). MyCase has a flat single-Client concept. This is not a field mapping problem.
Part 6 covers why two different write patterns (synchronous REST and async outbox) came out of a single architectural decision: source-of-truth partitioning. Transport follows ownership.
Part 7 covers a webhook ordering failure we found in staging: two rapid Docketwise updates, out-of-order delivery to MyCase, and a silent data rollback. The version guard and idempotency key that fixed it.
Part 8 covers the billing generalization. The trial, conversion, and cancellation lifecycle was wired to one specific addon. Making it work for Immigration without disrupting the existing Accounting addon required a service refactor above the provider layer.
Next: Part 2. The OAuth2 Integration That Was Never Going to Work. The integration inherited a grant model with the hosting relationship inverted. This post covers what was wrong with it and why the problem was structural, not implementational.
This post is the one to return to when a later post references "the before state" or "why both systems are still live." The series assumes familiarity with Rails, REST, and webhook-based integrations at a conceptual level.


Top comments (0)