Notifuse: a self-hosted email platform where the web analytics also live in PostgreSQL
Open-source, self-hosted newsletter, email marketing and transactional email platform. Visual MJML editor, Liquid templating, 7 sending providers.
At a glance
- What is it?
- Notifuse bundles campaign email, transactional sending, a visual MJML editor and cookieless web analytics into one Go and React application. The interesting part is not the feature list but the storage decision: everything, including the analytics sessions, sits in PostgreSQL 17.
- Who is it for?
- Adopt Notifuse if you already run PostgreSQL 17, want campaigns and transactional mail behind one API, and would rather drop monthly partition tables than operate a separate analytics store. Do not adopt it if you need a permissively licensed codebase you can relicense or embed, because the repository ships without a machine-readable licence and NOASSERTION tells you nothing about your obligations.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Notifuse takes on: campaign mail, transactional mail and analytics in one install
Most self-hosted senders solve one half of the problem. A newsletter tool handles broadcasts and subscriber lists; a transactional API handles password resets and receipts. Running both usually means two systems, two contact databases and two sets of delivery events that never reconcile. Notifuse positions itself against that split. The README calls it an alternative to Mailchimp, Brevo, Mailjet, Listmonk, Mailerlite, Klaviyo and Loop.so, and the feature set covers both sides: campaigns with scheduling and A/B testing on one hand, a REST transactional API on the other.
The target user is a small engineering team or an agency that is willing to run its own infrastructure to cut per-contact pricing. Notifuse Cloud starts at $16 per month, so the project is not pretending self-hosting is the only option. The self-hosted path matters for teams with data residency constraints, or for agencies that need multi-tenant workspaces so each client gets an isolated sending context. The workspace model is listed as a first-class feature, which is a different posture from tools that bolt on teams later.
The unusual addition is web analytics. Notifuse merged the Staminads feature set into the application, so a workspace can track on-site sessions without cookies and without a second service. That is a genuine scope decision, not a checkbox: it changes what the database has to store and how you plan retention.
Clean architecture in Go, with the analytics living in monthly partitions
The repository describes a layered Go backend: domain entities in internal/domain/, business logic in internal/service/, storage in internal/repository/, and HTTP handlers and middleware in internal/http/. PostgreSQL is the only primary store, queried through Squirrel as the query builder. The frontend splits into two React applications: the console for administration, built with Ant Design and TypeScript, and an embeddable notification center widget.
The analytics design is the part worth reading closely. The README states that monthly partitions are created automatically, named like web_sessions_y2026m08 with web_pages_* and web_goals_* siblings. Retention is therefore a schema operation rather than a delete query: you expire old data by issuing DROP TABLE against the partitions you no longer want, and the disk space returns immediately. Anyone who has watched a DELETE on a large events table run for hours will recognise why that matters.
Sessions are measured by engaged time rather than wall clock, described as TimeScore, with bounce rate derived from engagement. Channel attribution uses 40 default rules that classify traffic from paid click identifiers such as gclid and fbclid, organic search, social and email. Those rules are editable per workspace, and the README notes a one-click backfill of historical data when rules change. The tracking script is served by your own instance at /na.js and is about 21 KB gzipped; the README states the user agent is parsed in the browser so the raw string never reaches the server. Geo lookup uses the MaxMind GeoLite2 City database shipped in the image at /app/geoip/GeoLite2-City.mmdb, and you can override it by dropping your own copy into the mounted data/ directory or setting GEOIP_DB_PATH.
Getting it running: the setup wizard, the compose file and the PostgreSQL 17 floor
The README gives an interactive setup wizard as the intended path for deployment and configuration. It does not print a full docker compose invocation in the material available here, so treat the compose file in the repository as the reference rather than any command quoted from this article. What the material does specify is the database requirement: PostgreSQL 17 or newer. The shipped compose file stays on 17, and the README is explicit about why. Upgrading a major version needs pg_upgrade, and PostgreSQL 18 changed its data directory layout. If you are already on 18, you are outside what the shipped compose file tests.
Web analytics is enabled per workspace, and setup is described as pasting the snippet from the workspace settings page. The snippet points at /na.js on your own instance. That means the tracking endpoint and the console share the same origin, which simplifies content security policy work but also means your analytics availability is tied to your application availability.
Sending providers are configured through the application rather than through environment variables alone, given the wizard-based setup. The README lists Amazon SES, Mailgun, Postmark, Mailjet, SparkPost, SendGrid and SMTP. Seven named providers plus plain SMTP is the fallback that keeps the platform usable when a hosted provider rejects your domain or your use case. Templates use Liquid, with the README giving {{ contact.first_name }} as the variable syntax. The visual builder emits MJML components with a real-time preview, so the editor output and the templating layer are separate concerns you can reason about independently.
Automations are a graph, and the per-enrollment state is the detail that matters
The automation builder is a canvas where each automation is a graph with a single trigger as its root. Node types listed in the README are delay, email, branch, filter, add_to_list, remove_from_list, ab_test, webhook and list_status_branch. Delays wait minutes, hours or days. Branches split on contact conditions or on a contact's status within a given list. A/B testing exists inside flows with weighted variants, not only on broadcasts. Webhook steps call an external URL mid-journey with a retry budget.
The design decision I would flag is per-contact enrollment state. Every enrollment tracks its own status (active, completed, exited, failed) and its current node, with a per-node history of entered, processing, completed, failed and skipped. Enrollment control lets you run a contact once or every_time the trigger fires, with an optional exit_on_reply. Automations move between draft, live and paused, and paused automations hold their enrollments rather than dropping them.
That is more bookkeeping than a simple drip sequence needs, and it is the reason the feature is credible for anything with branching. Without per-node history, a failed webhook step in the middle of a journey is nearly impossible to diagnose after the fact. With it, you can see which node a contact stalled on. The cost is storage and write volume on every enrollment transition, which is worth knowing before you build a flow that fires on high-frequency events.
Where Notifuse is the wrong choice
The licence is the first constraint, and it is not a small one. The repository metadata reports NOASSERTION, which means no machine-readable licence was detected. The README does not state a licence either. For a self-hosted platform you intend to run internally, that may be workable once you read the actual LICENSE file. For anything involving redistribution, embedding in a product, or a corporate policy that requires an SPDX identifier before approval, NOASSERTION is a blocker until a human resolves it. I cannot tell you what the terms are, because the material does not say.
The PostgreSQL 17 floor is the second constraint. If your platform standard is PostgreSQL 16, or if you have already moved to 18, the shipped compose file does not match your environment. The README's note about pg_upgrade and the PostgreSQL 18 data directory change is a warning, not an invitation.
Scale is the third. The README points heavy-traffic installs at AlloyDB Omni via compose.alloydb.yaml for columnar acceleration on dashboard queries, with no schema changes required. That is a real escape hatch, but it is a different database product with its own operational profile. If you are expecting plain PostgreSQL to absorb millions of analytics sessions per month on modest hardware, the README does not claim that, and neither should you assume it.
Finally, deliverability is not a feature you install. Notifuse connects to providers; it does not make an unauthenticated domain send mail that lands. If your sending reputation is the actual problem, a new platform will not fix it.
How this differs from Listmonk and from hosted senders
Listmonk is named in the README as one of the tools Notifuse positions against, and the comparison is instructive. Listmonk is a Go newsletter and mailing list manager with a smaller surface: campaigns, lists, templates, a transactional API. It does not ship a visual MJML drag-and-drop builder, and it does not ship a web analytics module with session partitions and channel attribution rules. If your requirement is newsletters plus a subscriber list and nothing else, Listmonk's smaller footprint is a fair reason to prefer it, and the operational burden is correspondingly lower.
Notifuse's approach is to absorb adjacent tools. The Staminads merge is the clearest example: rather than telling you to run a separate cookieless analytics service and join the data yourself, the sessions live in the same PostgreSQL instance as your contacts, which makes it possible to reason about a campaign and its on-site outcome in one place. The trade-off is that your email platform now owns your analytics retention policy, and that policy is expressed as DROP TABLE on monthly partitions.
The hosted senders (Mailchimp, Brevo, Klaviyo and the rest) differ on the axis that matters most to the buyer: they take deliverability and infrastructure off your plate and charge per contact. Notifuse inverts that. You get the provider choice and the data, and you take on the database, the upgrades and the sending reputation. The $16 per month cloud tier exists precisely for teams that want the software without that trade.
Maintenance cost and what the release cadence implies
The release history shows v40.0 on 2026-09-05, v39.1 on 2026-09-01 and v39.0 on 2026-08-29, with the last push to the repository on 2026-09-08. Three releases in roughly a week, and a major version number that has reached 40, tells you the project moves quickly. Fast iteration is good for feature coverage and bad for operators who want to upgrade once a quarter. A jump from v39 to v40 is a signal to read release notes before pulling, not to assume the upgrade is mechanical.
Concretely, the upgrade surface includes the database schema, since analytics partitions are created automatically and the README notes that changing attribution rules triggers a backfill. It includes the PostgreSQL major version constraint discussed above. It includes the GeoLite2 database, which you may be refreshing yourself via geoipupdate or by dropping a new GeoLite2-City.mmdb into data/. And it includes the console and notification center frontends, which are separate React builds.
On licensing, the honest position is that this review cannot tell you your obligations. NOASSERTION means the tooling found no recognisable licence text. Whether that is an oversight in the repository metadata or a deliberate custom licence, you need to read the file before you deploy it anywhere that matters. That is not legal advice, it is a statement about what the supplied material does and does not contain.
Editorial conclusion
Adopt Notifuse if you already run PostgreSQL 17, want campaigns and transactional mail behind one API, and would rather drop monthly partition tables than operate a separate analytics store. Do not adopt it if you need a permissively licensed codebase you can relicense or embed, because the repository ships without a machine-readable licence and NOASSERTION tells you nothing about your obligations. Before committing, read the LICENSE file in the repository, confirm the PostgreSQL 17 constraint against your existing cluster, and test the SMTP provider path end to end, since that is the fallback when a hosted provider rejects your sending domain.
Community notes