Database-Driven Mailing List Management
This mailing list system uses MySQL with Postfix's native MySQL support for real-time dynamic list management. Postfix queries the database directly for each email - no scripts or reloads needed.
Database Schema
Three-table design with many-to-many relationships:
Tables
lists - Mailing list definitions
list_id(primary key)list_name- Display namelist_email- Full email address (e.g.,community@lists.sasalliance.org)description- Optional descriptionactive- Boolean flag to enable/disable list
members - Member information
member_id(primary key)name- Display nameemail- Email addressactive- Boolean flag to enable/disable member
list_members - Subscription junction table
list_id+member_id(composite unique key)active- Boolean flag to enable/disable subscription- Foreign keys to
listsandmembers
How It Works
- Incoming email arrives for
community@lists.sasalliance.org - Postfix queries MySQL using the config in
mysql_virtual_alias_maps.cf - Database returns comma-separated list of active member emails
- Postfix expands the alias and delivers to all members
- Changes take effect immediately - no restart or reload needed!
Managing Lists and Members
Via MySQL Client
Connect to the database:
docker-compose exec mysql mysql -u maillist -p maillist
Common Operations
View all lists:
SELECT list_id, list_name, list_email, active FROM lists;
View all members:
SELECT member_id, name, email, active FROM members;
View subscriptions for a list:
SELECT m.name, m.email
FROM members m
JOIN list_members lm ON m.member_id = lm.member_id
JOIN lists l ON lm.list_id = l.list_id
WHERE l.list_email = 'community@lists.sasalliance.org'
AND lm.active = TRUE AND m.active = TRUE;
Add a new member:
INSERT INTO members (name, email)
VALUES ('John Doe', 'john.doe@example.com');
Subscribe member to list:
-- Method 1: Using subqueries (one step)
INSERT INTO list_members (list_id, member_id)
VALUES (
(SELECT list_id FROM lists WHERE list_email = 'community@lists.sasalliance.org'),
(SELECT member_id FROM members WHERE email = 'john.doe@example.com')
);
Unsubscribe member from list:
DELETE FROM list_members
WHERE list_id = (SELECT list_id FROM lists WHERE list_email = 'community@lists.sasalliance.org')
AND member_id = (SELECT member_id FROM members WHERE email = 'john.doe@example.com');
Create a new mailing list:
INSERT INTO lists (list_name, list_email, description)
VALUES ('Developers', 'dev@lists.sasalliance.org', 'Developer discussions');
Disable a list (keeps data, stops delivery):
UPDATE lists SET active = FALSE WHERE list_email = 'community@lists.sasalliance.org';
Re-enable a list:
UPDATE lists SET active = TRUE WHERE list_email = 'community@lists.sasalliance.org';
Verification
Test that Postfix can query the database:
docker-compose exec postfix postmap -q "community@lists.sasalliance.org" mysql:/etc/postfix/mysql_virtual_alias_maps.cf
This should return a comma-separated list of member email addresses.
Database Initialization
The database is automatically initialized from database/schema.sql when the MySQL container first starts. Sample data includes:
- 4 mailing lists (community, board, members, announcements)
- 2 sample members
- Sample subscriptions
Reset Database
To completely reset the database (deletes all data!):
docker-compose down -v # Remove volumes
docker-compose up -d # Reinitialize from schema.sql
Performance
Postfix caches MySQL query results, so the database isn't queried for every single email. The cache TTL is configurable in mysql_virtual_alias_maps.cf if needed.