Investor relations for ASX-listed companies, on one platform
The platform runs the whole investor relations function for listed companies. The share register, the public investor site, the announcement feed, shareholder communications, and the reporting that goes to the board.
- 26 modules, one codebase
- 22 external integrations
- 1 database per client
- 0 companies sharing a database
- 1 place to audit for tenant isolation
What the platform does
Investor relations is three jobs that most listed companies run in three separate places.
Know who owns you. The share register changes every trading day. People buy, sell, and move holdings between accounts. Knowing who actually holds your stock this morning is harder than it sounds, and it is the input to almost everything else.
Tell the market. Announcements, results, investor questions, and the public investor site the company gets judged on. All of it regulated, all of it time-sensitive.
Report to the board. Who bought, who sold, how the price moved, and what any of it means. Usually assembled by hand, monthly, by someone who would rather be doing something else.
The platform does all three. Our job was to build it so that it works when many listed companies use it at once.
- Client
- An investor relations platform for ASX-listed companies
- Market
- Australia, ASX small and mid-cap issuers
- Product
- Multi-tenant B2B SaaS
- Our scope
- Backend architecture, tenant provisioning, integrations, AI layer, cloud infrastructure
The one fact that shaped everything
Multiple listed companies run on the same platform. Some of them compete with each other, on the same exchange, in the same sector, chasing the same institutional money.
One company's shareholder list is exactly the list a rival would like to see.
That turns multi-tenancy from an infrastructure detail into the primary design problem, and it sets a standard higher than the usual one. Not "we filter carefully." The standard the design has to meet is that one company's data is not reachable from another company's session, including through code paths nobody has written yet.
There is a second fact of similar weight. This is a regulated disclosure surface. A half-published announcement, a shareholder email that reaches only part of the register, or an AI-written paragraph nobody approved are not degraded user experiences. They are market communication failures with a listed company's name on them.
Everything below follows from those two facts.
The three decisions that mattered
If you read nothing else on this page, read this.
Each company gets its own database
Isolation is a property of the architecture rather than a discipline the team maintains.
Nothing heavy runs where users are waiting
Work whose duration depends on how much data a client happens to have runs in short-lived compute that spins up, finishes, and terminates.
Nothing reaches the market unreviewed
Every AI output lands in a queue, not on an exchange. Every publish is explicit, atomic, and reversible.
A database per company
The default answer to multi-tenancy is one shared database with a tenant column on every table. It is a good answer for many products. It was the wrong one here, for three reasons.
It depends on everyone remembering. Separation holds only as long as every query, every join, every report builder and every scheduled job applies the tenant filter correctly, including the ones written next year by someone who has never read this page. With 26 modules and around 20 scheduled jobs touching shareholder data, the safety of the whole system becomes a function of the least careful query anyone writes in it.
The failure is silent. A missing filter does not throw an error. It returns more rows. No exception, no alert, no slow response. Just a correct-looking board pack containing another company's top twenty holders, and nothing in the system objecting.
It cannot be undone. One bad query is not one bad response. It is one listed company seeing a competitor's shareholder base and largest buyers and sellers. On top of the commercial damage sits personal information under the Privacy Act, confidentiality obligations around price-sensitive information, and an incident that is reportable, disclosable and permanent.
Reviewing every query forever is not a control. It is a hope with a maintenance schedule.
What we did
Each client company runs on its own dedicated database. No table anywhere holds two companies' shareholder records side by side.
Five things make that structural rather than a matter of convention.
Resolution comes before connection. Every request works out which company it belongs to before any database connection opens, and then opens against that company's database. The class of bug is not caught by a reviewer. It is left with nothing to do.
Isolation is provisioned, not configured. The database is created in the same automated onboarding run that creates the company record, the identity pool, the branding and the custom domain. There is no manual step in which to get it wrong, so a tenant does not come up half-isolated because somebody forgot one.
It covers background work too. Scheduled jobs process one company at a time, connecting to one database at a time. Background jobs are where shared-database isolation usually breaks in practice.
It sits behind a separate identity boundary. Each company also has its own identity pool, so users are separated at login and their data is separated at the connection. Two independent mechanisms, both of which would have to fail.
The credentials themselves are scoped. Application components connect with database users holding only the permissions they need, and no administrative credential exists in the application at all. This is the layer that matters if something does get through. An injection runs with the privileges of the credential it landed on, so the ceiling on the damage is set before the attack rather than negotiated after it.
The most likely cause of cross-company leakage was designed out. A query that forgets its tenant filter can no longer return another company's data, because there is nothing else in the database to return. The control is structural rather than procedural.
The isolation question shrank from 26 modules to one place. Instead of auditing every query in every module, forever, there is one thing to audit: how a request resolves to a connection.
One section of onboarding due diligence has a one-sentence answer. Your data is in your own database, with its own credentials, reachable only after your company is resolved. That sentence closes the section of an enterprise questionnaire that otherwise generates a fortnight of back and forth.
Data deletion went from a project to an operation. A company that leaves takes its database with it, so a contractual deletion obligation is one action rather than coordinated deletes across shared tables.
One client's data volume no longer slows another client's queries at the data layer, because they are not querying the same store.
What it costs
This is not free and we would not present it as free. Fixed infrastructure cost rises with the number of tenants. Schema changes have to run many times and need their own pipeline. Platform-wide analytics need a separate aggregation path.
Each of those has a bounded answer. The cost scales with paying customers, so it is marginal cost per revenue-bearing unit rather than fixed overhead. Migrations are a pipeline problem solved once. Analytics belong in an aggregation layer regardless. Connections are the fourth cost and the one worth spelling out, which is the next section.
The comparison is not this cost against free. It is this cost against one cross-tenant disclosure between two listed competitors, which is not a number anyone puts on a slide because the tail on it includes losing the client, the client's peers, and the ability to sell into the segment at all.
The question this architecture always gets
The challenge. A database per company changes the shape of the connection problem. In a shared-database design there is one pool and one ceiling. With a database per tenant, the naive implementation opens a pool per tenant, and connection count then grows with the number of customers rather than with the amount of work. At twenty tenants nobody notices. At two hundred the platform runs out of connections before it runs out of anything else, and that failure is total rather than gradual.
It is sharper than it first appears, because demand here is not proportional to paying users. Every client's investor centre is public by design, which is what makes it useful, and public means continuously visited by crawlers, scrapers and automated collectors. A request that reaches the application consumes a connection whether a person sent it or not.
What we did. Three things, at three different layers.
Connections are pooled in front of the databases rather than held per tenant. Application instances talk to a pooling layer, and the pooling layer multiplexes onto the databases. Connection count then tracks concurrent work rather than customer count, which is what makes the model hold as the client list grows.
Each tenant has a ceiling. Per-tenant connection limits mean one company's traffic spike, legitimate or otherwise, is bounded by that company's own allocation rather than drawing down the capacity every other company depends on. This is the same property the isolation model was chosen for, applied one layer down: a bad day for one client stays one client's bad day.
Junk traffic is turned away before it reaches the pool. Requests are validated at the perimeter against each company's own registered domains, a web application firewall filters automated traffic, and public investor surfaces carry a challenge before a request reaches the application. The cheapest connection is the one never opened.
Above the pool, application instances scale horizontally behind a load balancer, so capacity for real users grows with real demand rather than being sized for the worst hour of the year.
Onboarding the two hundredth tenant is closer to a commercial event than an architectural one. Connection count follows concurrent work, so customer growth stops being a capacity cliff.
A traffic spike on one client's investor site is contained to that client's allocation. The blast radius that database-per-tenant gives you at the data layer is preserved at the connection layer instead of being handed back.
Automated load is refused at the perimeter rather than at the database, which is what allows the public investor surfaces to stay genuinely public.
The failure mode changed from total to local. Platform-wide connection exhaustion is an outage. Per-tenant throttling is a visible, recoverable, attributable event.
Heavy work runs somewhere else
Investor relations has a load profile that punishes naive infrastructure. It is quiet for most of the month, and then it is reporting season, when every listed company on the platform releases results and emails its full register in the same week.
That peak is not random and it cannot be smoothed. It is a calendar event every customer shares. Provision for it all year and you pay twelve months for two weeks of use. Provision for the average and the platform struggles in the fortnight that matters most to every client at once.
What we did
Work is separated by what governs its duration.
Anything a user is waiting on runs on the main service, which carries no scheduled or batch work of its own.
Recurring scheduled work, around 20 jobs, runs on a dedicated worker: the nightly register sync, share prices through market hours, commodity prices, social statistics, email dispatch.
Work whose runtime depends on client data volume runs in short-lived compute that spins up, finishes and terminates. Large campaign sends, the nightly transaction sync, monthly board pack generation, and first-time data loads for a new client.
The rule is one line. If a job's duration is set by how much data a client happens to have, it does not run where users are served.
Peak capacity is billed by the minute rather than provisioned for twelve months. Infrastructure cost tracks real usage, and the busiest fortnight of the year stops being a line item the other fifty weeks pay for.
Response times stopped tracking the largest client's data volume. A company with ten times the register of its neighbour no longer sets the pace for the neighbour.
Scheduled work runs on time. The heaviest job of the day moved off the same machine as the twenty jobs that used to queue behind it.
Scaling the analysis stopped meaning scaling the website. Heavier processing can be added without resizing the service that serves users, which is what keeps the roadmap open.
The second spike
The challenge. A campaign is two load events rather than one. Sending to a full register is the first. The clicks that follow are the second, and they are larger and more concentrated, because thousands of shareholders open the same email in the same hour and follow the same link to the same document.
Serve that from the application and the platform absorbs the same campaign twice, the second time at higher intensity than the send itself.
What we did. Resolution of those links is cached, so a repeat click is answered without reaching the application and the database behind it. It is the same principle as the perimeter one, applied to traffic the platform generated itself: the cheapest request is the one that never reaches the database.
The traffic a campaign creates is decoupled from the work the platform has to do. A large send stops being a self-inflicted load test, which matters most in exactly the fortnight when every client is sending at once.
Nothing reaches the market unreviewed
Most AI content features produce fluent text that could have been written about any company in the sector. In a disclosure workflow that is worse than useless, because it is confident and unverifiable at the same time.
And the failure is invisible to monitoring. Nothing errors. Nothing times out. A well-written paragraph about the wrong company, or a well-written paragraph nobody approved, looks exactly like a working feature until it is on an exchange.
What we did
Three things make the output specific to the company using it.
It draws on that company's own history. Announcement content is indexed per company, so drafts and report commentary are grounded in what the business has actually told the market. An IR manager can check a claim against a source rather than trusting the model.
Voice is stored per company. A mining explorer and a fintech do not end up sounding like each other, because tone is configuration rather than one global instruction.
Different work goes to different models. Short, frequent tasks such as summaries and campaign copy are handled differently from long-form quarterly reporting. Cost stays proportionate to the work, and capability is spent where it changes the output.
Then the part that matters most in this industry.
Every AI output lands in a queue rather than on the market. Content is a draft until someone publishes it. Publishing is atomic, with a path back. Placement detection waits for approval before it surfaces. Whether an investor question is answered publicly or privately is a human decision.
And every one of those decisions leaves a record. A gate is only a control if somebody can show it was passed. Who approved a release and when, who moved something from draft to published, who decided an investor answer was public rather than private, and who approved a matched placement, each with an actor, a time, and the version that went out.
That is not a logging preference. Continuous disclosure obligations make the question of who released what, and when, a legal one rather than an operational one, and it is asked long after the fact by people who were not in the room. A platform that gates carefully and records nothing has built the control and thrown away the evidence for it.
The record is written where the application cannot revise it, because a trail the system can edit shows what the system currently believes rather than what happened.
AI became usable on a regulated disclosure surface. Without the gate, a generative feature is not shippable to a listed company at any quality of output. With it, the same feature is a drafting tool with a named person accountable for release.
Draft to published is a deliberate act rather than a save. A half-finished announcement does not reach a public endpoint, because public endpoints serve the published version only.
Output is checkable rather than trusted. Grounding drafts in the company's own disclosure history means a claim has a source, which is the difference between a tool an IR manager uses and one they quietly stop using.
A question asked six months later has an answer. Who released this, who approved it, and what did the version that went out actually say. Those arrive after an announcement has moved a price, and reconstructing them from memory is not an answer.
Tone changes are configuration, not a release. Improving how the platform writes for one client does not require shipping code.
The constraints we designed around
Platforms that carry real volume across real integrations meet constraints. Some belong to partners who will not change their systems for you. Some appear when a threshold is crossed. Some come from the industry itself.
Building for all of them on day one is not prudence, it is waste. The work is knowing which to design for up front, which to instrument and address at the threshold, and which are somebody else's decision to be routed around cleanly.
Every registry delivers a share register differently
The challenge. Three registry providers serve the Australian market and none of them agree on how a register should be handed over. Delivery method, security requirements and data shape all differ, and none of that is going to change to suit a platform.
What we did. Separated the mechanism from the contract. Each provider gets its own adapter. All of them normalise into one internal register model, and every module downstream reads that model without ever learning where the data came from.
Adding a fourth registry is an adapter rather than a migration. Which registry a prospect happens to use stopped being a sales qualification question, which widens the addressable market without widening the codebase.
Partner security requirements conflict with cloud defaults
The challenge. Financial data providers impose access requirements that a cloud platform's default behaviour does not satisfy. That is reasonable on both sides and irreconcilable in the abstract.
What we did. Contained it. One partner's requirement is met inside the integration that has it, rather than being allowed to reshape how the whole platform behaves.
The next partner with a similar requirement is a configuration change rather than a project, and no single integration's needs became everybody's problem.
Reporting season is a shared peak
The challenge. Shared infrastructure for shareholder email is the right design until several companies mail their full registers in the same window, which in investor relations is not a tail event. It is the calendar.
What we did. Above a set size, a campaign is given its own compute, so one company's send does not degrade another's. More importantly, completion is confirmed against the email provider's own delivery record rather than inferred from the absence of an error.
Campaign completion is measured rather than assumed. In a mail pipeline, "nothing crashed" and "everyone received it" are different claims, and only one of them is worth reporting to a listed company.
The heaviest job wants the busiest hour
The challenge. The nightly transaction sync has to finish before the market opens, which is also when announcements are released, IR teams log in, and twenty other jobs want to run. Putting it alongside the user-facing service means the hour with the most demand has the least headroom.
What we did. Moved it to its own scheduled compute, sized independently, running one company at a time.
The rest of the schedule runs on time instead of queueing behind the heaviest job on the platform, and a failure is attributable to one company rather than to a batch.
Investor sites are public by design
The challenge. A public investor centre has to be reachable by anyone with the link. That is the product. It also means every client site is continuously crawled by bots and automated collectors, and on shared infrastructure that load is paid for by every customer.
What we did. Requests are validated at the perimeter against each company's own registered domains, and automated traffic is turned away before it reaches the application or the database.
Capacity stays reserved for people. A crawl aimed at one company stays that company's problem instead of everybody's, and junk load is refused before it costs a single database connection. The connection economics behind that are covered under decision one.
Which criterion this is really about
Isolation reads as a confidentiality control, and it is one. The criterion that shaped more of this build is the less fashionable one.
Processing integrity asks whether a system does what it claims, completely and accurately. On a disclosure surface that is the whole job. Campaign completion asserted against the email provider's own delivery record rather than inferred from the absence of an error is a processing integrity control. So is a publish action that is explicit, atomic and reversible, and an AI output that lands in a queue rather than on an exchange.
The control set was designed against the trust services criteria rather than mapped to them afterwards, which means an organisation pursuing an attestation documents what already exists. That is a statement about how the platform was built, not a claim to hold a report.
The impact of these decisions, depending on who you are.
The impact, depending on your job
If you run the business
Isolation is a market entry requirement here, not a safety feature. Selling to listed companies means passing a security review, and a structural answer turns the longest section of that review into a sentence rather than a project.
The platform can serve competitors at the same time, which is what makes the addressable market the whole exchange rather than one issuer per sector. A shared-database design would have made every second customer in a sector a conversation about risk.
Extension is commercial rather than engineering. Adding a registry is an adapter, adding a client is configuration, so growth does not queue behind a roadmap.
If you run engineering
The isolation model is the part worth your attention. It is a structural control rather than a procedural one, which means your security review has one question to answer rather than 26. Onboarding due diligence gets a one-sentence answer.
The rest follows the same instinct. Workload separated by what governs its duration. Provider differences contained in adapters. Delivery confirmed rather than assumed.
If you run a delivery team
Most of what is on this page came from decisions about where complexity should live, not from clever code. Provisioning is one orchestrated run rather than a checklist, so a tenant does not exist half-configured. Adding a registry is an adapter. Adding a domain, a scheduled job or a tone template is configuration.
That is the difference between a platform and a series of bespoke builds wearing the same logo, and it is what keeps a team shipping features in year two instead of maintaining 26 special cases.
If you own the numbers
Peak capacity is bought by the minute, so infrastructure cost tracks real usage rather than worst-case provisioning.
Isolation cost scales with paying customers. It is marginal cost per revenue-bearing unit, not fixed overhead, and it buys down a risk whose tail includes losing an entire market segment.
Security review stops being a sales delay. The longest section of most enterprise due-diligence questionnaires has a structural answer, which shortens the gap between interest and signature.
Talk to the client, not just to us
Everything on this page is our account of our own work. If you are seriously evaluating us, we will arrange a reference call with a client who has been through a build like this one, and you can ask them the questions you would rather not ask us.
Technologies we built with
We name the layers rather than the suppliers on the data path, because a supplier map is our client's exposure rather than our credential. Full detail available under NDA.
More case studies
View all case studiesBuilding a financial platform that has to be right, and provable?
Tell us what you're building, and we'll tell you honestly how we'd approach it.