Vibecode Mattermost Professional
track this build5 phases, 15 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Mattermost Professional, self-host team chat with channels, threads, search, and integrations. The hard boundary is managed operations, mobile push, enterprise security, support, and governance, plus network, moderation, and communication infrastructure.
You are building a lean indie version of Mattermost Professional. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Mattermost Professional · indie build Mattermost on a server you own, behind TLS with a real certificate, connected to an email provider that delivers, with a deliberate decision about mobile push, retention and moderation policies written down, backups of the database and the uploaded files together, and a restore you have performed. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Application | Mattermost, self-hosted | channels, threads, search and clients already exist; writing them is the thing rule zero prevents | | Database | PostgreSQL | what the application expects, and the thing whose backup actually matters | | Files | Local disk or S3-compatible storage | uploads are the other half of a working restore and are easy to forget | | Edge | A reverse proxy with automatic certificates and WebSocket support | chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly | | Email | A transactional provider with a verified sending domain | invitations, password reset and notification emails all depend on delivery | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Linux server meeting the application's requirements** · typically 10 to 30 US dollars a month for a small team - Why: Chat is latency sensitive in a way people notice, and an undersized server produces a sluggish client that everyone comments on. - Get it: Check the project's current requirements for your team size, then provision accordingly. - Verify: The application starts and a client connects promptly - [ ] **A second server for the restore drill** · a few US dollars if deleted the same day - Why: Restoring onto the machine that already has the files proves nothing, and the missing file storage is exactly what a database-only backup leaves out. - Get it: The same provider, smallest suitable plan, destroyed after the drill. - Verify: You can SSH to it - [ ] **A domain with DNS you control** · roughly 10 to 15 US dollars a year - Why: Needed for the certificate, the site URL and the email sending records. - Get it: Any registrar, then add the A record and the email verification records. - Verify: The hostname resolves and serves a valid certificate - [ ] **A transactional email provider with a verified domain** · free tiers cover a small team - Why: Invitations and password resets both depend on delivery, and mail sent from the server directly will land in spam. - Get it: Create an account, add the DNS records, take the credentials. Verification is the slow step. - Verify: An invitation arrives at three providers and is not in spam - [ ] **A decision about mobile push notifications** · varies by arrangement; check current pricing rather than assuming - Why: This is the constraint that surprises people after migration. Notification certificates belong to the published applications, so a self-hosted server cannot send to them directly. - Get it: Check the project's current options for push, including any hosted proxy service and its cost, or the effort of building and distributing your own mobile applications. Decide before the team migrates. - Verify: A backgrounded mobile application receives a notification, or the limitation is documented and communicated - [ ] **Retention and moderation policies** · free - Why: Chat accumulates informal conversation quickly, and both what you keep and who decides what is acceptable are decisions with weight once a team relies on it. - Get it: Write both before inviting anyone. One page each is enough. - Verify: Both are written and the settings match them ## Quick start ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. - managed operations, mobile push, enterprise security, support, and governance - network effects - native mobile apps - video and voice infrastructure - enterprise moderation and discovery If one of those is essential to you, that is the reason to keep paying for Mattermost Professional, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Mattermost Professional The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me self-hosted team chat you operate, with the mobile notification problem understood before you start to replace Mattermost Professional. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a chat server. Mattermost is open source and this is a deployment task: run it, secure it, back it up, and operate it. Every phase below is deploy, configure and verify, and the last is a restore drill you actually perform. Understand the mobile notification constraint before you commit, because it surprises people after they have migrated a team. Push notifications to the official mobile applications go through a push proxy, since the notification certificates belong to the published applications rather than to your server. You either use the project's proxy service or build and distribute your own mobile applications with your own certificates. Check the current arrangement yourself, and decide before the team depends on it, because chat without reliable mobile notifications is chat people stop reading. ### Stack (fixed, do not substitute) - Application: Mattermost, self-hosted · channels, threads, search and clients already exist; writing them is the thing rule zero prevents - Database: PostgreSQL · what the application expects, and the thing whose backup actually matters - Files: Local disk or S3-compatible storage · uploads are the other half of a working restore and are easy to forget - Edge: A reverse proxy with automatic certificates and WebSocket support · chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly - Email: A transactional provider with a verified sending domain · invitations, password reset and notification emails all depend on delivery ### Data model (create this before Phase 1) - The application owns its schema. What you own is the operational state, and it is worth recording. - instance · version, host, database, file storage location and the deployment method - identity · how people sign in, and whether accounts are invite-only - push · which arrangement you chose for mobile notifications, and what it costs - retention · per channel kind, with the reason and the policy that requires it - backups · database and files, with the date of the last restore ### Phase 1 · Running behind a proxy that understands sockets Build: Provision a server, run the application and PostgreSQL, and put a reverse proxy in front with automatic certificates and WebSocket support. Set the site URL correctly. Then leave a client connected for an hour and confirm it did not silently disconnect. Trap: A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. Done when: The application serves over HTTPS with a valid certificate. A client left connected for an hour with no traffic is still connected rather than having silently reconnected. The site URL matches the domain, verified by checking a generated link. A server restart brings everything back automatically. Do not build yet: No email, push, retention or backups. ### Phase 2 · Email that arrives Build: Configure a transactional provider and verify the sending domain. Send an invitation to a real address at three different mail providers and confirm none land in spam. Then invite the first real member. Trap: Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. Done when: An invitation arrives at three different mail providers and is not in spam. A password reset link works. The sending domain is verified with the records the provider requires. A bounced address is visible in the provider's dashboard rather than silently dropped. Do not build yet: No push decision, retention or backups yet. ### Phase 3 · The mobile push decision Build: Decide how mobile push will work and implement it. Check the current options and their costs yourself rather than assuming. Then test on a real phone: send a message with the application backgrounded and confirm a notification arrives. Trap: Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. Done when: A message sent while the mobile application is backgrounded produces a notification on a real phone. The chosen push arrangement and its cost are written down. If push is not available, the team has been told before migrating and the limitation is recorded. Notification content settings match your privacy expectations, since previews appear on a lock screen. Do not build yet: No retention or backups yet. ### Phase 4 · Access, retention and moderation Build: Restrict signup to invitation or a verified domain. Write a retention policy per channel kind with its reason and configure it. Write a moderation policy and set the permissions to match. Enable two factor for administrators. Trap: Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. Done when: Signup is restricted and an uninvited address cannot create an account, tested directly. A retention policy is configured and its reason is documented per channel kind. Administrators have two factor enabled. A written moderation policy exists and the permission settings match it. Do not build yet: No backups or restore yet. ### Phase 5 · Backups of both halves Build: Nightly database dumps and file storage backups, dated, to a destination that is not this server. Alert if a night passes with no backup. Keep several generations. Trap: A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. Done when: Both a database dump and a file backup land off the server nightly. A missing backup produces an alert. Several generations are retained. The backup includes the configuration needed for a working restore, not only the data. Do not build yet: The restore drill is next and it is the one that counts. ### Phase 6 · The restore drill and upgrades Build: Restore the database and files onto a clean server, sign in, and open a channel with an attachment. Record the date and duration. Write an upgrade procedure that backs up first and read the release notes for breaking changes. Trap: A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. Done when: A clean server serves the restored instance, an existing user can sign in, and an attachment from before the backup downloads correctly. The date and duration are recorded. An upgrade has been rehearsed on the second server before the live one. The previous version remains deployable. Do not build yet: Nothing after this. ### Out of scope (and why) - Managed operations. Patching, upgrades, certificate renewal and capacity are now yours, and chat is something people notice immediately when it is down. - Mobile push through the published applications without arranging it deliberately, which is the constraint most likely to surprise you. - Enterprise security and governance features, compliance exports and the controls larger organisations require. - The network effects of a platform your contacts are already on, which no amount of self-hosting recovers. ### README must contain - That mobile push requires a deliberate arrangement because notification certificates belong to the published applications - That the reverse proxy must support long-lived WebSocket connections, with the idle timeout verified - That signup is restricted and how a new member is invited - The retention policy per channel kind and the reason for each - That backups cover the database and the file storage, with the date of the last restore ===== AGENTS.md ===== # Agent instructions · Mattermost Professional indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Mattermost, self-hosted, PostgreSQL, Local disk or S3-compatible storage, A reverse proxy with automatic certificates and WebSocket support, A transactional provider with a verified sending domain. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ===== BUILD_PLAN.md ===== # Build plan · Mattermost Professional Mattermost on a server you own, behind TLS with a real certificate, connected to an email provider that delivers, with a deliberate decision about mobile push, retention and moderation policies written down, backups of the database and the uploaded files together, and a restore you have performed. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Running behind a proxy that understands sockets Chat works, and it keeps working when a conversation goes quiet for an hour. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and web ports only. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` 2. Run the application and database Following the project's own deployment guidance for the version you are using. Files: `docker-compose.yml` 3. Configure the proxy for long-lived sockets Upgrade headers and a generous idle timeout. Verify by leaving a client idle for an hour. Files: `Caddyfile` ### Done when - [ ] The application serves over HTTPS with a valid certificate - [ ] An idle client stays connected for an hour - [ ] Generated links use the correct site URL - [ ] A server restart restores service automatically ### Watch out - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. ## Phase 2 · Email that arrives Invitations and resets reach inboxes, because everything else depends on them. ### Steps 1. Verify the sending domain The DNS records your provider specifies. Propagation takes hours, so this is why it is early. 2. Test across providers Three different ones, checking spam in each. One provider proves nothing. 3. Confirm the reset flow Not just the invitation. It is the one people need when they cannot get in. ### Done when - [ ] An invitation arrives at three providers and avoids spam - [ ] A password reset link works - [ ] The sending domain is verified - [ ] Bounces are visible rather than silent ### Watch out - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. ## Phase 3 · The mobile push decision Mobile notifications work, or the team knows in advance that they will not. ### Steps 1. Choose the push arrangement deliberately Check the current options and costs. Write down what you chose and why. Files: `docs/push.md` 2. Test on a real device Backgrounded application, real message, real phone. This is the only test that counts. 3. Decide what a notification reveals Message previews appear on lock screens. Decide whether that is acceptable for your team. ### Done when - [ ] A backgrounded mobile application receives a notification - [ ] The push arrangement and cost are written down - [ ] If push is unavailable, the team knows before migrating - [ ] Notification preview content matches your privacy decision ### Watch out - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. ## Phase 4 · Access, retention and moderation The right people are in, old messages do not accumulate forever, and someone is responsible. ### Steps 1. Restrict signup Invitation only or a verified domain. Test with an outside address. 2. Write and configure retention Per channel kind with the reason. Chat accumulates and much of it should not be kept forever. Files: `docs/retention.md` 3. Write the moderation policy What is not allowed, who decides, how to appeal. Before the first incident rather than during it. Files: `docs/moderation.md` ### Done when - [ ] An uninvited address cannot create an account - [ ] Retention is configured with a documented reason - [ ] Administrator accounts have two factor enabled - [ ] The moderation policy exists and settings match it ### Watch out - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. ## Phase 5 · Backups of both halves The database and the uploaded files are both backed up, off the machine. ### Steps 1. Dump the database nightly Dated, compressed, off the machine. Files: `scripts/backup.sh` ```sh pg_dump "$DATABASE_URL" | gzip > chat-$(date +%F).sql.gz ``` 2. Back up file storage Same schedule, same destination. Uploads are half the content people care about. Files: `scripts/backup.sh` 3. Alert on a missing backup Silence is not evidence a backup ran. Files: `scripts/check_backup.sh` ### Done when - [ ] Database and file backups both land off the server nightly - [ ] A suppressed backup produces an alert - [ ] Several generations are retained - [ ] Configuration is included in the backup ### Watch out - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ## Not in this build - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. ## After v1, if you want it - Single sign-on against an identity provider you already run - A staging instance restored from last night's backup, which exercises the restore weekly - Automated attachment retention, since files rather than messages are what fills the disk ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Appears in every link and email. Correct before the first invitation. MM_SERVICESETTINGS_SITEURL=https://chat.example.com # Required · secret. Database connection. Half of what a restore needs. MM_SQLSETTINGS_DATASOURCE=postgres://user:pass@db:5432/mattermost?sslmode=disable # Required. Uploaded files. The other half of a restore, and the half people forget. MM_FILESETTINGS_DIRECTORY=./data/files # Required. Transactional provider host. MM_EMAILSETTINGS_SMTPSERVER=smtp.provider.com # Required · secret. Provider credential, on the server only. MM_EMAILSETTINGS_SMTPPASSWORD=replace-with-provider-password # Required. Which push arrangement you chose. Recorded here so it is not forgotten. PUSH_ARRANGEMENT=documented-in-docs-push # Required. Where both nightly artefacts go. BACKUP_DEST=s3://your-bucket/chat
You are building a lean indie version of Mattermost Professional. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Mattermost Professional · indie build Mattermost on a server you own, behind TLS with a real certificate, connected to an email provider that delivers, with a deliberate decision about mobile push, retention and moderation policies written down, backups of the database and the uploaded files together, and a restore you have performed. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Application | Mattermost, self-hosted | channels, threads, search and clients already exist; writing them is the thing rule zero prevents | | Database | PostgreSQL | what the application expects, and the thing whose backup actually matters | | Files | Local disk or S3-compatible storage | uploads are the other half of a working restore and are easy to forget | | Edge | A reverse proxy with automatic certificates and WebSocket support | chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly | | Email | A transactional provider with a verified sending domain | invitations, password reset and notification emails all depend on delivery | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Linux server meeting the application's requirements** · typically 10 to 30 US dollars a month for a small team - Why: Chat is latency sensitive in a way people notice, and an undersized server produces a sluggish client that everyone comments on. - Get it: Check the project's current requirements for your team size, then provision accordingly. - Verify: The application starts and a client connects promptly - [ ] **A second server for the restore drill** · a few US dollars if deleted the same day - Why: Restoring onto the machine that already has the files proves nothing, and the missing file storage is exactly what a database-only backup leaves out. - Get it: The same provider, smallest suitable plan, destroyed after the drill. - Verify: You can SSH to it - [ ] **A domain with DNS you control** · roughly 10 to 15 US dollars a year - Why: Needed for the certificate, the site URL and the email sending records. - Get it: Any registrar, then add the A record and the email verification records. - Verify: The hostname resolves and serves a valid certificate - [ ] **A transactional email provider with a verified domain** · free tiers cover a small team - Why: Invitations and password resets both depend on delivery, and mail sent from the server directly will land in spam. - Get it: Create an account, add the DNS records, take the credentials. Verification is the slow step. - Verify: An invitation arrives at three providers and is not in spam - [ ] **A decision about mobile push notifications** · varies by arrangement; check current pricing rather than assuming - Why: This is the constraint that surprises people after migration. Notification certificates belong to the published applications, so a self-hosted server cannot send to them directly. - Get it: Check the project's current options for push, including any hosted proxy service and its cost, or the effort of building and distributing your own mobile applications. Decide before the team migrates. - Verify: A backgrounded mobile application receives a notification, or the limitation is documented and communicated - [ ] **Retention and moderation policies** · free - Why: Chat accumulates informal conversation quickly, and both what you keep and who decides what is acceptable are decisions with weight once a team relies on it. - Get it: Write both before inviting anyone. One page each is enough. - Verify: Both are written and the settings match them ## Quick start ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. - managed operations, mobile push, enterprise security, support, and governance - network effects - native mobile apps - video and voice infrastructure - enterprise moderation and discovery If one of those is essential to you, that is the reason to keep paying for Mattermost Professional, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Mattermost Professional The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me self-hosted team chat you operate, with the mobile notification problem understood before you start to replace Mattermost Professional. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a chat server. Mattermost is open source and this is a deployment task: run it, secure it, back it up, and operate it. Every phase below is deploy, configure and verify, and the last is a restore drill you actually perform. Understand the mobile notification constraint before you commit, because it surprises people after they have migrated a team. Push notifications to the official mobile applications go through a push proxy, since the notification certificates belong to the published applications rather than to your server. You either use the project's proxy service or build and distribute your own mobile applications with your own certificates. Check the current arrangement yourself, and decide before the team depends on it, because chat without reliable mobile notifications is chat people stop reading. ### Stack (fixed, do not substitute) - Application: Mattermost, self-hosted · channels, threads, search and clients already exist; writing them is the thing rule zero prevents - Database: PostgreSQL · what the application expects, and the thing whose backup actually matters - Files: Local disk or S3-compatible storage · uploads are the other half of a working restore and are easy to forget - Edge: A reverse proxy with automatic certificates and WebSocket support · chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly - Email: A transactional provider with a verified sending domain · invitations, password reset and notification emails all depend on delivery ### Data model (create this before Phase 1) - The application owns its schema. What you own is the operational state, and it is worth recording. - instance · version, host, database, file storage location and the deployment method - identity · how people sign in, and whether accounts are invite-only - push · which arrangement you chose for mobile notifications, and what it costs - retention · per channel kind, with the reason and the policy that requires it - backups · database and files, with the date of the last restore ### Phase 1 · Running behind a proxy that understands sockets Build: Provision a server, run the application and PostgreSQL, and put a reverse proxy in front with automatic certificates and WebSocket support. Set the site URL correctly. Then leave a client connected for an hour and confirm it did not silently disconnect. Trap: A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. Done when: The application serves over HTTPS with a valid certificate. A client left connected for an hour with no traffic is still connected rather than having silently reconnected. The site URL matches the domain, verified by checking a generated link. A server restart brings everything back automatically. Do not build yet: No email, push, retention or backups. ### Phase 2 · Email that arrives Build: Configure a transactional provider and verify the sending domain. Send an invitation to a real address at three different mail providers and confirm none land in spam. Then invite the first real member. Trap: Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. Done when: An invitation arrives at three different mail providers and is not in spam. A password reset link works. The sending domain is verified with the records the provider requires. A bounced address is visible in the provider's dashboard rather than silently dropped. Do not build yet: No push decision, retention or backups yet. ### Phase 3 · The mobile push decision Build: Decide how mobile push will work and implement it. Check the current options and their costs yourself rather than assuming. Then test on a real phone: send a message with the application backgrounded and confirm a notification arrives. Trap: Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. Done when: A message sent while the mobile application is backgrounded produces a notification on a real phone. The chosen push arrangement and its cost are written down. If push is not available, the team has been told before migrating and the limitation is recorded. Notification content settings match your privacy expectations, since previews appear on a lock screen. Do not build yet: No retention or backups yet. ### Phase 4 · Access, retention and moderation Build: Restrict signup to invitation or a verified domain. Write a retention policy per channel kind with its reason and configure it. Write a moderation policy and set the permissions to match. Enable two factor for administrators. Trap: Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. Done when: Signup is restricted and an uninvited address cannot create an account, tested directly. A retention policy is configured and its reason is documented per channel kind. Administrators have two factor enabled. A written moderation policy exists and the permission settings match it. Do not build yet: No backups or restore yet. ### Phase 5 · Backups of both halves Build: Nightly database dumps and file storage backups, dated, to a destination that is not this server. Alert if a night passes with no backup. Keep several generations. Trap: A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. Done when: Both a database dump and a file backup land off the server nightly. A missing backup produces an alert. Several generations are retained. The backup includes the configuration needed for a working restore, not only the data. Do not build yet: The restore drill is next and it is the one that counts. ### Phase 6 · The restore drill and upgrades Build: Restore the database and files onto a clean server, sign in, and open a channel with an attachment. Record the date and duration. Write an upgrade procedure that backs up first and read the release notes for breaking changes. Trap: A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. Done when: A clean server serves the restored instance, an existing user can sign in, and an attachment from before the backup downloads correctly. The date and duration are recorded. An upgrade has been rehearsed on the second server before the live one. The previous version remains deployable. Do not build yet: Nothing after this. ### Out of scope (and why) - Managed operations. Patching, upgrades, certificate renewal and capacity are now yours, and chat is something people notice immediately when it is down. - Mobile push through the published applications without arranging it deliberately, which is the constraint most likely to surprise you. - Enterprise security and governance features, compliance exports and the controls larger organisations require. - The network effects of a platform your contacts are already on, which no amount of self-hosting recovers. ### README must contain - That mobile push requires a deliberate arrangement because notification certificates belong to the published applications - That the reverse proxy must support long-lived WebSocket connections, with the idle timeout verified - That signup is restricted and how a new member is invited - The retention policy per channel kind and the reason for each - That backups cover the database and the file storage, with the date of the last restore ===== AGENTS.md ===== # Agent instructions · Mattermost Professional indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Mattermost, self-hosted, PostgreSQL, Local disk or S3-compatible storage, A reverse proxy with automatic certificates and WebSocket support, A transactional provider with a verified sending domain. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ===== BUILD_PLAN.md ===== # Build plan · Mattermost Professional Mattermost on a server you own, behind TLS with a real certificate, connected to an email provider that delivers, with a deliberate decision about mobile push, retention and moderation policies written down, backups of the database and the uploaded files together, and a restore you have performed. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Running behind a proxy that understands sockets Chat works, and it keeps working when a conversation goes quiet for an hour. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and web ports only. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` 2. Run the application and database Following the project's own deployment guidance for the version you are using. Files: `docker-compose.yml` 3. Configure the proxy for long-lived sockets Upgrade headers and a generous idle timeout. Verify by leaving a client idle for an hour. Files: `Caddyfile` ### Done when - [ ] The application serves over HTTPS with a valid certificate - [ ] An idle client stays connected for an hour - [ ] Generated links use the correct site URL - [ ] A server restart restores service automatically ### Watch out - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. ## Phase 2 · Email that arrives Invitations and resets reach inboxes, because everything else depends on them. ### Steps 1. Verify the sending domain The DNS records your provider specifies. Propagation takes hours, so this is why it is early. 2. Test across providers Three different ones, checking spam in each. One provider proves nothing. 3. Confirm the reset flow Not just the invitation. It is the one people need when they cannot get in. ### Done when - [ ] An invitation arrives at three providers and avoids spam - [ ] A password reset link works - [ ] The sending domain is verified - [ ] Bounces are visible rather than silent ### Watch out - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. ## Phase 3 · The mobile push decision Mobile notifications work, or the team knows in advance that they will not. ### Steps 1. Choose the push arrangement deliberately Check the current options and costs. Write down what you chose and why. Files: `docs/push.md` 2. Test on a real device Backgrounded application, real message, real phone. This is the only test that counts. 3. Decide what a notification reveals Message previews appear on lock screens. Decide whether that is acceptable for your team. ### Done when - [ ] A backgrounded mobile application receives a notification - [ ] The push arrangement and cost are written down - [ ] If push is unavailable, the team knows before migrating - [ ] Notification preview content matches your privacy decision ### Watch out - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. ## Phase 4 · Access, retention and moderation The right people are in, old messages do not accumulate forever, and someone is responsible. ### Steps 1. Restrict signup Invitation only or a verified domain. Test with an outside address. 2. Write and configure retention Per channel kind with the reason. Chat accumulates and much of it should not be kept forever. Files: `docs/retention.md` 3. Write the moderation policy What is not allowed, who decides, how to appeal. Before the first incident rather than during it. Files: `docs/moderation.md` ### Done when - [ ] An uninvited address cannot create an account - [ ] Retention is configured with a documented reason - [ ] Administrator accounts have two factor enabled - [ ] The moderation policy exists and settings match it ### Watch out - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. ## Phase 5 · Backups of both halves The database and the uploaded files are both backed up, off the machine. ### Steps 1. Dump the database nightly Dated, compressed, off the machine. Files: `scripts/backup.sh` ```sh pg_dump "$DATABASE_URL" | gzip > chat-$(date +%F).sql.gz ``` 2. Back up file storage Same schedule, same destination. Uploads are half the content people care about. Files: `scripts/backup.sh` 3. Alert on a missing backup Silence is not evidence a backup ran. Files: `scripts/check_backup.sh` ### Done when - [ ] Database and file backups both land off the server nightly - [ ] A suppressed backup produces an alert - [ ] Several generations are retained - [ ] Configuration is included in the backup ### Watch out - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ## Not in this build - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. ## After v1, if you want it - Single sign-on against an identity provider you already run - A staging instance restored from last night's backup, which exercises the restore weekly - Automated attachment retention, since files rather than messages are what fills the disk ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Appears in every link and email. Correct before the first invitation. MM_SERVICESETTINGS_SITEURL=https://chat.example.com # Required · secret. Database connection. Half of what a restore needs. MM_SQLSETTINGS_DATASOURCE=postgres://user:pass@db:5432/mattermost?sslmode=disable # Required. Uploaded files. The other half of a restore, and the half people forget. MM_FILESETTINGS_DIRECTORY=./data/files # Required. Transactional provider host. MM_EMAILSETTINGS_SMTPSERVER=smtp.provider.com # Required · secret. Provider credential, on the server only. MM_EMAILSETTINGS_SMTPPASSWORD=replace-with-provider-password # Required. Which push arrangement you chose. Recorded here so it is not forgotten. PUSH_ARRANGEMENT=documented-in-docs-push # Required. Where both nightly artefacts go. BACKUP_DEST=s3://your-bucket/chat
You are building a production product version of Mattermost Professional. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Mattermost Professional · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Mattermost Professional, self-host team chat with channels, threads, search, and integrations. The hard boundary is managed operations, mobile push, enterprise security, support, and governance, plus network, moderation, and communication infrastructure. ## Product outcome Team chat you operate, where sockets stay open, invitations arrive, mobile notifications either work or were understood in advance, retention is deliberate, and a restore has produced working attachments as well as working messages. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Docker - PostgreSQL - public HTTPS deployment - email provider - object storage for attachments ## Explicit non-goals for v1 - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. - managed operations, mobile push, enterprise security, support, and governance - network effects - native mobile apps - video and voice infrastructure - enterprise moderation and discovery ## Success criteria - An idle client is confirmed connected after an hour - Invitations are confirmed to arrive at three providers - Mobile push is confirmed working or documented as unavailable - Signup restriction is confirmed with an outside address - A restore is confirmed to produce working attachments and is dated ===== BRIEF.md ===== # Build brief · Mattermost Professional The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me self-hosted team chat you operate, with the mobile notification problem understood before you start to replace Mattermost Professional. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a chat server. Mattermost is open source and this is a deployment task: run it, secure it, back it up, and operate it. Every phase below is deploy, configure and verify, and the last is a restore drill you actually perform. Understand the mobile notification constraint before you commit, because it surprises people after they have migrated a team. Push notifications to the official mobile applications go through a push proxy, since the notification certificates belong to the published applications rather than to your server. You either use the project's proxy service or build and distribute your own mobile applications with your own certificates. Check the current arrangement yourself, and decide before the team depends on it, because chat without reliable mobile notifications is chat people stop reading. ### Stack (fixed, do not substitute) - Application: Mattermost, self-hosted · channels, threads, search and clients already exist; writing them is the thing rule zero prevents - Database: PostgreSQL · what the application expects, and the thing whose backup actually matters - Files: Local disk or S3-compatible storage · uploads are the other half of a working restore and are easy to forget - Edge: A reverse proxy with automatic certificates and WebSocket support · chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly - Email: A transactional provider with a verified sending domain · invitations, password reset and notification emails all depend on delivery ### Data model (create this before Phase 1) - The application owns its schema. What you own is the operational state, and it is worth recording. - instance · version, host, database, file storage location and the deployment method - identity · how people sign in, and whether accounts are invite-only - push · which arrangement you chose for mobile notifications, and what it costs - retention · per channel kind, with the reason and the policy that requires it - backups · database and files, with the date of the last restore ### Phase 1 · Running behind a proxy that understands sockets Build: Provision a server, run the application and PostgreSQL, and put a reverse proxy in front with automatic certificates and WebSocket support. Set the site URL correctly. Then leave a client connected for an hour and confirm it did not silently disconnect. Trap: A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. Done when: The application serves over HTTPS with a valid certificate. A client left connected for an hour with no traffic is still connected rather than having silently reconnected. The site URL matches the domain, verified by checking a generated link. A server restart brings everything back automatically. Do not build yet: No email, push, retention or backups. ### Phase 2 · Email that arrives Build: Configure a transactional provider and verify the sending domain. Send an invitation to a real address at three different mail providers and confirm none land in spam. Then invite the first real member. Trap: Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. Done when: An invitation arrives at three different mail providers and is not in spam. A password reset link works. The sending domain is verified with the records the provider requires. A bounced address is visible in the provider's dashboard rather than silently dropped. Do not build yet: No push decision, retention or backups yet. ### Phase 3 · The mobile push decision Build: Decide how mobile push will work and implement it. Check the current options and their costs yourself rather than assuming. Then test on a real phone: send a message with the application backgrounded and confirm a notification arrives. Trap: Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. Done when: A message sent while the mobile application is backgrounded produces a notification on a real phone. The chosen push arrangement and its cost are written down. If push is not available, the team has been told before migrating and the limitation is recorded. Notification content settings match your privacy expectations, since previews appear on a lock screen. Do not build yet: No retention or backups yet. ### Phase 4 · Access, retention and moderation Build: Restrict signup to invitation or a verified domain. Write a retention policy per channel kind with its reason and configure it. Write a moderation policy and set the permissions to match. Enable two factor for administrators. Trap: Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. Done when: Signup is restricted and an uninvited address cannot create an account, tested directly. A retention policy is configured and its reason is documented per channel kind. Administrators have two factor enabled. A written moderation policy exists and the permission settings match it. Do not build yet: No backups or restore yet. ### Phase 5 · Backups of both halves Build: Nightly database dumps and file storage backups, dated, to a destination that is not this server. Alert if a night passes with no backup. Keep several generations. Trap: A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. Done when: Both a database dump and a file backup land off the server nightly. A missing backup produces an alert. Several generations are retained. The backup includes the configuration needed for a working restore, not only the data. Do not build yet: The restore drill is next and it is the one that counts. ### Phase 6 · The restore drill and upgrades Build: Restore the database and files onto a clean server, sign in, and open a channel with an attachment. Record the date and duration. Write an upgrade procedure that backs up first and read the release notes for breaking changes. Trap: A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. Done when: A clean server serves the restored instance, an existing user can sign in, and an attachment from before the backup downloads correctly. The date and duration are recorded. An upgrade has been rehearsed on the second server before the live one. The previous version remains deployable. Do not build yet: Nothing after this. ### Out of scope (and why) - Managed operations. Patching, upgrades, certificate renewal and capacity are now yours, and chat is something people notice immediately when it is down. - Mobile push through the published applications without arranging it deliberately, which is the constraint most likely to surprise you. - Enterprise security and governance features, compliance exports and the controls larger organisations require. - The network effects of a platform your contacts are already on, which no amount of self-hosting recovers. ### README must contain - That mobile push requires a deliberate arrangement because notification certificates belong to the published applications - That the reverse proxy must support long-lived WebSocket connections, with the idle timeout verified - That signup is restricted and how a new member is invited - The retention policy per channel kind and the reason for each - That backups cover the database and the file storage, with the date of the last restore ===== ARCHITECTURE.md ===== # Architecture · Mattermost Professional ## Stack | Part | Choice | Why | | --- | --- | --- | | Application | Mattermost, self-hosted | channels, threads, search and clients already exist; writing them is the thing rule zero prevents | | Database | PostgreSQL | what the application expects, and the thing whose backup actually matters | | Files | Local disk or S3-compatible storage | uploads are the other half of a working restore and are easy to forget | | Edge | A reverse proxy with automatic certificates and WebSocket support | chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly | | Email | A transactional provider with a verified sending domain | invitations, password reset and notification emails all depend on delivery | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Application | Channels, threads, search and clients. | Not yours to replace. Upgrading it is the only change you make here. | | Edge | TLS and long-lived connections. | Any proxy with proper WebSocket support and a generous idle timeout. | | Push | Mobile notifications. | The constraint is structural rather than a configuration choice, which is why it is decided in rule zero. | | Storage | Database and uploaded files. | Destination is replaceable; backing up both together is not. | | Policies | Retention and moderation. | Written first, configured second. Software cannot substitute for the decision. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MM_SERVICESETTINGS_SITEURL` · required · Appears in every link and email. Correct before the first invitation. - `MM_SQLSETTINGS_DATASOURCE` · required, secret · Database connection. Half of what a restore needs. - `MM_FILESETTINGS_DIRECTORY` · required · Uploaded files. The other half of a restore, and the half people forget. - `MM_EMAILSETTINGS_SMTPSERVER` · required · Transactional provider host. - `MM_EMAILSETTINGS_SMTPPASSWORD` · required, secret · Provider credential, on the server only. - `PUSH_ARRANGEMENT` · required · Which push arrangement you chose. Recorded here so it is not forgotten. - `BACKUP_DEST` · required · Where both nightly artefacts go. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Mattermost Professional product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Mattermost, self-hosted, PostgreSQL, Local disk or S3-compatible storage, A reverse proxy with automatic certificates and WebSocket support, A transactional provider with a verified sending domain. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. - A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. ===== MILESTONES.md ===== # Delivery milestones · Mattermost Professional Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Running behind a proxy that understands sockets Chat works, and it keeps working when a conversation goes quiet for an hour. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and web ports only. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` 2. Run the application and database Following the project's own deployment guidance for the version you are using. Files: `docker-compose.yml` 3. Configure the proxy for long-lived sockets Upgrade headers and a generous idle timeout. Verify by leaving a client idle for an hour. Files: `Caddyfile` ### Done when - [ ] The application serves over HTTPS with a valid certificate - [ ] An idle client stays connected for an hour - [ ] Generated links use the correct site URL - [ ] A server restart restores service automatically ### Watch out - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. ## M2 · Email that arrives Invitations and resets reach inboxes, because everything else depends on them. ### Steps 1. Verify the sending domain The DNS records your provider specifies. Propagation takes hours, so this is why it is early. 2. Test across providers Three different ones, checking spam in each. One provider proves nothing. 3. Confirm the reset flow Not just the invitation. It is the one people need when they cannot get in. ### Done when - [ ] An invitation arrives at three providers and avoids spam - [ ] A password reset link works - [ ] The sending domain is verified - [ ] Bounces are visible rather than silent ### Watch out - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. ## M3 · The mobile push decision Mobile notifications work, or the team knows in advance that they will not. ### Steps 1. Choose the push arrangement deliberately Check the current options and costs. Write down what you chose and why. Files: `docs/push.md` 2. Test on a real device Backgrounded application, real message, real phone. This is the only test that counts. 3. Decide what a notification reveals Message previews appear on lock screens. Decide whether that is acceptable for your team. ### Done when - [ ] A backgrounded mobile application receives a notification - [ ] The push arrangement and cost are written down - [ ] If push is unavailable, the team knows before migrating - [ ] Notification preview content matches your privacy decision ### Watch out - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. ## M4 · Access, retention and moderation The right people are in, old messages do not accumulate forever, and someone is responsible. ### Steps 1. Restrict signup Invitation only or a verified domain. Test with an outside address. 2. Write and configure retention Per channel kind with the reason. Chat accumulates and much of it should not be kept forever. Files: `docs/retention.md` 3. Write the moderation policy What is not allowed, who decides, how to appeal. Before the first incident rather than during it. Files: `docs/moderation.md` ### Done when - [ ] An uninvited address cannot create an account - [ ] Retention is configured with a documented reason - [ ] Administrator accounts have two factor enabled - [ ] The moderation policy exists and settings match it ### Watch out - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. ## M5 · Backups of both halves The database and the uploaded files are both backed up, off the machine. ### Steps 1. Dump the database nightly Dated, compressed, off the machine. Files: `scripts/backup.sh` ```sh pg_dump "$DATABASE_URL" | gzip > chat-$(date +%F).sql.gz ``` 2. Back up file storage Same schedule, same destination. Uploads are half the content people care about. Files: `scripts/backup.sh` 3. Alert on a missing backup Silence is not evidence a backup ran. Files: `scripts/check_backup.sh` ### Done when - [ ] Database and file backups both land off the server nightly - [ ] A suppressed backup produces an alert - [ ] Several generations are retained - [ ] Configuration is included in the backup ### Watch out - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ## M6 · The restore drill and upgrades (production only) You have rebuilt it on a clean server, and an upgrade is planned rather than hopeful. ### Steps 1. Restore onto a clean server Database, files and configuration. Then sign in as an existing user. 2. Verify an attachment downloads From a channel that predates the backup. This is the check that catches a database-only restore. Files: `README.md` 3. Write and rehearse the upgrade Back up, read breaking changes, upgrade the second server, verify, then the live one. Files: `docs/upgrade.md` ### Done when - [ ] A restored instance accepts an existing sign-in - [ ] An attachment from before the backup downloads - [ ] The restore date and duration are recorded - [ ] An upgrade was rehearsed before the live one ### Watch out - A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. ===== OPERATIONS.md ===== # Operations · Mattermost Professional ## Backup Nightly database and file storage together, dated, off the machine, several generations, with the configuration included. A restore missing any of the three is incomplete in a way that only appears later. ## Restore Clean server, restore all three, sign in as an existing user and download an attachment from before the backup. Record the date and duration. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime, WebSocket connection count, database connections, disk and email delivery. Disk fills quietly because uploads accumulate and nobody deletes them. ## Incident checklist For messages appearing late, check the proxy idle timeout before anything else. For missing notifications, the push arrangement is the first suspect. For a failed upgrade, restore the pre-upgrade backup rather than repairing in place. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An idle client is confirmed connected after an hour - [ ] Invitations are confirmed to arrive at three providers - [ ] Mobile push is confirmed working or documented as unavailable - [ ] Signup restriction is confirmed with an outside address - [ ] A restore is confirmed to produce working attachments and is dated ## Launch constraint Do not market omitted Mattermost Professional capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Appears in every link and email. Correct before the first invitation. MM_SERVICESETTINGS_SITEURL=https://chat.example.com # Required · secret. Database connection. Half of what a restore needs. MM_SQLSETTINGS_DATASOURCE=postgres://user:pass@db:5432/mattermost?sslmode=disable # Required. Uploaded files. The other half of a restore, and the half people forget. MM_FILESETTINGS_DIRECTORY=./data/files # Required. Transactional provider host. MM_EMAILSETTINGS_SMTPSERVER=smtp.provider.com # Required · secret. Provider credential, on the server only. MM_EMAILSETTINGS_SMTPPASSWORD=replace-with-provider-password # Required. Which push arrangement you chose. Recorded here so it is not forgotten. PUSH_ARRANGEMENT=documented-in-docs-push # Required. Where both nightly artefacts go. BACKUP_DEST=s3://your-bucket/chat
# Mattermost Professional · indie build Mattermost on a server you own, behind TLS with a real certificate, connected to an email provider that delivers, with a deliberate decision about mobile push, retention and moderation policies written down, backups of the database and the uploaded files together, and a restore you have performed. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Application | Mattermost, self-hosted | channels, threads, search and clients already exist; writing them is the thing rule zero prevents | | Database | PostgreSQL | what the application expects, and the thing whose backup actually matters | | Files | Local disk or S3-compatible storage | uploads are the other half of a working restore and are easy to forget | | Edge | A reverse proxy with automatic certificates and WebSocket support | chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly | | Email | A transactional provider with a verified sending domain | invitations, password reset and notification emails all depend on delivery | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Linux server meeting the application's requirements** · typically 10 to 30 US dollars a month for a small team - Why: Chat is latency sensitive in a way people notice, and an undersized server produces a sluggish client that everyone comments on. - Get it: Check the project's current requirements for your team size, then provision accordingly. - Verify: The application starts and a client connects promptly - [ ] **A second server for the restore drill** · a few US dollars if deleted the same day - Why: Restoring onto the machine that already has the files proves nothing, and the missing file storage is exactly what a database-only backup leaves out. - Get it: The same provider, smallest suitable plan, destroyed after the drill. - Verify: You can SSH to it - [ ] **A domain with DNS you control** · roughly 10 to 15 US dollars a year - Why: Needed for the certificate, the site URL and the email sending records. - Get it: Any registrar, then add the A record and the email verification records. - Verify: The hostname resolves and serves a valid certificate - [ ] **A transactional email provider with a verified domain** · free tiers cover a small team - Why: Invitations and password resets both depend on delivery, and mail sent from the server directly will land in spam. - Get it: Create an account, add the DNS records, take the credentials. Verification is the slow step. - Verify: An invitation arrives at three providers and is not in spam - [ ] **A decision about mobile push notifications** · varies by arrangement; check current pricing rather than assuming - Why: This is the constraint that surprises people after migration. Notification certificates belong to the published applications, so a self-hosted server cannot send to them directly. - Get it: Check the project's current options for push, including any hosted proxy service and its cost, or the effort of building and distributing your own mobile applications. Decide before the team migrates. - Verify: A backgrounded mobile application receives a notification, or the limitation is documented and communicated - [ ] **Retention and moderation policies** · free - Why: Chat accumulates informal conversation quickly, and both what you keep and who decides what is acceptable are decisions with weight once a team relies on it. - Get it: Write both before inviting anyone. One page each is enough. - Verify: Both are written and the settings match them ## Quick start ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. - managed operations, mobile push, enterprise security, support, and governance - network effects - native mobile apps - video and voice infrastructure - enterprise moderation and discovery If one of those is essential to you, that is the reason to keep paying for Mattermost Professional, and the README should say so rather than pretend.
# Build brief · Mattermost Professional The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me self-hosted team chat you operate, with the mobile notification problem understood before you start to replace Mattermost Professional. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a chat server. Mattermost is open source and this is a deployment task: run it, secure it, back it up, and operate it. Every phase below is deploy, configure and verify, and the last is a restore drill you actually perform. Understand the mobile notification constraint before you commit, because it surprises people after they have migrated a team. Push notifications to the official mobile applications go through a push proxy, since the notification certificates belong to the published applications rather than to your server. You either use the project's proxy service or build and distribute your own mobile applications with your own certificates. Check the current arrangement yourself, and decide before the team depends on it, because chat without reliable mobile notifications is chat people stop reading. ### Stack (fixed, do not substitute) - Application: Mattermost, self-hosted · channels, threads, search and clients already exist; writing them is the thing rule zero prevents - Database: PostgreSQL · what the application expects, and the thing whose backup actually matters - Files: Local disk or S3-compatible storage · uploads are the other half of a working restore and are easy to forget - Edge: A reverse proxy with automatic certificates and WebSocket support · chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly - Email: A transactional provider with a verified sending domain · invitations, password reset and notification emails all depend on delivery ### Data model (create this before Phase 1) - The application owns its schema. What you own is the operational state, and it is worth recording. - instance · version, host, database, file storage location and the deployment method - identity · how people sign in, and whether accounts are invite-only - push · which arrangement you chose for mobile notifications, and what it costs - retention · per channel kind, with the reason and the policy that requires it - backups · database and files, with the date of the last restore ### Phase 1 · Running behind a proxy that understands sockets Build: Provision a server, run the application and PostgreSQL, and put a reverse proxy in front with automatic certificates and WebSocket support. Set the site URL correctly. Then leave a client connected for an hour and confirm it did not silently disconnect. Trap: A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. Done when: The application serves over HTTPS with a valid certificate. A client left connected for an hour with no traffic is still connected rather than having silently reconnected. The site URL matches the domain, verified by checking a generated link. A server restart brings everything back automatically. Do not build yet: No email, push, retention or backups. ### Phase 2 · Email that arrives Build: Configure a transactional provider and verify the sending domain. Send an invitation to a real address at three different mail providers and confirm none land in spam. Then invite the first real member. Trap: Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. Done when: An invitation arrives at three different mail providers and is not in spam. A password reset link works. The sending domain is verified with the records the provider requires. A bounced address is visible in the provider's dashboard rather than silently dropped. Do not build yet: No push decision, retention or backups yet. ### Phase 3 · The mobile push decision Build: Decide how mobile push will work and implement it. Check the current options and their costs yourself rather than assuming. Then test on a real phone: send a message with the application backgrounded and confirm a notification arrives. Trap: Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. Done when: A message sent while the mobile application is backgrounded produces a notification on a real phone. The chosen push arrangement and its cost are written down. If push is not available, the team has been told before migrating and the limitation is recorded. Notification content settings match your privacy expectations, since previews appear on a lock screen. Do not build yet: No retention or backups yet. ### Phase 4 · Access, retention and moderation Build: Restrict signup to invitation or a verified domain. Write a retention policy per channel kind with its reason and configure it. Write a moderation policy and set the permissions to match. Enable two factor for administrators. Trap: Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. Done when: Signup is restricted and an uninvited address cannot create an account, tested directly. A retention policy is configured and its reason is documented per channel kind. Administrators have two factor enabled. A written moderation policy exists and the permission settings match it. Do not build yet: No backups or restore yet. ### Phase 5 · Backups of both halves Build: Nightly database dumps and file storage backups, dated, to a destination that is not this server. Alert if a night passes with no backup. Keep several generations. Trap: A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. Done when: Both a database dump and a file backup land off the server nightly. A missing backup produces an alert. Several generations are retained. The backup includes the configuration needed for a working restore, not only the data. Do not build yet: The restore drill is next and it is the one that counts. ### Phase 6 · The restore drill and upgrades Build: Restore the database and files onto a clean server, sign in, and open a channel with an attachment. Record the date and duration. Write an upgrade procedure that backs up first and read the release notes for breaking changes. Trap: A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. Done when: A clean server serves the restored instance, an existing user can sign in, and an attachment from before the backup downloads correctly. The date and duration are recorded. An upgrade has been rehearsed on the second server before the live one. The previous version remains deployable. Do not build yet: Nothing after this. ### Out of scope (and why) - Managed operations. Patching, upgrades, certificate renewal and capacity are now yours, and chat is something people notice immediately when it is down. - Mobile push through the published applications without arranging it deliberately, which is the constraint most likely to surprise you. - Enterprise security and governance features, compliance exports and the controls larger organisations require. - The network effects of a platform your contacts are already on, which no amount of self-hosting recovers. ### README must contain - That mobile push requires a deliberate arrangement because notification certificates belong to the published applications - That the reverse proxy must support long-lived WebSocket connections, with the idle timeout verified - That signup is restricted and how a new member is invited - The retention policy per channel kind and the reason for each - That backups cover the database and the file storage, with the date of the last restore
# Agent instructions · Mattermost Professional indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Mattermost, self-hosted, PostgreSQL, Local disk or S3-compatible storage, A reverse proxy with automatic certificates and WebSocket support, A transactional provider with a verified sending domain. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together.
# Build plan · Mattermost Professional Mattermost on a server you own, behind TLS with a real certificate, connected to an email provider that delivers, with a deliberate decision about mobile push, retention and moderation policies written down, backups of the database and the uploaded files together, and a restore you have performed. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Running behind a proxy that understands sockets Chat works, and it keeps working when a conversation goes quiet for an hour. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and web ports only. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` 2. Run the application and database Following the project's own deployment guidance for the version you are using. Files: `docker-compose.yml` 3. Configure the proxy for long-lived sockets Upgrade headers and a generous idle timeout. Verify by leaving a client idle for an hour. Files: `Caddyfile` ### Done when - [ ] The application serves over HTTPS with a valid certificate - [ ] An idle client stays connected for an hour - [ ] Generated links use the correct site URL - [ ] A server restart restores service automatically ### Watch out - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. ## Phase 2 · Email that arrives Invitations and resets reach inboxes, because everything else depends on them. ### Steps 1. Verify the sending domain The DNS records your provider specifies. Propagation takes hours, so this is why it is early. 2. Test across providers Three different ones, checking spam in each. One provider proves nothing. 3. Confirm the reset flow Not just the invitation. It is the one people need when they cannot get in. ### Done when - [ ] An invitation arrives at three providers and avoids spam - [ ] A password reset link works - [ ] The sending domain is verified - [ ] Bounces are visible rather than silent ### Watch out - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. ## Phase 3 · The mobile push decision Mobile notifications work, or the team knows in advance that they will not. ### Steps 1. Choose the push arrangement deliberately Check the current options and costs. Write down what you chose and why. Files: `docs/push.md` 2. Test on a real device Backgrounded application, real message, real phone. This is the only test that counts. 3. Decide what a notification reveals Message previews appear on lock screens. Decide whether that is acceptable for your team. ### Done when - [ ] A backgrounded mobile application receives a notification - [ ] The push arrangement and cost are written down - [ ] If push is unavailable, the team knows before migrating - [ ] Notification preview content matches your privacy decision ### Watch out - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. ## Phase 4 · Access, retention and moderation The right people are in, old messages do not accumulate forever, and someone is responsible. ### Steps 1. Restrict signup Invitation only or a verified domain. Test with an outside address. 2. Write and configure retention Per channel kind with the reason. Chat accumulates and much of it should not be kept forever. Files: `docs/retention.md` 3. Write the moderation policy What is not allowed, who decides, how to appeal. Before the first incident rather than during it. Files: `docs/moderation.md` ### Done when - [ ] An uninvited address cannot create an account - [ ] Retention is configured with a documented reason - [ ] Administrator accounts have two factor enabled - [ ] The moderation policy exists and settings match it ### Watch out - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. ## Phase 5 · Backups of both halves The database and the uploaded files are both backed up, off the machine. ### Steps 1. Dump the database nightly Dated, compressed, off the machine. Files: `scripts/backup.sh` ```sh pg_dump "$DATABASE_URL" | gzip > chat-$(date +%F).sql.gz ``` 2. Back up file storage Same schedule, same destination. Uploads are half the content people care about. Files: `scripts/backup.sh` 3. Alert on a missing backup Silence is not evidence a backup ran. Files: `scripts/check_backup.sh` ### Done when - [ ] Database and file backups both land off the server nightly - [ ] A suppressed backup produces an alert - [ ] Several generations are retained - [ ] Configuration is included in the backup ### Watch out - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ## Not in this build - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. ## After v1, if you want it - Single sign-on against an identity provider you already run - A staging instance restored from last night's backup, which exercises the restore weekly - Automated attachment retention, since files rather than messages are what fills the disk
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Appears in every link and email. Correct before the first invitation. MM_SERVICESETTINGS_SITEURL=https://chat.example.com # Required · secret. Database connection. Half of what a restore needs. MM_SQLSETTINGS_DATASOURCE=postgres://user:pass@db:5432/mattermost?sslmode=disable # Required. Uploaded files. The other half of a restore, and the half people forget. MM_FILESETTINGS_DIRECTORY=./data/files # Required. Transactional provider host. MM_EMAILSETTINGS_SMTPSERVER=smtp.provider.com # Required · secret. Provider credential, on the server only. MM_EMAILSETTINGS_SMTPPASSWORD=replace-with-provider-password # Required. Which push arrangement you chose. Recorded here so it is not forgotten. PUSH_ARRANGEMENT=documented-in-docs-push # Required. Where both nightly artefacts go. BACKUP_DEST=s3://your-bucket/chat
# Mattermost Professional · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Mattermost Professional, self-host team chat with channels, threads, search, and integrations. The hard boundary is managed operations, mobile push, enterprise security, support, and governance, plus network, moderation, and communication infrastructure. ## Product outcome Team chat you operate, where sockets stay open, invitations arrive, mobile notifications either work or were understood in advance, retention is deliberate, and a restore has produced working attachments as well as working messages. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Docker - PostgreSQL - public HTTPS deployment - email provider - object storage for attachments ## Explicit non-goals for v1 - Managed operations, patching and upgrades. - Mobile push without a deliberate arrangement. - Enterprise security, governance and compliance exports. - The network effects of a platform people are already on. - managed operations, mobile push, enterprise security, support, and governance - network effects - native mobile apps - video and voice infrastructure - enterprise moderation and discovery ## Success criteria - An idle client is confirmed connected after an hour - Invitations are confirmed to arrive at three providers - Mobile push is confirmed working or documented as unavailable - Signup restriction is confirmed with an outside address - A restore is confirmed to produce working attachments and is dated
# Build brief · Mattermost Professional The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me self-hosted team chat you operate, with the mobile notification problem understood before you start to replace Mattermost Professional. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write a chat server. Mattermost is open source and this is a deployment task: run it, secure it, back it up, and operate it. Every phase below is deploy, configure and verify, and the last is a restore drill you actually perform. Understand the mobile notification constraint before you commit, because it surprises people after they have migrated a team. Push notifications to the official mobile applications go through a push proxy, since the notification certificates belong to the published applications rather than to your server. You either use the project's proxy service or build and distribute your own mobile applications with your own certificates. Check the current arrangement yourself, and decide before the team depends on it, because chat without reliable mobile notifications is chat people stop reading. ### Stack (fixed, do not substitute) - Application: Mattermost, self-hosted · channels, threads, search and clients already exist; writing them is the thing rule zero prevents - Database: PostgreSQL · what the application expects, and the thing whose backup actually matters - Files: Local disk or S3-compatible storage · uploads are the other half of a working restore and are easy to forget - Edge: A reverse proxy with automatic certificates and WebSocket support · chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly - Email: A transactional provider with a verified sending domain · invitations, password reset and notification emails all depend on delivery ### Data model (create this before Phase 1) - The application owns its schema. What you own is the operational state, and it is worth recording. - instance · version, host, database, file storage location and the deployment method - identity · how people sign in, and whether accounts are invite-only - push · which arrangement you chose for mobile notifications, and what it costs - retention · per channel kind, with the reason and the policy that requires it - backups · database and files, with the date of the last restore ### Phase 1 · Running behind a proxy that understands sockets Build: Provision a server, run the application and PostgreSQL, and put a reverse proxy in front with automatic certificates and WebSocket support. Set the site URL correctly. Then leave a client connected for an hour and confirm it did not silently disconnect. Trap: A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. Done when: The application serves over HTTPS with a valid certificate. A client left connected for an hour with no traffic is still connected rather than having silently reconnected. The site URL matches the domain, verified by checking a generated link. A server restart brings everything back automatically. Do not build yet: No email, push, retention or backups. ### Phase 2 · Email that arrives Build: Configure a transactional provider and verify the sending domain. Send an invitation to a real address at three different mail providers and confirm none land in spam. Then invite the first real member. Trap: Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. Done when: An invitation arrives at three different mail providers and is not in spam. A password reset link works. The sending domain is verified with the records the provider requires. A bounced address is visible in the provider's dashboard rather than silently dropped. Do not build yet: No push decision, retention or backups yet. ### Phase 3 · The mobile push decision Build: Decide how mobile push will work and implement it. Check the current options and their costs yourself rather than assuming. Then test on a real phone: send a message with the application backgrounded and confirm a notification arrives. Trap: Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. Done when: A message sent while the mobile application is backgrounded produces a notification on a real phone. The chosen push arrangement and its cost are written down. If push is not available, the team has been told before migrating and the limitation is recorded. Notification content settings match your privacy expectations, since previews appear on a lock screen. Do not build yet: No retention or backups yet. ### Phase 4 · Access, retention and moderation Build: Restrict signup to invitation or a verified domain. Write a retention policy per channel kind with its reason and configure it. Write a moderation policy and set the permissions to match. Enable two factor for administrators. Trap: Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. Done when: Signup is restricted and an uninvited address cannot create an account, tested directly. A retention policy is configured and its reason is documented per channel kind. Administrators have two factor enabled. A written moderation policy exists and the permission settings match it. Do not build yet: No backups or restore yet. ### Phase 5 · Backups of both halves Build: Nightly database dumps and file storage backups, dated, to a destination that is not this server. Alert if a night passes with no backup. Keep several generations. Trap: A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. Done when: Both a database dump and a file backup land off the server nightly. A missing backup produces an alert. Several generations are retained. The backup includes the configuration needed for a working restore, not only the data. Do not build yet: The restore drill is next and it is the one that counts. ### Phase 6 · The restore drill and upgrades Build: Restore the database and files onto a clean server, sign in, and open a channel with an attachment. Record the date and duration. Write an upgrade procedure that backs up first and read the release notes for breaking changes. Trap: A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts. Done when: A clean server serves the restored instance, an existing user can sign in, and an attachment from before the backup downloads correctly. The date and duration are recorded. An upgrade has been rehearsed on the second server before the live one. The previous version remains deployable. Do not build yet: Nothing after this. ### Out of scope (and why) - Managed operations. Patching, upgrades, certificate renewal and capacity are now yours, and chat is something people notice immediately when it is down. - Mobile push through the published applications without arranging it deliberately, which is the constraint most likely to surprise you. - Enterprise security and governance features, compliance exports and the controls larger organisations require. - The network effects of a platform your contacts are already on, which no amount of self-hosting recovers. ### README must contain - That mobile push requires a deliberate arrangement because notification certificates belong to the published applications - That the reverse proxy must support long-lived WebSocket connections, with the idle timeout verified - That signup is restricted and how a new member is invited - The retention policy per channel kind and the reason for each - That backups cover the database and the file storage, with the date of the last restore
# Architecture · Mattermost Professional ## Stack | Part | Choice | Why | | --- | --- | --- | | Application | Mattermost, self-hosted | channels, threads, search and clients already exist; writing them is the thing rule zero prevents | | Database | PostgreSQL | what the application expects, and the thing whose backup actually matters | | Files | Local disk or S3-compatible storage | uploads are the other half of a working restore and are easy to forget | | Edge | A reverse proxy with automatic certificates and WebSocket support | chat is a long-lived connection and a proxy that closes idle sockets breaks it subtly | | Email | A transactional provider with a verified sending domain | invitations, password reset and notification emails all depend on delivery | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Application | Channels, threads, search and clients. | Not yours to replace. Upgrading it is the only change you make here. | | Edge | TLS and long-lived connections. | Any proxy with proper WebSocket support and a generous idle timeout. | | Push | Mobile notifications. | The constraint is structural rather than a configuration choice, which is why it is decided in rule zero. | | Storage | Database and uploaded files. | Destination is replaceable; backing up both together is not. | | Policies | Retention and moderation. | Written first, configured second. Software cannot substitute for the decision. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MM_SERVICESETTINGS_SITEURL` · required · Appears in every link and email. Correct before the first invitation. - `MM_SQLSETTINGS_DATASOURCE` · required, secret · Database connection. Half of what a restore needs. - `MM_FILESETTINGS_DIRECTORY` · required · Uploaded files. The other half of a restore, and the half people forget. - `MM_EMAILSETTINGS_SMTPSERVER` · required · Transactional provider host. - `MM_EMAILSETTINGS_SMTPPASSWORD` · required, secret · Provider credential, on the server only. - `PUSH_ARRANGEMENT` · required · Which push arrangement you chose. Recorded here so it is not forgotten. - `BACKUP_DEST` · required · Where both nightly artefacts go. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Mattermost Professional product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Mattermost, self-hosted, PostgreSQL, Local disk or S3-compatible storage, A reverse proxy with automatic certificates and WebSocket support, A transactional provider with a verified sending domain. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. - A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts.
# Delivery milestones · Mattermost Professional Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Running behind a proxy that understands sockets Chat works, and it keeps working when a conversation goes quiet for an hour. ### Steps 1. Provision and close the server Non-root user, key-only SSH, firewall allowing SSH and web ports only. ```sh ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable ``` 2. Run the application and database Following the project's own deployment guidance for the version you are using. Files: `docker-compose.yml` 3. Configure the proxy for long-lived sockets Upgrade headers and a generous idle timeout. Verify by leaving a client idle for an hour. Files: `Caddyfile` ### Done when - [ ] The application serves over HTTPS with a valid certificate - [ ] An idle client stays connected for an hour - [ ] Generated links use the correct site URL - [ ] A server restart restores service automatically ### Watch out - A reverse proxy with a short idle timeout closes the WebSocket during a quiet period, and the client reconnects without saying anything, so messages appear late and nobody knows why. Set the timeout deliberately and verify by leaving a client idle rather than by reading the configuration. ## M2 · Email that arrives Invitations and resets reach inboxes, because everything else depends on them. ### Steps 1. Verify the sending domain The DNS records your provider specifies. Propagation takes hours, so this is why it is early. 2. Test across providers Three different ones, checking spam in each. One provider proves nothing. 3. Confirm the reset flow Not just the invitation. It is the one people need when they cannot get in. ### Done when - [ ] An invitation arrives at three providers and avoids spam - [ ] A password reset link works - [ ] The sending domain is verified - [ ] Bounces are visible rather than silent ### Watch out - Sending from the server directly rather than through a provider produces mail that lands in spam almost everywhere, and the symptom is people saying they never got the invitation. Use a provider, verify the domain, and check the spam folder at three providers rather than one. ## M3 · The mobile push decision Mobile notifications work, or the team knows in advance that they will not. ### Steps 1. Choose the push arrangement deliberately Check the current options and costs. Write down what you chose and why. Files: `docs/push.md` 2. Test on a real device Backgrounded application, real message, real phone. This is the only test that counts. 3. Decide what a notification reveals Message previews appear on lock screens. Decide whether that is acceptable for your team. ### Done when - [ ] A backgrounded mobile application receives a notification - [ ] The push arrangement and cost are written down - [ ] If push is unavailable, the team knows before migrating - [ ] Notification preview content matches your privacy decision ### Watch out - Migrating a team and then discovering that mobile notifications do not arrive is how a self-hosted chat deployment gets abandoned in week two. Notification certificates belong to the published mobile applications, so a self-hosted server cannot send directly to them; resolve this before anyone depends on it. ## M4 · Access, retention and moderation The right people are in, old messages do not accumulate forever, and someone is responsible. ### Steps 1. Restrict signup Invitation only or a verified domain. Test with an outside address. 2. Write and configure retention Per channel kind with the reason. Chat accumulates and much of it should not be kept forever. Files: `docs/retention.md` 3. Write the moderation policy What is not allowed, who decides, how to appeal. Before the first incident rather than during it. Files: `docs/moderation.md` ### Done when - [ ] An uninvited address cannot create an account - [ ] Retention is configured with a documented reason - [ ] Administrator accounts have two factor enabled - [ ] The moderation policy exists and settings match it ### Watch out - Open signup on a publicly reachable chat server is discovered by scanners and produces accounts you did not invite, sometimes within days. Restrict signup before the server is reachable rather than after the first unexpected member appears. ## M5 · Backups of both halves The database and the uploaded files are both backed up, off the machine. ### Steps 1. Dump the database nightly Dated, compressed, off the machine. Files: `scripts/backup.sh` ```sh pg_dump "$DATABASE_URL" | gzip > chat-$(date +%F).sql.gz ``` 2. Back up file storage Same schedule, same destination. Uploads are half the content people care about. Files: `scripts/backup.sh` 3. Alert on a missing backup Silence is not evidence a backup ran. Files: `scripts/check_backup.sh` ### Done when - [ ] Database and file backups both land off the server nightly - [ ] A suppressed backup produces an alert - [ ] Several generations are retained - [ ] Configuration is included in the backup ### Watch out - A database dump without the file storage produces a restore where every conversation is intact and every shared file is a broken link, which is exactly the material people needed. Back up both on the same schedule and restore both together. ## M6 · The restore drill and upgrades (production only) You have rebuilt it on a clean server, and an upgrade is planned rather than hopeful. ### Steps 1. Restore onto a clean server Database, files and configuration. Then sign in as an existing user. 2. Verify an attachment downloads From a channel that predates the backup. This is the check that catches a database-only restore. Files: `README.md` 3. Write and rehearse the upgrade Back up, read breaking changes, upgrade the second server, verify, then the live one. Files: `docs/upgrade.md` ### Done when - [ ] A restored instance accepts an existing sign-in - [ ] An attachment from before the backup downloads - [ ] The restore date and duration are recorded - [ ] An upgrade was rehearsed before the live one ### Watch out - A restore that produces a working login and broken attachments looks successful until someone opens an old file. Verify by downloading an attachment from a restored channel rather than by checking that the application starts.
# Operations · Mattermost Professional ## Backup Nightly database and file storage together, dated, off the machine, several generations, with the configuration included. A restore missing any of the three is incomplete in a way that only appears later. ## Restore Clean server, restore all three, sign in as an existing user and download an attachment from before the backup. Record the date and duration. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime, WebSocket connection count, database connections, disk and email delivery. Disk fills quietly because uploads accumulate and nobody deletes them. ## Incident checklist For messages appearing late, check the proxy idle timeout before anything else. For missing notifications, the push arrangement is the first suspect. For a failed upgrade, restore the pre-upgrade backup rather than repairing in place. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An idle client is confirmed connected after an hour - [ ] Invitations are confirmed to arrive at three providers - [ ] Mobile push is confirmed working or documented as unavailable - [ ] Signup restriction is confirmed with an outside address - [ ] A restore is confirmed to produce working attachments and is dated ## Launch constraint Do not market omitted Mattermost Professional capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Appears in every link and email. Correct before the first invitation. MM_SERVICESETTINGS_SITEURL=https://chat.example.com # Required · secret. Database connection. Half of what a restore needs. MM_SQLSETTINGS_DATASOURCE=postgres://user:pass@db:5432/mattermost?sslmode=disable # Required. Uploaded files. The other half of a restore, and the half people forget. MM_FILESETTINGS_DIRECTORY=./data/files # Required. Transactional provider host. MM_EMAILSETTINGS_SMTPSERVER=smtp.provider.com # Required · secret. Provider credential, on the server only. MM_EMAILSETTINGS_SMTPPASSWORD=replace-with-provider-password # Required. Which push arrangement you chose. Recorded here so it is not forgotten. PUSH_ARRANGEMENT=documented-in-docs-push # Required. Where both nightly artefacts go. BACKUP_DEST=s3://your-bucket/chat
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Mattermost Professional because community software is valuable when members are already there and the operator does not have to own realtime reliability and abuse response. The recurring cost buys realtime delivery, notifications, abuse handling, search, media, permissions, mobile clients, backups, and community operations, not just the visible interface.
xmanaged operations, mobile push, enterprise security, support, and governance
xnetwork effects
xnative mobile apps
xvideo and voice infrastructure
xenterprise moderation and discovery
Don't feel like building it? These folks already made it free.
all 5 free alternatives to Mattermost Professional →· no votes, no pay-to-list · just what's real
Vibecode Mattermost Professional
Kinda. The core of Mattermost Professional is buildable in a weekend with the prompt on this page, but there are real gaps: managed operations, mobile push, enterprise security, support, and governance, network effects. Read the honest list above before committing.
How much does Mattermost Professional cost?
Mattermost Professional costs about $10/month (Professional, checked 2026-07-31), which is $120 per year.
What do I lose by replacing Mattermost Professional?
Honestly: managed operations, mobile push, enterprise security, support, and governance; network effects; native mobile apps; video and voice infrastructure; enterprise moderation and discovery. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Mattermost Professional?
Yes: Rocket.Chat Community (Channels, threads, search and integrations for up to a hundred concurrent users; MongoDB joins the team.) Mattermost Team Edition (Mattermost without the invoice: full team chat below 250 users, but no SSO and every server problem belongs to you.) Zulip (Email-shaped topics inside team chat; excellent for async work and merciless about naming conversations.) All 5 curated free alternatives are at vibecodeit.com/mattermost-professional/alternatives. The prompt is for when you want it exactly your way.