=================================================================
   DChat 2.0 - Self-Hosted Live Chat
=================================================================

DChat 2.0 is a modern self-hosted live chat solution built on
.NET 8, ASP.NET Core, SignalR, and Blazor WebAssembly.

=================================================================
   Upgrading from ZChat (2.0.8 and earlier)
=================================================================

The product was renamed from ZChat to DChat. Your data, settings and
customers' pages need no changes, but the server program itself has
a new file name, and anything that starts it by name must be pointed
at the new one:

  Server program:  ZChat.Web.dll / ZChat.Web.exe
               ->  DChat.Web.dll / DChat.Web.exe

  - Windows service: point the existing service at the new exe
    (run in an elevated prompt; the service keeps its old name):
        sc stop ZChatServer
        sc config ZChatServer binPath= "C:\ZChat\Web\DChat.Web.exe --urls http://0.0.0.0:5000"
        sc start ZChatServer
  - IIS: the package's Web\web.config already names DChat.Web. If you
    kept your own web.config, change processPath/arguments from
    ZChat.Web to DChat.Web.
  - dotnet command lines, scripts, systemd units: ZChat.Web.dll ->
    DChat.Web.dll.
  - After extracting the new package over the old folder, delete the
    old ZChat.*.dll / ZChat.*.exe / ZChat.*.pdb files in Web\. If they
    stay and something still starts ZChat.Web, you are running the OLD
    version. http://your-server/api/health says which is running:
    "service": "DChat Server" and its version.

What keeps working without any change:
  - zchat.lic (DChat.lic / dchat.lic are also found)
  - appsettings.json naming the connection string "ZChat", and its
    "ZChat" log-level overrides
  - the database (its default name stays ZChat / zchatdemo)
  - widget embeds using /widget/zchat.iife.js and ZChat.init(),
    ZChat.open() etc. (window.ZChat is the same object as window.DChat);
    page CSS for #zchat-widget-root and --zchat-* custom properties
  - webhook receivers checking X-ZChat-Signature (every X-DChat-* header
    is also sent under its X-ZChat-* name); mail gateways and API
    inboxes sending X-ZChat-Inbound-Secret / X-ZChat-Inbox-Token; agent
    bots sending X-ZChat-Bot-Token
  - Docker: a .env with ZCHAT_SA_PASSWORD / ZCHAT_JWT_SECRET; the
    zchat-data / zchat-appdata / zchat-ollama volumes are reused
  - signed-in dashboard sessions, and the desktop agent's settings
    (copied from %LocalAppData%\ZChat on first start)
  - robots.txt rules for ZChatBot (the crawler is now DChatBot and
    honours rules for either name)

=================================================================
   Quick Start
=================================================================

1. Install prerequisites:
   - .NET 8 ASP.NET Core Runtime or later:
   https://dotnet.microsoft.com/download/dotnet/8.0
   - SQL Server Express or SQL Server 2014+
   - SQL Server command-line tools (sqlcmd) if you want run-local.cmd
     to create the demo database automatically.

2. Try the demo (one command):
   - Just run:
       run-local.cmd
   - On first run this creates the demo database (Server=.\SQLEXPRESS;
     Database=zchatdemo), installs the schema and the seed accounts,
     then starts DChat on:
       http://127.0.0.1:5050/dashboard
   - Re-running is safe: an already-set-up database is detected and
     left untouched.
   - Sign in with the seeded demo accounts:
       Admin: admin / admin123
       Agent: agent1 / agent123
     These accounts are for first-run evaluation only. The first
     sign-in with either one must set a new password before anything
     else works (the dashboard asks for it).
   - Try the visitor chat widget against your running server at:
       http://127.0.0.1:5050/widget-test.html

   Options (run-local.ps1):
       -Port 5060                 use a different port
       -Server ".\SQL2019"        target a different SQL instance
       -Database "zchatdemo2"     use a different database name
       -OpenBrowser               open the dashboard automatically
       -SkipDatabaseSetup         never touch the database
       -ConnectionString "..."    use a full custom connection string
                                  (disables automatic database setup)

3. Production install on your own SQL Server (manual):
   - Create a blank database (e.g., "ZChat" - the default name, kept from before the product was renamed)
   - Run install-source\ZCHAT-APP-DB.sql to create tables
   - Run install-source\seed-data.sql to create default accounts
   - No licence file is needed. DChat runs as the free Community
     edition with unlimited agents when Web\DChat.lic is absent.
   - Update Web\appsettings.json with your connection string
     or copy from Web\appsettings.example.json

4. Start the server:
   cd Web
   dotnet DChat.Web.dll --urls "http://localhost:5000"

5. Open Dashboard in browser:
   http://localhost:5000/dashboard

   Default accounts (created by seed-data.sql):
     Admin: admin / admin123
     Agent: agent1 / agent123
   These are seed accounts, not production credentials. Sign in once,
   create your real admin/agent accounts or change both passwords, and
   remove any accounts you do not need.

6. Add chat widget to your website:

   Option A - Self-initializing (recommended):
   <script src="http://YOUR-SERVER:5000/widget/dchat.iife.js"
           data-server-url="http://YOUR-SERVER:5000"
           data-site-id="default"></script>

   Option B - Programmatic:
   <script src="http://YOUR-SERVER:5000/widget/dchat.iife.js"></script>
   <script>
     DChat.init({ serverUrl: 'http://YOUR-SERVER:5000', siteId: 'default' });
   </script>

=================================================================
   Verify Your Install
=================================================================

After the server starts, run the checks in VERIFY-INSTALL.txt.
They confirm:
  - license and database readiness
  - dashboard login
  - seeded accounts
  - widget JavaScript and widget test page
  - public widget status/offline-message APIs

=================================================================
   Inbound Email (optional)
=================================================================

   DChat can accept support requests by email. Messages land in the
   Missed Chats page alongside offline web messages, so your team
   answers them with the reply-by-email action already there.

   DChat does not connect to a mailbox itself. A mail gateway hands
   the already-split message over by HTTP, which means this works
   with your own mail server and needs no extra library:

     Postfix/Exim pipe  ->  a small script that POSTs the fields
     internal relay     ->  same
     hosted inbound-parse webhook (if you use one)

   The endpoint is DISABLED until you set a secret. Leaving it empty
   is deliberate - an open inbound endpoint would let anyone forge a
   support request from any address.

   1. Put a long random value in Web\appsettings.json:

        "Email": { "InboundSecret": "<a long random string>" }

   2. Have your gateway POST to:

        POST /api/v1/email/inbound
        X-DChat-Inbound-Secret: <the same value>
        Content-Type: application/json

        {
          "from":      "\"Ada Lovelace\" <ada@example.com>",
          "subject":   "Cannot sign in",
          "text":      "I get an error on the login page.",
          "messageId": "<abc@mail.example.com>",
          "inReplyTo":  null,
          "references": null,
          "headers":   { "Auto-Submitted": "no" }
        }

      Only "from" plus one of "subject"/"text" are required. Passing
      "messageId", "inReplyTo", "references" and "headers" is strongly
      recommended: they let a customer's follow-up join the same
      conversation instead of opening a duplicate, and they let DChat
      recognise autoresponders.

   Responses:
      200 {"accepted":true, ...}   stored (or threaded onto an existing
                                   conversation)
      200 {"accepted":false, ...}  understood and deliberately dropped -
                                   an autoresponder, bounce or list mail.
                                   Do NOT retry these.
      400                          malformed (no usable From address)
      401                          wrong or missing secret
      404                          feature not enabled (no secret set)

   Notes:
     - Automated mail is dropped rather than answered, so DChat and an
       out-of-office autoresponder cannot mail each other in a loop.
     - A reply is only threaded when it comes from the same address as
       the original, so a forwarded thread cannot be appended to
       someone else's conversation.
     - If a webhook URL is configured, an inbound email also raises an
       EMAIL_RECEIVED notification to Slack/Teams/Discord.

   Sender authentication (recommended):
     A From header is whatever the sender typed. If your gateway knows
     whether the message passed SPF, DKIM and DMARC, pass that on and
     DChat will not let a forged sender into a real customer's thread.
     Either add the verdicts as fields:

        "spf":   "pass",       (pass, fail, softfail, none, ...)
        "dkim":  "pass",
        "dmarc": "fail"

     or forward the receiving server's Authentication-Results header
     in "headers":

        "headers": { "Authentication-Results":
          "mx.example.com; spf=pass smtp.mailfrom=example.com;
           dkim=fail header.d=example.com; dmarc=fail" }

     DMARC decides when present; otherwise one passing SPF or DKIM
     result is enough. When the verdict is present and failing, the
     message is NOT threaded onto the sender's earlier conversation or
     linked to their contact: it starts a new conversation marked
     "[Sender not verified]" with a private note for agents (the
     response carries "senderVerified": false). Replies still go to
     the address the mail claimed, so a forger never sees them. With
     no verdict at all, mail is handled as before. The same fields
     work for email inboxes.
=================================================================
   Editions: Community, Premium, Enterprise
=================================================================

  Community   Free, unlimited agents, no licence file. The whole live
              chat product: widget, dashboard, email, your own database.
  Premium     $19 per agent per month. Adds the AI assistant, custom
              roles, removing "Powered by DChat", automation rules,
              macros, auto-assignment, per-agent chat limits, required
              attributes, CSAT review notes, extra channels (Telegram,
              WhatsApp, SMS, Messenger, Instagram, LINE, API), voice
              through your own Twilio number, and Slack integration.
  Enterprise  $49 per agent per month. Premium plus single sign-on
              (OpenID Connect or SAML 2.0) and advanced SLA policies.

  Details: https://dchat.com/pricing.aspx. The dashboard's Plan page
  shows which tier this server runs and what each tier adds.

  What a lapse does, feature by feature (nothing is ever deleted):
    Extra channels  Customers' messages keep arriving and are stored;
                    agents cannot reply through the channel.
    Voice           Calls keep being filed and go to voicemail; the
                    softphone and click-to-call stop.
    Slack           Nothing more is posted to Slack or relayed back.
    Single sign-on  The SSO button disappears and "require SSO" stops
                    being enforced: every agent signs in with a password
                    again. The configuration stays stored.
    Everything else The feature switches off; its settings, rules,
                    macros and roles are kept for when you renew.
  A paid feature can always be switched OFF on any tier; switching it
  ON needs the tier. Refusals from the API carry the code
  requires_premium or requires_enterprise.

=================================================================
   Upgrading from 2.0.9 to 2.0.10 - read this first
=================================================================

  - No database script to run and no configuration change required.
    Stop the server, replace the Web folder (keep Web\appsettings.json,
    your licence file if you have one, and Web\App_Data), and start it
    again. The new tables and columns are created at startup:
      EmailThreadMessage                      email threading
      WidgetSite, SupportSession.SiteId       multi-site widgets
      ChannelSurvey, Inbox.SurveyJson         channel surveys
      Company, CompanyDomain, CompanyAttribute,
      CompanyNote, Contact.CompanyId          companies
      ReportEmailSubscription, ReportEmailSend
                                              scheduled report emails
    Existing data is left alone.

  - Email inboxes now keep one conversation per email thread, not one
    per sender. New mail joins a conversation through its In-Reply-To /
    References headers (also for mail answered before the upgrade);
    mail without them joins the sender's latest open conversation only
    when the subject matches within 7 days (inbox setting "Subject
    threading window (days)", 0 = off). So a customer who writes about
    a new subject now gets a new conversation. Conversations from
    before the upgrade are not split or merged. Agent replies by email
    now carry their own Message-ID and threading headers, and web
    addresses in them become links.

  - New in 2.0.10:
      Multiple sites      a Sites page: several websites on one
                          install, each with its own embed id, allowed
                          domains, default department and team, and
                          its own report. Free. A site's own look
                          (colour, texts, position, pre-chat form) is
                          Premium except on the first site; without
                          Premium every site uses the Widget Builder
                          look. Existing snippets without a site id
                          behave exactly as before.
      Channel surveys     a satisfaction survey (CSAT, or NPS per
                          inbox) sent through the channel when a
                          conversation on email, WhatsApp, Messenger,
                          Instagram, Telegram, LINE or SMS is resolved,
                          plus an NPS report. Inboxes that existed
                          before the upgrade have surveys OFF; new
                          inboxes start with CSAT on. Email surveys and
                          NPS are free; on a channel that needs Premium
                          the survey needs it too.
      Companies           company records with domains, attributes and
                          notes; contacts link to one company; an
                          activity timeline on contacts and companies.
                          Free.
      Report emails       daily or weekly team summaries (figures only,
                          with a CSV) to agents who can view reports,
                          at their own hour and time zone. Free; needs
                          SMTP to be configured.
      API reference       /api/docs now lists every public API
                          operation, generated from the server itself,
                          with who may call it.
      Typing preview      agents can see a visitor's message while it
                          is typed. OFF by default (Settings, "Typing
                          preview"); when on, the widget tells the
                          visitor so.

  - Nothing else is required.

=================================================================
   Upgrading from 2.0.8 to 2.0.9 - read this first
=================================================================

  - 2.0.9 is the first release under the name DChat. Read "Upgrading
    from ZChat (2.0.8 and earlier)" at the top of this file first: the
    server program is now DChat.Web.exe / DChat.Web.dll, so a Windows
    service, an IIS web.config of your own, or any script that starts
    ZChat.Web must be pointed at the new name, and the old ZChat.*
    files in Web\ deleted. Nothing else breaks: old widget snippets
    (zchat.iife.js, ZChat.init), zchat.lic / ZChat.lic, X-ZChat-*
    webhook and inbound headers, the "ZChat" connection string, the
    ZCHAT_* Docker variables and the zchat-* Docker volumes all keep
    working.

  - No database script to run. Stop the server, replace the Web folder
    (keep Web\appsettings.json, your licence file if you have one, and
    Web\App_Data), and start it again. The new tables (visitor shop
    events and shop triggers, import jobs, AI action runs, bot flows
    and their versions and statistics, legal holds) are created at
    startup; existing data is left alone.

  - New in 2.0.9, all switched off or empty until you set them up:
      E-commerce events   product viewed, cart value and checkout
                          abandoned from the widget SDK and Shopify,
                          a shop timeline beside the conversation, and
                          cart/abandonment proactive triggers with
                          product cards (Premium).
      Importers           history from Chatwoot (API), Intercom (JSON
                          export) and contacts CSV, with a dry-run
                          preview; imported conversations are closed
                          history and fire no webhooks, rules, email or
                          AI. Free on every edition.
      AI actions          the AI assistant calls HTTP actions you
                          define, with agent approval or visitor
                          confirmation; actions that change data
                          default to agent approval (Premium).
      Bot flows           visual bot flows with a simulator, versions
                          and per-step statistics. Free; AI steps need
                          the AI assistant (Premium).
      AI auto-triage      the automation action "AI classify" sets
                          labels, sentiment, priority, language and
                          department, or only suggests them; it never
                          overrides an agent (Premium).
      Required 2FA        require two-factor sign-in for admins or
                          everyone. Users without an authenticator can
                          still sign in, but only to enrol one. Single
                          sign-on sign-ins are exempt. Free.
      IP allowlist        limit dashboard and API access to listed
                          addresses (Enterprise). If you lock yourself
                          out, set Security:IpAllowlist:Disabled to
                          true in Web\appsettings.json and restart.
      Retention           delete conversations older than a set age,
                          with a preview, a confirm step and legal
                          holds. Free; off by default.

  - New look. The dashboard and the chat widget have a calmer,
    monochrome style. A widget colour you chose (in the Widget Builder
    or in the embed snippet) is kept and still recolours the widget; a
    site that never chose one now shows the new near-black default.

  - Nothing else is required.

=================================================================
   Upgrading from 2.0.7 to 2.0.8 - read this first
=================================================================

  - No database script to run. Stop the server, replace the Web folder
    (keep Web\appsettings.json, your licence file if you have one, and
    Web\App_Data), and start it again. The new knowledge-source, bot
    test and AI-feedback tables are created at startup; existing data
    is left alone.

  - Licence file name. 2.0.8 looks for ZChat.lic and zchat.lic, so a
    licence keeps working under either spelling, also on a
    case-sensitive (Linux/Docker) file system. Keep the file you have.

  - Knowledge sources (Premium, AI assistant) need an embeddings model
    for meaning-based search. Set Chatbot:EmbeddingModel, or leave it
    empty for the provider's default:
      OpenAI     text-embedding-3-small (the default)
      Ollama     nomic-embed-text (the default; run
                 "ollama pull nomic-embed-text" on the Ollama host)
      Anthropic  has no embeddings API: keyword search only
    Set it to "none" to switch embeddings off. Without embeddings the
    assistant still answers from your sources by keyword match.

  - Website crawls follow the same rule as webhooks: private and
    internal addresses are refused unless Webhooks:AllowPrivateTargets
    is true. Set it only if you deliberately crawl an intranet site.

  - Nothing else is required. Knowledge sources and the bot test
    bench start empty, and the widget asks "Did that help?" only under
    answers from the AI assistant.

=================================================================
   Upgrading from 2.0.6 to 2.0.7 - read this first
=================================================================

  - No database script to run. Stop the server, replace the Web folder
    (keep Web\appsettings.json, Web\zchat.lic if you have one, and
    Web\App_Data), and start it again. New tables (teams, Linear links,
    SMS/WhatsApp campaigns, dashboard apps) and columns are created at
    startup; existing data is left alone.

  - The first start takes longer on a large database. 2.0.7 adds six
    indexes to SupportSession and SupportMessage (they make the inbox
    list about 20 times faster). They are built once, at the first
    start, before the server answers: on a database with millions of
    messages this can take several minutes. Let it finish; if the server
    is stopped midway, the build simply starts again next time.

  - Snapshot reads. At startup DChat switches on
    ALLOW_SNAPSHOT_ISOLATION for its database so the inbox list no
    longer blocks live chats under load. That needs ALTER permission on
    the database. If DChat's SQL login does not have it, DChat works
    exactly as before and logs one warning. To turn it on, run this once
    as the database owner (DChat is the default database name; use
    yours), then restart DChat:

      ALTER DATABASE [ZChat] SET ALLOW_SNAPSHOT_ISOLATION ON

  - Nothing else is required. The new features start switched off or
    empty: teams, dashboard apps, Linear, Shopify and SMS/WhatsApp
    campaigns.

=================================================================
   Upgrading from 2.0.5 to 2.0.6 - read this first
=================================================================

  - No database script to run. Stop the server, replace the Web folder
    (keep Web\appsettings.json, Web\zchat.lic if you have one, and
    Web\App_Data), and start it again. SchemaBootstrapService creates the
    new tables and columns at startup and leaves existing data alone.

  - Seed passwords must be changed. An account still on a shipped demo
    password (admin/admin123, agent1/agent123) can only set a new
    password: every other API and SignalR call answers 403
    password_change_required until it does. The dashboard shows the
    change-password screen; the desktop app asks you to change it in the
    web dashboard first.

  - Access tokens are narrower. A personal access token can no longer
    change credentials (passwords, two-factor, single sign-on, tokens,
    channel secrets), reveal a secret, or create or promote an
    administrator - those actions need a signed-in person. A token also
    stops working when its owner's password changes, and when single
    sign-on is required for its (non-administrator) owner. Scripts that
    did any of this with a token must now be done in the dashboard.

  - Widget identity: the secret is now revealed only with
    POST /api/v1/widget-identity/secret/reveal from a dashboard sign-in
    (audited). The old GET ...?reveal=true answers 400 reveal_moved.
    Update any script that read the secret that way.

  - Widget allowed domains (Widget Builder) start empty, and an empty
    list allows every site, as before. Nothing changes until you fill it
    in.

  - Business hours can now be set per inbox, with an optional away
    reply that stays off until you switch it on. An inbox without its
    own hours uses the global schedule, as before.

  - Desktop app: download dchat-agent-console.zip 2.0.6. It still talks
    to a 2.0.5 server, but single sign-on from the app, signatures and
    template variables ({{variables}} in canned replies) need a 2.0.6
    server; on an older one the SSO button stays disabled and variables
    are inserted as written.

=================================================================
   Upgrading from 2.0.4 to 2.0.5 - read this first
=================================================================

  - No database script to run. Stop the server, replace the Web folder
    (keep Web\appsettings.json, Web\zchat.lic if you have one, and
    Web\App_Data), and start it again. SchemaBootstrapService creates the
    new tables and columns at startup and leaves existing data alone.

  - Auto-assignment starts OFF. It now uses a new setting,
    AutoAssign.Enabled, switched on only from Automation > Auto-assignment
    in the dashboard. The old "AutoAssign" value that earlier Settings
    pages saved (often ticked without anyone choosing it) is kept but
    never read, so an upgrade never starts handing out your queue.
    Auto-assignment is a Premium feature.

  - Some features are now Premium or Enterprise (see Editions above).
    Without a licence the server runs as Community: live chat, the
    widget and the dashboard keep working with unlimited agents.

  - What a lapse does: when paid coverage ends, the paid features switch
    off and NOTHING else does. Your settings, rules, macros and roles are
    kept for when you renew; messages on extra channels keep arriving and
    calls keep being filed (replying through them needs Premium). Agents
    are never locked out.

  - Seats are an honour system. The Plan page shows agents active against
    agents covered, and administrators see a reminder when more are
    active; nothing is ever blocked.

  - Founding customers keep their tier for life. A one-time licence you
    already bought runs as Premium. An Enterprise or Source Code licence
    bought through the client center runs as Enterprise. Licences from
    the older zchat.com store can only be recognised as Premium; if you
    bought Enterprise there, ask us for a replacement licence.

=================================================================
   Upgrading to 2.0.4 (from earlier 2.0 releases)
=================================================================

  - Everyone signs in again once. Sessions are now tied to the account's
    password, so a password change, an administrator reset, or deleting
    the account ends every other session for that account within seconds.
    Tokens issued by earlier builds carry no such binding and are refused.

  - Behind a reverse proxy (nginx, IIS ARR, Cloudflare)? DChat no longer
    trusts the X-Forwarded-For header by default - a visitor could forge it
    to evade the IP block list and the per-address rate limits. Add your
    proxy to "Network" in Web\appsettings.json or every visitor is recorded
    as the proxy's address:

        "Network": {
          "TrustedProxies": [ "10.0.0.5" ],
          "TrustedProxyNetworks": [ "10.0.0.0/8" ],
          "ForwardLimit": 1
        }

  - Webhooks may only target public addresses. Localhost, private networks
    and link-local addresses (including cloud metadata services) are
    refused when the URL is saved and again before every send, and
    redirects are not followed. If your receiver really is on your private
    network, set "Webhooks": { "AllowPrivateTargets": true } and accept
    that the server can then be pointed at anything on that network.

  - Database setup repairs itself. If an earlier install stopped part way
    through creating tables, launching this build against that database
    creates whatever is missing; a complete database is left untouched.

=================================================================
   Operator settings (Web\appsettings.json)
=================================================================

  Jwt:SecretKey               Generated on FIRST RUN, on this machine, and
                              stored in Web\App_Data\jwt-signing-key. The
                              download contains no key: one baked into a
                              public package would be the same for everyone
                              who downloaded it. Delete the file to sign
                              every agent out; back it up with App_Data.
                              Set this value explicitly if you run more
                              than one instance behind a load balancer, or
                              a container without a persistent App_Data
                              volume - otherwise each instance mints its
                              own key and rejects the others' tokens.
  Network:TrustedProxies      Reverse proxies whose X-Forwarded-For to
  Network:TrustedProxyNetworks  believe. Empty = header ignored (default).
  Webhooks:AllowPrivateTargets  true to allow private/local webhook receivers.
                              Default false.
  Uploads:RetentionDays       Delete chat attachments older than N days.
                              0 = keep indefinitely (default).
  Channels:SecretKey          Key that encrypts inbox secrets (bot tokens,
                              app secrets, auth tokens), the SSO client
                              secret, the Slack tokens and the widget
                              identity secret in the database. Generated on
                              first run into Web\App_Data\channel-secrets-key.
                              Pin it (base64 of 32 random bytes) when several
                              instances share one database. Lose the file and
                              those secrets must be re-entered.
  Channels:PublicBaseUrl      Fallback for the Public URL on the Inboxes page
                              (the page setting wins). See Channels below.
  Push:Subject                Contact for push services: a mailto: address
                              or an https URL of yours. Default is
                              https://dchat.com/ - set your own.
  Push:VapidPrivateKey        Web push key (base64 PKCS#8, P-256). Generated on
                              first run into Web\App_Data\vapid-keys. Pin it
                              when several instances share one database.
  Push:AllowPrivateEndpoints  true to allow non-https / private push
                              endpoints (testing only). Default false.
  Slack:SyncSeconds           How often new conversations are posted to Slack
                              (1-300, default 5).
  HelpCenter:ViewFlushSeconds How often article view counts are written
                              (default 30).


=================================================================
   Security notes - read before going live
=================================================================

  - The first sign-in with admin/admin123 or agent1/agent123 must set a
    new password before anything else works (the dashboard asks for it;
    the desktop app tells you to do it in the web dashboard). Delete the
    seed accounts once you have your own administrator.
  - Web\App_Data holds per-install secrets generated on first run:
      jwt-signing-key       signs every agent session
      channel-secrets-key   decrypts inbox, SSO, Slack and widget
                            identity secrets stored in the database
      vapid-keys            the web push key pair
    plus uploads, the knowledge base and logs. Back it up with the
    database. NEVER publish it, serve it, commit it or copy it into a
    public package: anyone holding these files can forge agent sessions
    and read your channel credentials.
  - Behind a reverse proxy, list it in Network:TrustedProxies (see
    "Upgrading to 2.0.4") - otherwise every visitor is recorded as the
    proxy's address, and rate limits and IP blocks act on the proxy.
  - Serve DChat over HTTPS. Telegram, Meta, LINE, Slack and browsers'
    push services all refuse plain http.

=================================================================
   Public URL (needed by channels, voice and file sending)
=================================================================

  Dashboard > Inboxes > Public URL: the address the outside world
  reaches this server at, e.g. https://chat.example.com (no path, no
  query). Channels:PublicBaseUrl in appsettings.json is the fallback.

  - It must be PUBLIC and HTTPS: providers call it from the internet.
  - It must match EXACTLY what you paste into each provider (scheme,
    host, port). Twilio signs every request against the exact URL it
    called; a mismatch (http vs https, a different host name behind a
    proxy) fails the signature check with 401.
  - Each inbox then shows its webhook URL:
        https://chat.example.com/api/v1/channels/{inboxId}/webhook
    (a Voice inbox shows /api/v1/voice/{inboxId}/incoming instead).
  - Sending files through WhatsApp (Meta or Twilio) and Twilio SMS
    needs it (https): the provider downloads the file from here.

=================================================================
   Channels (Dashboard > Inboxes)
=================================================================

  Email inboxes are free. Every other channel needs Premium to create
  or change; after a lapse an existing inbox keeps receiving but cannot
  be replied through (switching it off always works). Secret fields are
  encrypted, never shown again, and "generated" fields (verify tokens,
  inbound tokens) are shown ONCE when the inbox is created - copy them
  then, or rotate them later to see a new value.

  Telegram
    1. Create a bot with @BotFather (/newbot); paste the Bot token.
    2. Save, then press Connect webhook. DChat calls Telegram's
       setWebhook with the webhook URL and a generated secret (sent back
       in X-Telegram-Bot-Api-Secret-Token). Needs an https Public URL.

  SMS (Twilio) / WhatsApp (Twilio)
    1. Paste Account SID and Auth token (the token verifies
       X-Twilio-Signature). Give a From number in E.164 (+14155550100)
       or a Messaging service SID.
    2. Twilio console, the number / messaging service / WhatsApp sender:
       "A message comes in" = Webhook, HTTP POST, the inbox webhook URL.
    3. The URL in Twilio and the Public URL must match character for
       character, or every message is refused.
    WhatsApp: free-form replies only within 24 hours of the customer's
    last message; after that use an approved template (create it in
    Twilio's Content Template Builder, then press Sync templates).

  WhatsApp Cloud API (Meta)
    1. Paste Phone number ID, Access token (system-user token with
       whatsapp_business_messaging), App secret (verifies
       X-Hub-Signature-256) and optionally the WhatsApp Business Account
       ID (needed for Sync templates).
    2. Meta app dashboard > WhatsApp > Configuration: Callback URL = the
       inbox webhook URL, Verify token = the value shown when the inbox
       was created. DChat answers Meta's hub.challenge. Subscribe the
       "messages" field.

  Facebook Messenger / Instagram DMs (Meta)
    1. Paste Page ID (Instagram: the Instagram account ID), Page access
       token and App secret.
    2. Meta app dashboard > Messenger (or Instagram) > Webhooks: Callback
       URL = the inbox webhook URL, Verify token = the generated value.
       Subscribe to "messages". Replies only within 24 hours.

  LINE
    1. Paste Channel secret (verifies X-Line-Signature) and Channel
       access token.
    2. LINE Developers console > Messaging API: Webhook URL = the inbox
       webhook URL, turn "Use webhook" ON and auto-reply messages OFF.
       Replies use LINE's push API and count against your LINE quota.

  API (your own app)
    - Your app POSTs JSON to the inbox webhook URL with the header
      X-DChat-Inbox-Token: <inbound token> (or Authorization: Bearer):
        {"contact":{"identifier":"customer-42","name":"Jane",
                    "email":"jane@example.com"},
         "message":{"id":"your-unique-id","text":"Hello"}}
      message.id makes retries safe (duplicates are ignored).
    - Agent replies are POSTed to your Callback URL (public http(s))
      with X-DChat-Timestamp and
      X-DChat-Signature: sha256=HMAC-SHA256(hmac secret,
                                            timestamp + "." + body).

  Email (free)
    - Mode "conversation" files mail as a conversation in this inbox;
      "missed_chat" keeps the old Missed Chats behaviour.
    - Your mail gateway POSTs the same JSON as /api/v1/email/inbound
      (see Inbound Email above) to the inbox webhook URL with
      X-DChat-Inbound-Secret: <the inbox's inbound secret>.
    - Agent replies go out through the SMTP server in Settings.

=================================================================
   Voice (Twilio, Premium)
=================================================================

  1. Buy or reuse a Twilio voice number. Inboxes > new inbox > Voice
     (Twilio): Account SID, Auth token, Phone number (E.164), Ring mode
     (browser / forward / browser_then_forward), Forward to, ring time,
     voicemail and transcription options.
  2. Twilio console > the number > Voice configuration:
       A call comes in     = Webhook, HTTP POST,
                             https://YOUR-HOST/api/v1/voice/{inboxId}/incoming
       Call status changes = https://YOUR-HOST/api/v1/voice/{inboxId}/status
  3. Browser softphone (optional): in Twilio create an API key (SK...)
     and a TwiML App (AP...) whose Voice URL is
       https://YOUR-HOST/api/v1/voice/{inboxId}/outbound  (HTTP POST)
     (the Calls page shows the exact URLs). Paste API key SID, API key
     secret and TwiML app SID into the voice inbox.
  4. Agents open Calls and press "Switch softphone on" to be rung in
     the browser, or use "Call via my phone" for click-to-call.
  5. Optional: "Outbound destinations" on the voice inbox limits the
     numbers agents can call to the country codes you list (for example
     +1, +44). Outbound calls are billed to your Twilio account; empty
     allows any number.
  As with SMS, the Public URL must match what Twilio calls exactly.

=================================================================
   Single sign-on (Dashboard > Single Sign-On, Enterprise)
=================================================================

  OpenID Connect (Entra ID, Okta, Google, Keycloak, Auth0 ...)
    - Register an app with your provider using the redirect URI the
      page shows:  https://YOUR-HOST/api/v1/sso/oidc/callback
    - Fill in Authority (issuer URL, https; discovery is read from
      {authority}/.well-known/openid-configuration), Client ID, Client
      secret, Scopes (default "openid profile email") and Client
      authentication (client_secret_basic or client_secret_post).
      Press Check discovery to catch typos.

  SAML 2.0 (ADFS, Okta, Entra ID, OneLogin, Shibboleth ...)
    - Give your IdP this server's SP metadata URL:
        https://YOUR-HOST/api/v1/sso/saml/metadata
      (ACS: https://YOUR-HOST/api/v1/sso/saml/acs, HTTP-POST).
    - Import the IdP's metadata (URL or pasted XML): it fills the IdP
      entity ID, sign-on URL (HTTP-Redirect) and signing certificate(s).
      Responses must be signed; IdP-initiated sign-in is not accepted.

  Both protocols
    - Public server URL: set it when a proxy rewrites the host, so the
      redirect URI / metadata carry your public address.
    - Email, login name and display name claims (defaults email,
      preferred_username, name). An email the IdP marks unverified is
      refused.
    - Allowed email domains (one per line, exact match; empty = any).
    - "Create an agent account on first sign-in" (JIT) with an optional
      role for new agents (never Admin).
    - "Require single sign-on for agents" refuses password sign-in for
      non-administrators. Administrators ALWAYS keep password sign-in:
      that is the break-glass path when the IdP is down. Keep at least
      one administrator with a strong password (and two-factor).

=================================================================
   Help center (Dashboard > Help Center, free)
=================================================================

  - Create a portal, categories and articles; publish the portal. It is
    served at https://YOUR-HOST/hc/{portal-slug}/{locale}.
  - Custom domain: enter e.g. help.example.com in the portal settings,
    then point that name's DNS (CNAME/A) at this server and bind the
    host name (and its TLS certificate) in IIS / your reverse proxy so
    requests reach DChat with that Host header. The portal then answers
    at the domain's root; /api, /widget and /dashboard still work there.
  - Widget search is opt-in: tick "Offer this portal's articles in the
    chat widget" on ONE published portal. Until then the widget shows
    no "Search help articles" entry.
  - Import knowledge base copies the App_Data\kb files into a
    portal as draft articles.

=================================================================
   Web push notifications
=================================================================

  Agents turn them on per browser under My Profile > Browser
  notifications (with a "Send a test" button).
  - The key pair is generated per install in Web\App_Data\vapid-keys.
    Back it up; replacing it makes browsers subscribe again.
  - Set Push:Subject to "mailto:you@example.com" (or your https URL).
  - Browsers only allow push on HTTPS (or http://localhost). On iPhone
    and iPad the dashboard must be added to the home screen first.

=================================================================
   API access tokens and API reference
=================================================================

  - Each agent creates tokens under API Tokens (scope read or write,
    optional expiry, at most 25 active). The token (zat_...) is shown
    once. Send it as:  Authorization: Bearer zat_...
  - A token acts with its owner's CURRENT rights; demoting or deleting
    the owner shrinks or stops it. Read tokens may only GET. No token
    can manage tokens, sign-in, push subscriptions or the live hub.
  - No token, whatever its scope, can change credentials or make an
    administrator: create, change or delete agents, reset two-factor,
    change roles, single sign-on, Slack or widget-identity settings,
    write secret-bearing or webhook settings, rotate inbox secrets,
    reveal the widget identity secret, or get a voice softphone token
    or place a call. Sign in to the dashboard for those.
  - A token stops working when its owner's password changes (their own
    change or an administrator's reset), and, while single sign-on is
    required, an agent's tokens do not work at all (administrators,
    who keep password sign-in, keep theirs).
  - Administrators see and revoke everyone's tokens; every create and
    revoke is in the Audit Log.
  - API reference:  https://YOUR-HOST/api/docs
    (OpenAPI document: /api/docs/openapi.json)

=================================================================
   Widget identity verification and JavaScript API
=================================================================

  1. Widget Builder > Identity validation > Generate secret. Keep it on
     your server; never put it in a page.
  2. On YOUR server compute, for the signed-in user's id:
       identifierHash = lower-case hex HMAC-SHA256(secret, identifier)
     using the secret string exactly as shown (do not hex-decode it):
       Node: require('crypto').createHmac('sha256', SECRET)
                 .update(String(user.id)).digest('hex')
       PHP:  hash_hmac('sha256', $user->id, $secret)
       C#:   Convert.ToHexString(HMACSHA256.HashData(
                 Encoding.UTF8.GetBytes(secret),
                 Encoding.UTF8.GetBytes(userId))).ToLowerInvariant()
  3. In the page:
       DChat.setUser('user-4711', { identifierHash: '...',
                     name: 'Ann Lee', email: 'ann@example.com',
                     plan: 'pro' });
  A match shows "Verified identity" and files the chat under that
  contact. Tick "Ignore identities that do not verify" so a page cannot
  attach itself to someone else's history. Replacing the secret
  invalidates every hash computed with the old one.

  JavaScript API (window.DChat):
    setUser(identifier, { identifierHash, name, email, phone, ...attrs })
    setCustomAttributes({ plan: 'pro', seats: 12 })   (null removes)
    open() / close() / toggle()
    reset()          forget the user and conversation (call on sign-out)
    on(event, fn)    events: ready, open, close, message, unread;
                     returns an unsubscribe function

=================================================================
   Slack (Dashboard > Integrations, Premium)
=================================================================

  1. Create a Slack app with bot scopes chat:write and users:read and
     install it to your workspace.
  2. Event Subscriptions: Request URL =
       https://YOUR-HOST/api/v1/integrations/slack/events
     (public HTTPS; the page shows the URL as this server sees itself -
     use your public address if behind a proxy). Subscribe to the bot
     events message.channels (and message.groups for a private channel).
  3. Invite the bot to the channel. Paste the Bot token (xoxb-...),
     Signing secret and the Channel ID (C0123ABCDEF, not the name),
     optionally a Dashboard address for links, tick "Post new
     conversations to Slack and relay thread replies", Save, then Test
     connection.
  Each new conversation becomes a thread; replies in the thread reach
  the customer. Only conversations after switching on are posted.

=================================================================
   Detailed Installation Guide
=================================================================

See Document\install.htm for step-by-step instructions.

=================================================================
   Components
=================================================================

Web\           - DChat Server (API + Dashboard + Widget)
                 Runs as a standalone .NET 8 application.
                 Can be hosted behind IIS, Nginx, or as a Windows Service.
                 No licence file: runs as the free Community edition.

Console\       - DChat Desktop Agent (Windows)
                 See Console\README.txt for build instructions.
                 WinUI 3 app requires Visual Studio 2022 to compile.

Document\      - Installation guide and documentation.

WebForm\       - Standalone evaluation download form. Open
                 WebForm\download-request.html to reuse the same package
                 preflight and direct-download flow on another website.

run-local.cmd  - Quick local demo launcher (Windows). Sets up the demo
                 database on first run and opens the dashboard.
run-local.ps1  - PowerShell launcher with overridable DB and port settings.
run-local.sh   - Quick local demo launcher (Linux / macOS). Same first-run
                 database setup using sqlcmd.
docker-up.sh   - Docker launcher (Linux / macOS). Generates a .env with
docker-up.cmd    unique secrets, then brings up the full stack.
.env.example   - Template for Docker secrets (DB password, JWT signing key).

=================================================================
   System Requirements
=================================================================

Server:
  - .NET 8 Runtime (ASP.NET Core)
  - SQL Server 2014+ (Express is fine)
  - Windows Server 2016+ or Linux
  - 1 GB RAM minimum, 2 GB recommended

Agent Console (optional):
  - Windows 10 version 1809 or later
  - Windows App SDK Runtime

Docker:
  - Fastest path: run ./docker-up.sh (Linux/macOS) or docker-up.cmd
    (Windows). It generates a .env with a unique database password and
    JWT signing key, then runs "docker compose up -d --build".
  - Or manually: cp .env.example .env, edit the secrets, then
    docker compose up -d. Compose creates the DChat database and applies
    the schema and seed data automatically on first startup.
  - Secrets are never baked into docker-compose.yml. DChat refuses to
    start in production with a shipped default JWT key, so each install
    must use its own values.
  - App_Data lives on the named volume zchat-appdata, so chat attachments,
    agent avatars, the knowledge base and the logs survive
    'docker compose up --force-recreate' and image updates. Back it up with
    'docker run --rm -v zchat-appdata:/d -v $PWD:/b alpine tar czf
    /b/appdata.tgz -C /d .'. Removing the volume (docker compose down -v)
    deletes all of it.
  - Set DCHAT_JWT_SECRET in .env for Docker rather than relying on the
    first-run generated key: with more than one replica each container would
    mint its own and reject tokens issued by the others.

Notes:
  - The Console folder contains build instructions for the WinUI 3
    desktop agent. Pre-built binaries are not included.
  - Back up regularly: your SQL Server database, your dchat.lic if you
    bought one, and the Web\App_Data folder (uploads and runtime data).
