DarDev runs three cooperating surfaces on dardev-vps that must stay logically separate: Twenty CRM at crm.dardev.net holds relationship and consent metadata, Listmonk at mail.dardev.net holds audiences you may email, and the static news site at news.dardev.net publishes articles plus a narrow subscribe and contact API. Security between them is enforced by network routing, webhook authentication, opt-in gates before CRM rows become list members, and SMTP identity isolated on send.dardev.net.
Procurement teams ask whether a public site can leak CRM exports, whether cold imports can become campaigns overnight, and whether promotional and transactional mail share one reputation bucket. The answers below reflect production configuration from inside-unified-mailer-stack and dogfood-our-own-stack.
Three zones at a glance
- CRM zone (crm.dardev.net): workspace auth, prospect records, API keys—no bulk marketing sends
- Mailer zone (mail.dardev.net): Listmonk lists, templates, campaigns—consented recipients only
- Public zone (news.dardev.net): static HTML, /api/news/subscribe, /api/news/contact—no CRM admin access
- Sync worker (localhost:3100): translates CRM tag changes into Listmonk membership after opt-in rules pass
- SMTP edge (send.dardev.net): Stalwart submission with DKIM on the sending subdomain, not root @dardev.net Zoho
Frequently asked questions
Should news.dardev.net call Listmonk or Twenty directly from the browser?
No. The public site talks only to sync-worker endpoints such as /api/news/subscribe and /api/news/contact. Those handlers validate input, rate-limit abuse, and write through server-side credentials. Browser clients never receive Twenty or Listmonk API keys. That split keeps XSS on a blog page from becoming a CRM export or list purge.
Can a CRM CSV import automatically subscribe everyone to marketing mail?
Not in our stack. Imports land in Twenty with consent:unknown until a public form or documented process records opt-in. The sync worker maps CRM tags like product:hesabi:marketing to Listmonk lists only when explicit flags change—see crm-driven-product-lists. A spreadsheet row alone must never create a campaign audience.
Where do secrets and API keys live?
Twenty, Listmonk, Stalwart, and sync-worker credentials live in VPS environment files loaded by Docker Compose—not in git or client-side JavaScript. Webhook URLs register in admin UIs with shared secrets where supported. Postgres backups that include CRM and list data are treated as confidential production artifacts.
Why isolate outbound mail on send.dardev.net instead of the corporate domain?
Root @dardev.net remains on Zoho for employee and inbound customer mail. Listmonk SMTP uses send.dardev.net with its own SPF, DKIM, and DMARC on GoDaddy DNS; PTR at OVH points to mta.dardev.net. If campaign reputation collapses, helpdesk inboxes do not inherit the blacklist. See why-send-dardev-subdomain.
How are webhooks between CRM, mailer, and sync worker protected?
Twenty posts person events to mail.dardev.net/webhooks/twenty; Listmonk posts unsubscribes to /webhooks/listmonk. Endpoints run behind nginx TLS and validate signatures before mutating CRM notes or list membership. Misconfigured webhooks fail closed in sync logs. Health checks on port 3100 must pass before we treat a deploy as production-ready.
What personal data does the public news site collect?
Subscribe forms collect email plus list selection; contact forms collect name, email, and message for CRM routing. Submissions create or update Twenty people through the sync worker, not a public admin API. Double opt-in applies where supported—see double-opt-in-subscribe-flows. Consent provenance stays in CRM plus Listmonk subscriber metadata.
Can we embed crm.dardev.net in an iframe inside a dashboard?
We avoid it. Twenty workspace auth and cookie policies break when nested in iframes—an issue we hit on Hesabi CRM views. Operators use direct TLS URLs with workspace login. Dashboard widgets should use views and APIs designed for embedding, not the full admin application.
How do marketing and transactional templates stay separated?
Listmonk templates are classified before upload: promotional templates may include product CTAs; transactional templates cover confirmations without upsell modules. Mixing them destroys the boundary in email-compliance-marketing-transactional. CRM tags for customers may sync to product news lists; they do not grant unlimited promotional frequency without marketing opt-in.
What should we check before clicking Send on a Listmonk campaign?
Confirm audience provenance—opt-in form URL or CRM tag history, never a raw import. Verify From addresses use @send.dardev.net. Sample a Twenty row and confirm consent fields match the list name. Review prior bounce and complaint rates and Stalwart queue depth.
Does Hesabi or EscaHire change these boundaries?
Product scope does not relax security rules. Hesabi is Tunisia-only; fiscal data stays in Hesabi infrastructure, not Listmonk. EscaHire is waitlist—not general availability—so cold leads must not auto-sync into launch campaigns. Only CRM tags and list names differ across product lines.
Network and TLS on dardev-vps
Compose services bind to localhost ports—Twenty on 3001, Listmonk on 9002, sync worker on 3100—while host nginx terminates TLS per subdomain. That pattern coexists with other stacks without sharing admin cookies across hostnames. No mailer admin port is exposed raw to the internet; details in integrations-we-actually-run.

When boundaries slip, the first symptoms are operational: spam complaints after a CRM import, SPF failures after a From-address edit, or webhook retries filling logs after a secret rotation. Catch those in weekly reviews rather than at fiscal year audit.
Treat boundary reviews as release discipline. When DNS, webhook secrets, or PRODUCT_LIST_MAP changes, update the runbook your CRM and marketing ops share. For architecture context read inside-unified-mailer-stack; for scope framing read deployed-realities-explained. Inbound questions route through news.dardev.net contact into Twenty—the same path we use for product interest.



