writeout.ai: the queue setting that undoes the design
Transcribe and translate your audio files - for free
At a glance
- What is it?
- The interesting part of this project is a pipeline with two models in it and a format change in the middle: audio goes to a speech-to-text API, the transcript comes back as a timed subtitle file, and that file is chunked to fit a chat model's context window before it is translated. The design implies a queue, and the shipped example environment sets the queue to run synchronously.
- Who is it for?
- Adopt writeout.ai if you want a small self-hosted front end over the hosted transcription and translation APIs and are comfortable with metered cost, because the signup credit mentioned in the setup steps is the ceiling on how much you can try. Change the queue connection before you deploy rather than after, since a synchronous queue runs the transcription inside the request that uploaded the file.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 92 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two models and a subtitle file in the middle
The pipeline is short enough to describe in one sentence, and the interesting part is the middle of it.
Audio goes to a speech-to-text API. The response is a timed subtitle file in the WebVTT format. That file is split into chunks small enough to fit inside a chat model's context window, and each chunk is sent to a chat API for translation.
Choosing a subtitle format as the intermediate representation is a good decision, and worth saying why. You did not have to invent an intermediate structure: WebVTT is a standard with timings already in it, which means the transcript is immediately useful as a subtitle track before any translation happens, and the chunking has a natural unit to split on. A transcript-as-one-blob-of-text would have needed a segmenter written before anything else could work.
It also means the translation step is chunking by subtitle block, which is a mechanical rule rather than a semantic one. That is the design's main weakness, and it is a real one: a subtitle block is bounded by display timing, not by sentences, so a single sentence can straddle two chunks. A translation model asked to translate half a sentence produces something reasonable-looking and wrong, and nothing in the pipeline is positioned to notice.
Two models, two providers, two cost structures. Transcription is metered by audio duration and translation is metered by tokens, and the pipeline runs both for every file. That is worth understanding before you point it at a library of recordings.
The whole thing is described as being for free, which it is not in the sense that matters: the software is free, and the API calls are metered.
The queue setting contradicts the architecture
The README says transcription runs through Laravel's queued jobs. The shipped example environment sets the queue to run synchronously.
Those two statements are in tension and the resolution is not documented. A queue is what makes the design work: you upload a file, a job is dispatched, the request returns immediately, and the job makes a call to a speech API that takes anywhere from a few seconds to minutes depending on the recording. With a synchronous queue connection, all of that happens inside the HTTP request that handled the upload. The request stays open for the duration of the transcription, and when the platform's request timeout arrives, the job is killed and the file is not transcribed.
So a deployment that copies the example environment gets the architecture described in the prose and none of its benefits. It works, for short files, on a platform with a generous timeout, and it fails exactly where it matters.
The fix is one line in the configuration file, and the reason it is worth writing about is that nothing in the documentation points at it. There is no note saying to change this before deploying, no mention of what queue driver to use instead, and no background information about workers. For a project whose entire value proposition is that you drop in a repository and run it, an undocumented setting that changes the shape of the work is the most likely thing to cost somebody an afternoon.
The surrounding defaults tell you this is a local development configuration rather than a production one, which is fair. The app environment is local, debug output is on, and the mail host is a local catcher. The problem is not that the defaults are development defaults; it is that nothing distinguishes them from the settings you would ship.
Uploads to local disk, mail to a local inbox
Two more lines in the example environment are worth reading before you deploy, and one of them is a genuine trap.
The first pair is the filesystem and the queue:
QUEUE_CONNECTION=sync
FILESYSTEM_DISK=localLocal filesystem is the sensible default for an application that receives uploads, and the storage directory in the repository is where they will land. Combined with a synchronous queue, it means the whole job runs in one process on one machine, which is a coherent development arrangement.
The second pair is the mail configuration, and this one is a trap:
MAIL_HOST=mailpit
MAIL_PORT=1025That host name is a development mail catcher, a small service that accepts mail and shows it in a web interface instead of delivering it. If you deploy this file as-is, every confirmation email, password reset and notification goes into a local inbox that you will never look at. Nothing errors. The application will report success.
That is the failure mode worth naming: not an exception but a silent no-op. Account creation appears to work, the confirmation never arrives, and the cause is a hostname in a configuration file that nobody reads after the first deploy.
Whether it matters depends on how the application authenticates, and the example file also contains credentials for a GitHub sign-in flow, which suggests there is a second path that does not need mail at all. If that is how most people use it, the mail configuration is vestigial. If it is not, it is the first thing to change.
The database block has the same character: a local server, a root user, an empty password, and a schema name that says what the project used to be called before it was called writeout.
A JavaScript front end on a Laravel back end
The repository is a Laravel application with a separate JavaScript front end, and you can tell from the build configuration.
The JavaScript side has a dev script and a build script that both invoke the asset bundler, and the bundler is configured through a Laravel-specific plugin, which is how server-rendered projects hook a bundler into their own pipeline. But the application also depends on an HTTP client library, and there is no server-side rendering integration in sight. Together those two facts describe a single-page front end talking to a Laravel API rather than a Blade application with progressive enhancement.
That is the right choice for this application, and it follows from the pipeline. Transcription is a long-running operation with progress, results arriving in an order unrelated to submission, and a dashboard view of past jobs. None of that is a page render.
The dependency versions, though, are worth a look. The bundler is at a version 4 major, the CSS framework at a version 3 release from the end of 2022, the forms plugin at a version from the same period, and the HTTP client at a version 1 release from late 2022. The repository's last push is from June 2026.
So the front end is on a generation of tools that predates the project's recent commits by roughly three and a half years, pinned to caret ranges inside a lock file. Nothing here is unsafe, and the lock file means an install is reproducible. It does mean that if you need a feature that only exists in a newer bundler, you are looking at a dependency upgrade rather than a version bump, and it means the project's own tooling will start to need attention well before its application code does.
GitHub sign-in and a fixed budget for trying it
The setup instructions are short, and the second step in them is the one that determines whether you can use the application.
You create an account with the API provider, and the README notes that a free account comes with a small amount of credit. You then create a key from the account menu and put it in the environment file:
OPENAI_API_KEY=sk-...
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=That is the whole configuration. There is no model selection, no endpoint override, no local model option, and no mention of self-hosted inference.
The credit figure is worth taking seriously as a design constraint rather than as a footnote. Transcription is metered by audio duration, so the credit is a hard ceiling on how many minutes you can try, and translation is metered on top of that. If you are evaluating the application on a long recording, you may find the account exhausted before you have decided whether it works for you, and the README does not say what a minute of audio costs.
The GitHub credentials in the same block tell you the application authenticates users through a GitHub OAuth flow, which means your users need GitHub accounts and your deployment needs an OAuth app registered somewhere. That is a real deployment consideration and it is only discoverable from the configuration file.
None of this makes the project unsuitable. It makes it a thin, opinionated front end over two hosted APIs, which is a perfectly good thing to be, and it is a different thing from a transcription tool you run yourself. Knowing which one you are adopting is most of the evaluation.
Where writeout.ai is the wrong tool
Four cases.
If you cannot send audio to a hosted API, this is the wrong project, and not for a policy reason but a structural one. The transcription happens through the provider's API and the translation through a chat API, with no local path described anywhere. That is what the application is.
If your recordings contain more than one speaker and you need to know who said what, there is nothing here. The README does not mention diarisation or speaker labelling, and a subtitle format has no place to put it.
If you need an API rather than a web application, that is what this is not. There is no documented HTTP interface for submitting a file and retrieving a transcript, only a browser application with an upload form.
And if your recordings are long and the sentences matter, the chunking is the problem to test first rather than the model. Translation is chunked by subtitle block to fit a context limit, which is a mechanical split, and a sentence split across two chunks will be translated in two halves. Before you process a library, take one long recording and read the output at the block boundaries.
On maintenance: the licence is the permissive one, there are no tagged releases, the last push was on 2026-06-30, and the only funding line in the project is a referral link to another product from the same ecosystem. For a two-model pipeline with a chunking workaround in it, that is a thin maintenance base, and it is the main thing to weigh before making it load-bearing.
Editorial conclusion
Adopt writeout.ai if you want a small self-hosted front end over the hosted transcription and translation APIs and are comfortable with metered cost, because the signup credit mentioned in the setup steps is the ceiling on how much you can try. Change the queue connection before you deploy rather than after, since a synchronous queue runs the transcription inside the request that uploaded the file. Check the sentence boundaries in a long recording, because the translation step chunks a subtitle file to fit a context limit rather than segmenting on meaning. And expect the front-end dependencies to be from an older generation than the repository's last commit.
Frequently asked questions
How does writeout.ai transcribe and translate audio?
It sends uploaded audio to a speech-to-text API, receives the transcript as a WebVTT subtitle file, chunks that file into pieces small enough to fit the chat model's context limit, and sends each chunk to a chat API for translation. Laravel's queued jobs are used to do the transcription work.
What configuration does writeout.ai need?
An API key for the speech and chat provider placed in the environment file, a MySQL database reachable locally, and credentials for a GitHub OAuth application if you want users to sign in. The example environment file shows every setting, including storage, mail, cache and session drivers.
Why does transcription fail or time out on a long recording in writeout.ai?
The example environment file sets the queue connection to run synchronously, so the transcription job executes inside the HTTP request that handled the upload. A long recording then takes as long as the transcription, and a platform request timeout will kill it. The documentation does not mention changing this setting.
Is writeout.ai free to use?
The software is free and permissively licensed, but transcription and translation are metered through hosted APIs. The setup instructions note that a new provider account comes with a small amount of credit, which sets a ceiling on how much audio you can try.
Can writeout.ai tell speakers apart in a recording?
Nothing in the documentation describes diarisation or speaker labelling. The transcript is produced as a WebVTT subtitle file, which carries timings and text but no speaker identity, so separating voices is not part of what this application does.
What is writeout.ai built with?
A Laravel application with a separate JavaScript front end bundled with Vite and styled with Tailwind, using an HTTP client to talk to the back end rather than server-rendered templates. There is a localisation directory and a PHP test configuration alongside it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/beyondcode-writeout-ai)