{"id":22483986,"url":"https://github.com/wagesj45/gray-duck-mail","last_synced_at":"2025-08-05T01:40:01.330Z","repository":{"id":72306890,"uuid":"479912981","full_name":"wagesj45/gray-duck-mail","owner":"wagesj45","description":"Easy to use email discussion lists.","archived":false,"fork":false,"pushed_at":"2023-02-24T04:48:29.000Z","size":7127,"stargazers_count":22,"open_issues_count":5,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-02T17:46:36.554Z","etag":null,"topics":["community","community-management","docker","docker-image","email","email-sender","listserv","self-hosted"],"latest_commit_sha":null,"homepage":"https://grayduckmail.com/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wagesj45.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGE_LOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-10T04:31:54.000Z","updated_at":"2025-06-19T14:52:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"32e687a5-0f4e-48d6-9f16-d0a8ae903660","html_url":"https://github.com/wagesj45/gray-duck-mail","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wagesj45/gray-duck-mail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagesj45%2Fgray-duck-mail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagesj45%2Fgray-duck-mail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagesj45%2Fgray-duck-mail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagesj45%2Fgray-duck-mail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wagesj45","download_url":"https://codeload.github.com/wagesj45/gray-duck-mail/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wagesj45%2Fgray-duck-mail/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268820432,"owners_count":24312380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-04T02:00:09.867Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["community","community-management","docker","docker-image","email","email-sender","listserv","self-hosted"],"created_at":"2024-12-06T17:10:03.906Z","updated_at":"2025-08-05T01:40:01.303Z","avatar_url":"https://github.com/wagesj45.png","language":"C#","funding_links":[],"categories":["C# #"],"sub_categories":[],"readme":"# ![Gray Duck Logo](/assets/logo-32.png) Gray Duck Mail\n\nModern group email discussion lists.\n\n---\n\nGray Duck Mail is an alternative to email discussion group software such as [Mailman](https://docs.mailman3.org/en/latest/) and [Sympa](https://www.sympa.org/) designed to be **easy to set up** and **easy to manage**. Gray Duck Mail monitors a remote mail server for messages and processes them by relaying the message to all list members. Gray Duck Mail also automatically handles user subscriptions, unsubscription requests, and email bounces. Gray Duck Mail works with any external email host that allows for POP3/SMTP connections and message forwarding or aliasing. Gray Duck Mail relies totally on an external mail provider, allowing you to use an existing web host or email account.\n\nGray Duck Mail is written in C# and is powered by [ASP.NET Core 3.1 MVC](https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-3.1).\n\n## Table of Contents\n\n1. [Installation](#installation)\n   1. [Tags](#tags)\n   2. [Ports](#ports)\n   3. [Volumes](#volumes)\n   4. [Environment Variables](#environment-variables)\n      - [`RATE_LIMIT_PER_ROUND_COUNT`](#rate_limit_per_round_count)\n      - [`RATE_LIMIT_ROUND_WAIT_TIME`](#rate_limit_round_wait_time)\n      - [`FETCH_TIME`](#fetch_time)\n      - [`LOG_LEVEL`](#log_level)\n      - [`LANGUAGE`](#language)\n      - [`MIN_SEARCH_SCORE`](#min_search_score)\n      - [`WEB_ONLY`](#web_only)\n      - [`WEB_UNSUBSCRIBE`](#web_unsubscribe)\n      - [`WEB_USE_HTTPS`](#web_use_https)\n      - [`WEB_EXTERNAL_URL`](#web_external_url)\n      - [`WEB_SECRET`](#web_secret)\n      - [`ASPNETCORE_URLS`](#aspnetcore_urls)\n   5. [Security Considerations](#security-considerations)\n2. [Web Interface](#web-interface)\n   1. [Settings](#settings)\n      1. [Items Per Page](#items-per-page)\n      2. [Fuzzy Search](#fuzzy-search)\n   2. [Database Import](#database-import)\n   3. [Datbase Export](#datbase-export)\n3. [Contacts](#contacts)\n4. [Discussion Lists](#discussion-lists)\n   1. [Configuration](#configuration)\n      1. [Base Email Account](#base-email-account)\n      2. [Aliases](#aliases)\n         - [`request`](#request)\n         - [`subscribe`](#subscribe)\n         - [`unsubscribe`](#unsubscribe)\n         - [`bounce`](#bounce)\n         - [`owner`](#owner)\n\n## Installation\n\nGray Duck Mail is provided as a [docker image](https://hub.docker.com/r/wagesj45/gray-duck-mail).\n\n`docker pull wagesj45/gray-duck-mail`\n\n### Tags\n\nYou will find two tags utilized at [Docker Hub](https://hub.docker.com/r/wagesj45/gray-duck-mail): `testing` and `latest`. The `testing` tag is just that; an unstable image used for testing and debugging purposes. Unless you're actively working on development of Gray Duck Mail, only use the `latest` tag when pulling images and creating containers.\n\n### Ports\n\nThe docker image requires an HTTP port exposed and mapped to port **80** on the container. An optional port can be exposed and mapped to port **5000** that will accept external unsubscribe requests.\n\n### Volumes\n\nA single volume mounted to `/database` is required. This volume will store the local [SQLite3](https://sqlite.org/index.html) database file is stored. This volume provides non-volitile storage so that data is not lost between docker image upgrades.\n\n### Environment Variables\n\nThe docker image exposes the following environment variables that control the system.\n\n#### `RATE_LIMIT_PER_ROUND_COUNT`\n\nThe number of emails that can be sent before a defined time-out period. This is useful for email providers that place restrictions on the number of emails that can be sent. The default value is a [System.Int32](https://learn.microsoft.com/en-us/dotnet/api/system.int32?view=netcore-3.1) set to `00:05:00`.\n\n#### `RATE_LIMIT_ROUND_WAIT_TIME`\n\nThe time between sending groups of emails from the queue. After a given round of emails sent, the system will wait this amount of time before continuing from the email the queue. The default value is a [System.TimeSpan](https://docs.microsoft.com/en-us/dotnet/api/system.timespan?view=netcore-3.1) set to `00:05:00`.\n\n#### `FETCH_TIME`\n\nThe time between fetching email from the remote server. The default value is a [System.TimeSpan](https://docs.microsoft.com/en-us/dotnet/api/system.timespan?view=netcore-3.1) set to `00:05:00`.\n\n#### `LANGUAGE`\n\nSystem localization is supported. Language files are stored as `*.resx` files. Although the initial translations were automated, pull requests are accepted for human translations.\n\nThe following values are supported:\n\n- `en-US` - English\n- `ja-JP` - Japanese\n- `es-ES` - Spanish\n- `de-DE` - German\n\n#### `LOG_LEVEL`\n\nThe verbosity level with which to log application events. The default value is a [System.String](https://docs.microsoft.com/en-us/dotnet/api/system.string?view=netcore-3.1) set to `info`.\n\nThe folowing values are supoorted:\n\n- `trace` | `all`\n- `debug` | `verbose`\n- `info` | `information`\n- `warn` | `warning`\n- `err` | `error`\n- `fatal`\n\n#### `MIN_SEARCH_SCORE`\n\nThe minimum viable search score when performing [fuzzy searches](#Fuzzy-Search). The default value is a [System.Single (float)](https://docs.microsoft.com/en-us/dotnet/api/system.single?view=netcore-3.1) set to `0.2`. When using [fuzzy searches](#Fuzzy-Search) fractional scores between `1` and `0` are generated. Results approach zero as search results stray from an exact match.\n\n#### `WEB_ONLY`\n\nIf set, only the web interface will be initialized. [Background worker threads](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1\u0026tabs=visual-studio) will not be initialized. This is useful for running the web administration interface without the rick of background service workers modifying the remote mail server. The default value is a [System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/system.boolean?view=netcore-3.1) set to `0`.\n\nThe following values are supported:\n\n- `True` | `1`\n- `False` | `0`\n\n#### `WEB_UNSUBSCRIBE`\n\nIf set, an unsubscribe link will be generated and used in system and relayed messages. If unset, a link to the unsubscribe alias will be generated. The default value is a [System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/system.boolean?view=netcore-3.1) set to `1`.\n\nThe following values are supported:\n\n- `True` | `1`\n- `False` | `0`\n\n#### `WEB_USE_HTTPS`\n\nIf set, the URL generated for a externally accessible unsubscribe link will be generated using `HTTPS`. Otherwise, it will generated using `HTTP`. The default value is a [System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/system.boolean?view=netcore-3.1) set to `1`.\n\nThe following values are supported:\n\n- `True` | `1`\n- `False` | `0`\n\n#### `WEB_EXTERNAL_URL`\n\nThe host name used when generating an externally accessible unsubscribe link. The default value is a [System.String](https://docs.microsoft.com/en-us/dotnet/api/system.string?view=netcore-3.1) set to `example.com`.\n\n#### `WEB_SECRET`\n\nA unique string used when creating hash codes for the unsubscribe link appended to email messages. This string can be anything you choose. The hash is created by performing a SHA256 hash on the `ContactID`, `DiscussionListID`, and `WEB_SECRET`. This prevents outside actors from brute forcing the unsubscribe link maliciously. There is no default value for the [System.String](https://docs.microsoft.com/en-us/dotnet/api/system.string?view=netcore-3.1), however if it is not set, the system will set it to the string representation of a random [GUID](https://learn.microsoft.com/en-us/dotnet/api/system.guid?view=netcore-3.1).\n\n#### `ASPNETCORE_URLS`\n\nThe URLs monitored by the ASP.Net server runtime. The default value is a [System.String](https://docs.microsoft.com/en-us/dotnet/api/system.string?view=netcore-3.1) set to `http://+:5000;http://+:80`. You can learn more [here](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-6.0), but unless you're an advanced user modifying the Gray Duck Mail Docker image, you should leave this value at its default and ensure poth ports **80** and **5000** are configured. When configuring your Docker container, only port **80** is required for normal functioning.\n\n### Security Considerations\n\nGray Duck Mail segments its web interface between two ports, one internal and one external. The Docker image utilizes port **80** as its designated *internal* point of ingress, and port **5000** as its designated *external* point of ingress. This means that care should be taken to expose only port **5000** of the docker container to the public internet.\n\nUtilizing the external port allows utilization of a \"one click\" unsubscribe link. The unsubscribe link takes the form of `http[s]://hostname/Unsubscribe/{contactID}/{discussionListID}`. This might look like `https://example.com/Unsubscribe/529/187`, assuming a user with the internal identifier `529` is unsubscribing from discussion list with internal identifier `187`, utilizing `HTTPS` through the hostname `example.com`.\n\nThe unsubscribe link is the only externally accessible route in Gray Duck Mail. Attempting to load any other route from port **5000** will result in a [`403 Forbidden`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403) access error.\n\nGray Duck Mail should ideally be placed behind a reverse proxy forwarding web traffic **only** to port **5000** (or the port being forwarded to the external ingress port in the Docker container). It is highly recommended to utilize a [reverse proxy such as nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) serving [SSL content](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/). While not recommended, it might be possible to pair the secure ingress port with [HTTP basic authentication](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/).\n\nGray Duck Mail stores email login credentials in a local database. These credentials **are not** encrypted. Additionally, the web administration interface has no concept of users or content segmentation beyond its *internal* and *external* designations. This means that anyone with access to the **internal** web interface through port **80** will have access to the login credentials of each email discussion list, as well as the list users' contact information (name and email address) and message archive list (sender and message contents).\n\nThe database files used by Gray Duck Mail are not encrypted and store all data in plain text. When making backups of the `/database` docker volume or exporting copies of the database, care should be taken to ensure that file access is restricted.\n\n## Web Interface\n\nGray Duck Mail provides a web administration interface for basic system interaction. The web interface allows an administrator to create and remove distribution lists, create and remove list contacts, browse message archives, and access basic adminstration actions like [importing](#database-import) and [exporting copies of the local database](#database-export), care should be taken to make sure those files are not accessable to the public.\n\n### Settings\n\nThe web adminstration settings control the presentation of data *in* the web interface, so are stored as cookies in the user's browser. These settings have no influence on the system's discussion list email processing functionality.\n\n#### Items Per Page\n\nThis setting  controls the number of top level items displayed on a page, such as contacts or archived messages.\n\n#### Fuzzy Search\n\nSearching through contacts and message archives will by default perform exact searches. Results will contain the search term exactly as presented. If enabled, this option will allow the search engine to engage [fuzzy searching](https://en.wikipedia.org/wiki/Fuzzy_logic), using the [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) to find results that closely match a given search term. This can result in better search results, but **is computationally heavy** as all items being searched must be loaded into memory from the database. When searching large message archives or large contact lists, this could result in time-out errors from the web server.\n\n### Database Import\n\nThis action allows you to import an existing SQLite3 database file. When importing a database, the web server must restart. You can simply reload the web interface to see the new changes.\n\n### Database Export\n\nThis action allows you to export a copy of the SQLite3 database file. This file can be imported into another instance of Gray Duck Mail or viewed in an external application such as [DB Browser for SQLite](https://sqlitebrowser.org/)\n\n## Contacts\n\nContacts represent individual users that can send and recieve messages to a discussion list. New contacts can be added through the [web interface](#web-interface) or are added automatically when a previously unknown email address sends a message to `request` email alias for a discussion list.\n\n## Discussion Lists\n\nDiscussion lists represent a group of contacts that can relay messages to other users via a single designated email address. Limited automated moderation can be performed by sending messages to specific [email aliases](#aliases).\n\n### Configuration\n\nGray Duck Mail requires an external email account and a few [defined aliases](#aliases) in order to function. A discussion list functions by monitoring a given POP3 email account and processing each email by either relaying it to the discussion list members, or by analyzing a message's MIME headers and performing a moderation task.\n\n#### Base Email Account\n\nThe base email account is the actual email account provided to you by an external email provider, such as your [web host](https://www.inmotionhosting.com/).\n\n#### Aliases\n\nIn order to process operational emails, Gray Duck Mail expects certain email aliases to be configured. This configuration may be refered to by your email provider as \"forwards\". These aliases should be structured so that the command name precedes the base email address with a hyphen separating them.\n\nFor example: `subscribe-base.email.address@example.com`.\n\nThe following email aliases are expected to exist for automatic moderation:\n\n- `request-base.email.address@example.com`\n- `subscribe-base.email.address@example.com`\n- `unsubscribe-base.email.address@example.com`\n- `bounce-base.email.address@example.com`\n- `owner-base.email.address@example.com`\n\n##### `request`\n\nThe request alias is used to process requests to join a discussion list by new users.\n\n##### `subscribe`\n\nThe subscription alias is used by known users who have been invited to participate in a discussion list to confirm their willingness to participate.\n\n##### `unsubscribe`\n\nThe unsubscription alias is used by known users who have been invited to participate in a discussion list to revoke their consent to recieve emails associated with a given discussion list.\n\n##### `bounce`\n\nThe bounce alias is inserted into the [`return-path`](https://en.wikipedia.org/wiki/Variable_envelope_return_path) MIME header for all system delievered messages. Some email servers rely on this `return-path` value to notify senders of non-deliverable messages or invalid mail boxes.\n\n\n##### `owner`\n\nThe owner alias should forward to a non-discussion-list address, such as the administrators personal email account. The system sends alert messages to this address when external moderation is necessary, such as when a previously unknown user requests access to a discussion list.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwagesj45%2Fgray-duck-mail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwagesj45%2Fgray-duck-mail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwagesj45%2Fgray-duck-mail/lists"}