Case study 02 · Phishing-simulation platform · 2024 – present
Two years turning two incompatible phishing engines into one platform a non-technical manager can run
One click launches a realistic phishing drill, and every result lands in one dashboard.
Written by Hesham Ghoniem, lead engineer ·
- Sector
- Phishing simulation for security teams, MSPs and resellers
- Engagement
- Ongoing since 2024 · design, build, maintain
- Scope
- Full multi-tenant SaaS · dual-engine integration · billing
- Stack
- Laravel 11
- Vue 3
- Inertia
- Tailwind
- MySQL
- Redis
- Horizon
- 2 — Separate phishing engines unified into one workflow
- 7 — Live external services integrated
- 305 — Code changes merged across about 2 years
- 85 — Database changes shipped incrementally
- ~31,800 — Lines of application code
Overview
Many breaches begin with one click. We built the rehearsal.
Many security breaches begin with one employee clicking one convincing email. Our client needed a platform that could safely rehearse those attacks, so a company could see which of its people would fall for one and fix those weak spots before a real attacker found them.
CompertMe designed and built it. Since 2024 we have grown a single web app into a platform where a security team plans a realistic phishing drill, sends it to their staff, and watches in real time who spots the trap and who doesn't. The platform then turns the results into a report a director can act on.
What makes it unusual is how it runs underneath. It drives two completely different phishing engines at once, a standard one for everyday email drills and an advanced one for modern tricks such as fake login pages and QR-code scams, and it hides the difference behind one simple screen.
It grew in three stages: a single-company tool, then a full multi-tenant product that IT service providers resell to their own customers with each one kept separate, then the hard part, making the second, stubborn engine work at all. That piece took months and defines the whole story. Around it sat a quieter set of problems that turned out to be nearly as hard: showing a manager exactly what their staff will see, and bringing existing pages into the editor without breaking them.
The challenges
Ten problems, and the deepest one had no front door.
The standard engine, built for one team and asked to serve many
The everyday engine is respected open-source software, but it was built for a single security team testing itself. We needed it to run drills for many unrelated companies at once, without any of them colliding. Two problems took the longest.
- One shared namespace. The engine assumes every recipient list and sender is unique. Two customers both name a group "Sales Team", and to the engine that is the same group, so one company's drill could overwrite another's. We built the separation in underneath, and were still closing the last collision cases in 2026.
- Time zones. A drill scheduled for 9:00 AM kept going out at the wrong hour because the engine and the portal disagreed about time zones. It took a run of fixes through late 2025, and one more in 2026, to get send times right for every customer anywhere in the world. Nobody notices this bug until a campaign goes out at 3 AM.
One engine becomes two, and so does almost everything around it
For the platform's first fourteen months, there was only the standard engine. Every screen, service and database column assumed one engine, because only one existed. Then the advanced engine arrived. It was a second engine, not an add-on, and it needed its own recipient group, sending profile, campaign and email template, each kept in step with the original.
In early 2026 we copied the standard engine's campaign and email-template services outright, then grew the copies into the advanced engine's different shape. Four of the standard engine's services now have an advanced-engine twin. Tables that had held a single engine's identifier since the product's first weeks gained new columns a year later, and we backfilled every existing row so customers who signed up before the advanced engine did not look broken under a model built after them.
The advanced engine, powerful software with no front door
The advanced engine covers the attacks basic tools can't. It is very capable, but half of it was never built to be controlled by another program. The campaign half behaves like the standard engine and is easy to talk to. The other half, which stands in for the login page, is meant to be operated by a person typing at a terminal. It had no connection point for our portal. This became the deepest problem in the project, and it has its own panels below.
Two engines that must never disagree
A drill launches into one engine or the other, so a customer's recipient groups and sending domains have to exist in both and stay in step. For recipients, senders, templates and campaigns the two engines speak nearly the same language. The second also needs an extra setup step the first never did, and a kind of configuration that isn't an ordinary request at all. A save had to land correctly in both, and if one engine failed, the customer's data had to survive untouched.
A preview you can trust
Before a drill goes to two thousand people, the manager needs to see what they will see. But an email isn't a web page. Inboxes ignore styling, block images and strip anything clever, so a mock-up would not do. The preview had to be a real photograph taken by a real browser, made in the background, on servers that each needed that browser installed and found correctly. It took three generations to get right.
Any page, any way in
Nobody builds a phishing scenario from a blank page, so the platform lets people bring existing content in: a web address for a landing page, pasted HTML for an email. But pages are built a hundred different ways. Some arrive complete, some arrive empty until a browser assembles them, and most scatter their styling and images across other files. One fetch was never going to be enough. The importer became a ladder of up to three attempts with a clean-up pass behind it, and it is the largest piece of the service layer.
One platform, many companies, other people's brands
The same system had to sell as different plans with different limits and prices, appear under a reseller's own branding, and keep every customer's employee lists and results separate from every other customer's. That meant a three-tier hierarchy of portal owner, reseller and end customer.
Fair billing, and reports a director trusts
Customers wanted to pay only for what they use, with no surprise bills, and resellers needed to hand usage to their own clients, all self-service. A security manager also has to show the board how staff respond to phishing, which means turning raw tracking data into clean, branded reports automatically.
Building the server the advanced engine never had
Driving a terminal tool by hand is the visible half of the problem. Underneath it, something had to exist to be driven: a dedicated program on the advanced engine's own machine, built from nothing, because the login-page half of the engine shipped with no way for other software to talk to it.
Partners, a tier that had to be lived in before it was finished
Reselling isn't a box you tick. Getting from one company's tool to a tier where a reseller can run their own customers took a sustained run of releases, and parts of it were only found missing once a real partner started using it.
The advanced engine · no way in
The engine with no front door
The advanced engine lets the platform rehearse the attacks that fool even careful staff: convincing fake login pages and the QR-code scams now spreading through inboxes. The client needed it. One problem would not go away with planning. Half of the engine has no way for other software to talk to it.
The advanced engine is really two tools in one box. The campaign half covers recipients, senders, templates and sending, and it speaks nearly the same language as the standard engine, which is why so much of that code carried across. The other half plays the role of the login page.
Every other service we integrate has a front door, a documented connection point built for one program to talk to another. This half did not. It was designed for a person to drive live, typing commands into a terminal window and reading the replies on screen. From the portal's side, there was nothing to connect to.
We built the connection ourselves.
On the engine's own server we built a dedicated service whose job is to operate the tool the way a person would. It types the right commands into the live session, reads the responses off the screen, and hands a clean answer back to the portal. Our software now drives the tool by hand, at machine speed, the same way every time.
Then the plan and the server drifted apart.
The integration called for one operation above all others, "set this campaign up": create the route a QR code points to and hand it back. The server did not have it yet. Until it was written, the first QR drills produced codes that scanned to nowhere, because campaigns were created without their destination link. We wrote the missing operation and put the portal's steps in the right order: first create the route, then create the campaign with that route attached.
The engine also forgets.
Every change needs a full reload before it takes effect. Small naming rules broke things without any error until we found them, such as a template name with a space in it or a hyphen where the portal used an underscore. Stabilising all of it took a focused three-month effort, from the first integration at the end of 2025 to a solid QR-code drill in early 2026.
A single horizontal line with three markers: late 2025 the integration begins, then the missing operation is written, then in early 2026 QR-code drills are solid. Above the line, the stretch before the missing operation is marked red, the first QR codes scan to nowhere, and the stretch after it reads route first, then the campaign.
The customer clicks Launch. Underneath, the portal drives a tool that was never meant to be driven by software, and the customer never sees it.
The server side · built from nothing
Building the API that didn't exist
The panel above tells the human side of the problem: driving a terminal tool the way a person would, by typing into its live session. That only works because something else was built first, a program on the advanced engine's own machine that does the typing.
The campaign half already had an API. This half had no starting point. It had no official API and no unofficial one, because it was never written to be controlled by anything but a person. So we built a dedicated service from a blank file, on the server itself, with one job: give the portal somewhere to send an instruction and somewhere to ask a question.
The portal sends instructions to a control service we built; the control service drives the advanced engine the way a person would. A memory file inside the control service survives a restart. No part of it existed before this project.
More than twenty operations, none of them there on day one.
When it was finished it covered more than twenty operations. It checks whether the engine is alive and reads its status and configuration. It lists, reads, creates, edits, copies, switches on and off, and removes individual login-page templates. It sets up a whole campaign's route in one call, and lists and removes the links those routes produce. It restarts the engine cleanly, sends it a raw command, and saves and restores its state. We added each operation because something the portal needed had no other way to happen.
The engine has no memory of its own.
It has to restart often, every time a template changes, and it forgets everything it was told when it does. So the service keeps a small file recording what should be active: which templates, pointed at which domain, with which redirect. After every restart it reads that file back and returns the engine to its previous state. Without the file, a routine restart would wipe every customer's configuration.
Content tooling · the preview
The preview that had to be true
Before a security manager sends a drill to two thousand colleagues, they need to see exactly what those colleagues will see. A simulated phishing email that arrives with a broken layout, a missing logo or a button that goes nowhere doesn't test whether anyone can spot a scam. It looks like a mistake, and the result means nothing.
It sounds like a small feature. It took three attempts.
Three browser windows left to right: a live popup in late 2024 and a preview inside the campaign page in early 2025, both greyed as superseded, then a stored picture attached to every template in mid-2025, lit as the current one.
Each time the question was the same. How do we show the truth, fast, to many people at once?
An email is not a web page.
A modern browser draws a web page and forgives almost anything. Email programs forgive very little. They ignore most of the styling a designer writes, strip out anything clever, and often refuse to load pictures from elsewhere. A template that looks perfect in an editor can arrive looking like a different message. A guess would not do.
Two columns side by side: what the editor shows, with full styling, loaded images and intact layout, against what an inbox shows, with most styling ignored, images blocked and clever parts stripped.
The only honest preview is a photograph.
The portal runs a real, invisible Chrome browser on the server, opens the template or landing page the way a recipient's screen would, and takes a full-page picture. That worked, and it created new problems. The browser is a separate program that has to be installed, found and started correctly on every server, and production starts it differently from a laptop. We were still adjusting where it lives in early 2026, more than a year after the first version shipped.
Nobody should wait for a photograph.
Rendering takes seconds, and a manager with a library of templates can't be asked to watch each one. So previews are made in the background. When a manager saves a template, the portal moves on and a queue worker produces the picture. The picture then appears in the campaign builder, where the manager chooses what to send.
A preview that lies is worse than no preview, because the manager sends the drill believing they have seen it.
Content tooling · the importer
Any page, any way in
Nobody wants to build a convincing phishing scenario from a blank page. The natural starting point is something that already exists. So the landing-page editor lets a user type a web address and pull that page in, ready to adjust. For email templates, a user pastes in existing HTML and the portal cleans it so its styling survives an inbox.
It sounds simple. It became the largest single piece of the platform's service layer, at 809 lines.
Why one fetch was never enough
- Some pages arrive complete. Ask, and you get everything.
- Some arrive empty. Modern sites send a blank shell and assemble the page inside the visitor's browser, so a plain request returns nothing useful. For these the portal starts a real browser and waits for the page to finish building itself.
- Some respond differently depending on how you ask. What works on one site is ignored by the next.
- Almost all scatter their parts. Styling lives in separate files, images use shorthand that only makes sense on the original site, and scripts want to run inside our editor.
An ordered fall-through for importing a landing page by its web address: ask politely, then ask a different way, then open it in a real browser, with a clear error message if every attempt fails.
When the user already knows the page needs a real browser, it starts there.
The clean-up is where most of the work is.
The importer gathers the separate styling and writes it onto each element, because inboxes ignore style sheets. It turns every shorthand link into a full address so nothing breaks after the move. It can embed the images themselves, with a size cap so the template doesn't balloon, because inboxes often won't load them from elsewhere. It removes the scripts and tidies the markup so the editor accepts it, and carries across the page's background, font and colours so the editor looks like the page rather than a blank sheet.
The clean-up pass, in five stages
- Gather styles
- Fix links
- Handle images
- Remove scripts
- Tidy for the editor
Guardrails come with it.
Requests time out instead of hanging. A rate limit stops anyone hammering other people's websites from our servers. Scripts never reach the editor. The last dedicated round of improvements shipped in 2025.
The reseller tier · finished by use
Partners, the tier that had to be lived in
Reselling was always part of the plan. It was a line in the pitch and a box on a roadmap. In practice it took a sustained run of releases, because nobody can fully design a tier like this in advance. The only way to learn what a reseller needs is to give them a working version and see what breaks.
The core structure came first.
We added a layer above customer that could own several customers, roll up their results and allocate them campaign credit. We built and rebuilt it over roughly three weeks in mid-2025 as the real shape of the relationship became clear.
Then came the parts nobody had listed.
Support staff could already view a customer's account to help them, which we call impersonation, but the tool had only been built for one level of the hierarchy. It took over a year for someone to need the same thing for a reseller. We extended it to a second kind of account it was never written for, and we rewrote the on-screen banner so it says which kind of account is being viewed, not just "logged in as someone else".
The name changed too.
What launched as a generic "partner" turned out to be something more specific: an IT services company running phishing simulations for its own client base. Later in 2026 the label changed everywhere in the product from partner admin to MSP admin, because that is who had been using it all along.
A single horizontal line with three markers: the tier is built in mid-2025, impersonation is extended to a second kind of account over a year later, and the role is renamed to MSP admin later in 2026.
You can't design a reseller tier by thinking harder. You ship the smallest version, give it to a reseller, and let them tell you what is missing.
What we built
Three chapters, two engines, and one click that never changed.
Chapter one · 2024 · The single-company portal
One job, done well
We started with one job done well: letting a security team run a standard email phishing drill from end to end. A user verifies a sending domain, uploads a staff list, picks a template, schedules it, sends a test to themselves, launches, and watches the results come in: who opened, who clicked, who handed over a password. Behind it sat the first engine, an open-source phishing-simulation framework, plus the billing, templates and reporting that make it a product rather than a script. Previews of templates and landing pages arrived in the first months, at the end of 2024.
Chapter two · 2025 · From one company to many
A tool becomes a platform
The product had to be sellable through other people. Over the middle of 2025 we rebuilt the foundation into a three-tier structure, with the portal owner at the top, resellers in the middle and their customers underneath. We gave each kind of account its own role so one reseller's customers stay separate from another's. Every list, sender and result belongs to the company that owns it, and the queries that read them are scoped to it.
This turned a tool into a platform other businesses can put their own name on, although, as the panel above tells it, the reseller tier kept teaching us what it needed well into the following year. The content tools matured that year too: mid-2025 brought stored preview pictures for every template and landing page, and later in 2025 came a rebuilt importer for bringing existing pages into the editor.
Chapter three · 2026 · The second engine, and the modern attacks
One drill, either engine
This is the hardest chapter, told in the first two panels above. We built the service that gives the login-page half of the advanced engine an API it never had, integrated that engine through it, built the login-page template system behind realistic fake-login drills, and added QR-code phishing, a newer attack method that older email filters can miss. From then on a single drill could launch into either engine from the same screen.
Throughout
One click, two engines
The point of the whole build is that the person using it never learns any of this exists. They set up a drill once. The platform sends their recipient groups and sending domains to both engines and keeps them in step, launches the drill into the right engine, tracks every recipient individually, meters the cost and keeps each company's data separate. The complexity is real, and we carry it so the customer doesn't have to.
Technical deep-dive · one action, two engines
Inside the hardest integration
The defining engineering problem of the platform is neither engine on its own. It is keeping both in step from a single click. The two engines speak nearly the same language for recipients, senders and campaigns, but the second also needs an extra setup step and the control service we built to drive its login-page half.
The naive approach is to send to one engine, then the other, and hope. We rejected it. If the second send failed halfway, a recipient group or sending domain would exist in one engine and not the other, with no clear way to tell. In a security product, a half-configured setup is worse than none.
So every action fans out through one controller.
When a customer saves a recipient group or a sending domain, a single coordinator pushes it to both engines and records the outcome of each independently. A failure on either engine never touches the customer's data in the portal. The record is saved, the engine that succeeded is marked done, and the engine that failed is flagged with a one-click "resync" waiting. Nothing is lost, nothing is left half-done, and no external failure can corrupt what the customer owns.
One saved record enters the portal, which is the source of truth. Two arrows run out from it, one to the standard engine kept in step, one to the advanced engine shown broken with a one-click resync waiting. The arrows only ever point outward, and a broken one never damages the source.
The arrows only ever point outward from the portal, and a broken one never damages the source.
That one decision is what makes the system trustworthy.
Each record carries its own sync status for each engine, so at any moment the platform knows what reached the standard engine, what reached the advanced engine, and what needs a retry.
Around that core sit the background workers that keep results flowing. The platform polls the engines on a timer to pull back opens, clicks and submissions, marks drills complete when their window closes, and files the monthly reports, all without anyone pressing a button.
A saved recipient group or sender has to reach both engines. When one fails, the customer's data stays safe and the failure is flagged, never hidden.
How we work
Twelve rules, starting with one source of truth.
The portal is the source of truth
Where the product depends on an outside engine, we treat the engine as a downstream copy we keep in step, never as the master. A customer's data lives in our database and stays safe whatever the engines do.
A failure in one place must not corrupt another
Syncing to an external engine can fail. Saving the customer's own record cannot. We separate the two on purpose, so an outside outage flags a retry instead of losing work.
Hide the complexity
The person clicking Launch should never learn that there are two engines, a terminal driven by software, or a restart happening in the background. We carry that complexity so they don't have to.
Copy it, then let it diverge
When the second engine arrived mid-project we did not redesign from scratch. We copied what already worked for the first engine and grew the copy into the second engine's different shape, service by service and column by column, until the one-engine assumptions were gone.
Build the connection when there isn't one
When an essential tool had no way for software to talk to it, we did not drop the feature. We built a service of more than twenty operations on its own server, gave it a memory file so a restart doesn't wipe a customer's setup, and wrote the missing operations when the plan turned out to be ahead of the code.
Ship the smallest version, then let real use finish the design
Some things can't be fully planned, and a reseller tier least of all. We shipped a working version, watched where it fell short with real partners, and extended it twice instead of guessing everything up front.
Show the truth before the send
A drill that goes out looking broken proves nothing. Every template and landing page gets a real rendered preview, a picture taken by a real browser, before anyone presses Launch.
Try the easy way, then the next, then give up politely
Anything that depends on the outside world, such as a website, a server or a browser, gets a series of fallbacks and a readable error at the end, never a blank screen.
Run slow, unattended work in the background
Previews, results polling and reports run on a queue, and the result simply appears.
Start narrow, then widen
We shipped a working single-company product first and proved it, then grew it into the multi-tenant, resellable, dual-engine platform once the model was sound. We did not bet everything on one big launch.
Ship in small steps
Two years of steady change, 305 merged changes, each small enough to test and reverse, instead of one risky big bang. Roughly one in five is a fix, which is what iterative hardening looks like.
Settings, not redeployments
Scoring, plan limits, QR sizing, credit rules and pricing live in configuration, so the business can change its commercial rules without waiting on us.
Technologies used
Ten layers, and what each one is for.
Application
A mature framework that is fast to build on, with a large hiring pool.
- PHP 8.2
- Laravel 11
Interface
An app-like experience with no page reloads, across 180 screens and components.
- Vue 3
- Inertia
- Tailwind
- Vite
Data and scale
Heavy work such as sending, tracking and reporting runs in the background, never while a user waits.
- MySQL
- Redis
- Laravel Horizon
Phishing engines
The two simulation backends, one standard and one advanced, unified behind one portal.
- An open-source phishing-simulation framework
- A second engine built on a reverse-proxy tool
Payments
Self-service credit purchases in multiple currencies, with promo codes and automatic invoices.
- A payment platform with credit metering
Email and alerts
Reliable delivery of reports and notifications, and instant alerts to the operations team.
- A transactional-email service
- Team-chat alerts
Sign-in
Standard secure login, with enterprise single sign-on when a customer needs it.
- Jetstream
- Optional single sign-on
Documents and previews
Auto-generated template previews and board-ready PDF reports that match the screen.
- Headless Chrome
Content tools
Bringing existing pages into the editor, making email styling survive an inbox, and rendering pages the way a real browser does.
- TinyMCE editor
- DiDOM parser
- Emogrifier inliner
- Puppeteer
Cloud
File storage for login-page templates and payment records.
- AWS S3
Automated testing
Automated tests check each change before it reaches production.
Integrated external services, by what they do
- Standard phishing-simulation engine
- Advanced phishing-simulation engine
- A payment platform
- A transactional-email service
- A team-chat tool
- An identity provider, for optional single sign-on
- AWS S3
Seven live external services in all.
How the platform works
Six steps, from a sender domain to a board-ready report.
Step 1
Set up your sender
A company adds its email domain, and the platform walks it through verifying ownership so drills land reliably. It blocks unverified domains from sending, to protect the company's reputation.
Step 2
Load your people
Upload a staff list from a spreadsheet, or reuse a saved group. The platform validates it and flags any problems before anything is sent.
Step 3
Pick a scenario
Start from the ready-made library, clone one of your own, build one in the editor, paste in existing HTML that the portal cleans so its styling survives an inbox, or pull an existing page into a landing page by its web address. Every choice shows a true preview, a picture taken by a real browser, and you can send a test to yourself first.
Step 4
Launch into either engine
One click. Behind it, the platform launches the drill into the right engine, standard or advanced, keeps the recipients and senders in step across both, and starts tracking every recipient individually. Credits are metered so there are no surprise bills.
Step 5
Watch live
A dashboard shows who opened, who clicked and who fell for it, with timestamps and location, as it happens. When the drill's window closes, it wraps up on its own.
Step 6
Report
The platform turns the results into a clean, branded report a manager can forward to the board, and it can email monthly summaries automatically. Resellers see their whole customer base rolled up in one view.
Behind all of it
Each company's data is kept separate from every other's. Resellers manage their customers under their own brand. Credits, invoices and plan limits run themselves.
Results
What the business can do now that it couldn't in 2024.
Rehearse modern attacks, not just old ones
Standard email drills, fake-login drills and QR-code phishing, the newest method, all from one screen. This is what separates it from simpler simulators.
See it before you send it
Every template and landing page carries a real rendered preview, made in the background, so a manager checks what recipients will see rather than what the editor shows.
Start from anywhere
Library, clone, editor, pasted HTML, or an existing page pulled in by address. The importer tries up to three ways of reading a page and cleans up the result for the editor.
Drive the half of an engine that has no API
A service of more than twenty operations, with its own memory of what should be active, sits between the portal and the advanced engine's login-page half, so a routine restart doesn't cost a customer their configuration.
Sell through other people
Resellers run their own branded customer bases through a tier that was extended twice as real partners used it. It was most recently renamed to match who was running it: MSP admin.
Keep many companies apart
Every record belongs to a company, so each customer sees only their own staff and results.
Launch into either engine
A single click launches a drill into the right engine, and the groups and senders behind it stay in step across both. Either engine can fail without costing the customer their data, and a one-click retry replaces lost work.
Bill itself
Self-service credit purchases, multiple currencies, promo codes, automatic invoices and reseller credit grants, with no spreadsheets.
Report without effort
Raw tracking data becomes a branded PDF and an automatic monthly email a director can act on.
Run unattended
Timed background jobs run everything from minute-by-minute results polling to monthly executive reports with nobody in the loop.
The engineering, in numbers
- 2 Phishing engines unified into one workflow
- 7 External services integrated
- 22 Data models
- 32 Services, 14 of them dedicated to the two engines
- 85 Database changes shipped incrementally
- 180 Vue screens and components
- 3 Attempts the page importer makes before giving up
- 809 Lines in the page importer, the largest service
- 20+ Operations in the advanced engine's control service, built from zero
- 4 Standard-engine services that now have an advanced-engine twin
- 305 Code changes merged across about 2 years
- ~31,800 Lines of application code
Architecture decisions
These are the choices that make the platform trustworthy rather than just functional:
The portal is the source of truth
External engines are copies kept in step, never the master, so a customer's data is safe whatever the engines do.
Non-blocking dual-sync
Saving a customer's record and syncing it to an engine are deliberately separate, so an outside failure flags a one-click retry instead of corrupting or losing data.
Tenant scoping
Queries scope data to its owning company, so each customer works only with their own records.
Audit trails and soft deletes throughout
Major records track who created and changed them, and they are archived rather than deleted, so historical drill data stays available and accountable.
Honest status
The platform is live, working and actively developed, and every count on this page comes from the current codebase. It has no verified business metric: no number of customers, drills run, revenue or click-rate reduction. The page is written around what the software does and how it was built, not around usage figures, because one invented statistic would discredit the rest.
Related work
Cyber Exposure Platform
Cybersecurity · 2021 – present · Build, run, maintain
10 — Security services, five feeding one score
Air Cargo & Customs Platform
Logistics · 2025 – 2026 · Specify, build, deploy
7 days — Empty repository to a live, documented API. Both sides signed the contract on it
Next step
Have a product that depends on tools that were never meant to connect?
Work with an engineering team that builds the integration when there isn't one, keeps your data safe when an outside service fails, and hides the complexity so your customers never see it.
Client anonymised · every figure counted from the source code · October 2026