{"id":43614638,"url":"https://github.com/pyrex41/cl_email_schedule","last_synced_at":"2026-02-04T12:23:49.526Z","repository":{"id":297527629,"uuid":"997078192","full_name":"pyrex41/cl_email_schedule","owner":"pyrex41","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-06T00:24:48.000Z","size":823,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-06-06T00:29:14.762Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pyrex41.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2025-06-05T23:26:48.000Z","updated_at":"2025-06-06T00:24:45.000Z","dependencies_parsed_at":"2025-06-06T00:39:23.541Z","dependency_job_id":null,"html_url":"https://github.com/pyrex41/cl_email_schedule","commit_stats":null,"previous_names":["pyrex41/cl_email_schedule"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pyrex41/cl_email_schedule","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrex41%2Fcl_email_schedule","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrex41%2Fcl_email_schedule/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrex41%2Fcl_email_schedule/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrex41%2Fcl_email_schedule/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyrex41","download_url":"https://codeload.github.com/pyrex41/cl_email_schedule/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyrex41%2Fcl_email_schedule/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29084188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-04T03:31:03.593Z","status":"ssl_error","status_checked_at":"2026-02-04T03:29:50.742Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2026-02-04T12:23:49.018Z","updated_at":"2026-02-04T12:23:49.507Z","avatar_url":"https://github.com/pyrex41.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Email Scheduler - Common Lisp Implementation\n\nA sophisticated email scheduling system implemented in Common Lisp, featuring a powerful DSL for expressing business rules, CLOS-based domain modeling, and advanced error handling through the Common Lisp condition system.\n\n## Features\n\n- **Powerful DSL**: Macros for expressing scheduling rules and state-based exclusions\n- **CLOS Domain Model**: Object-oriented design with multiple dispatch\n- **Condition System**: Sophisticated error handling with restarts\n- **Streaming Processing**: Memory-efficient processing of large contact databases\n- **Load Balancing**: Intelligent distribution of emails across days\n- **Interactive Development**: REPL-friendly design with debugging tools\n- **Campaign Management**: Flexible campaign system with template support\n- **State Compliance**: Automated compliance with state-specific regulations\n\n## Roswell Integration\n\nThis project is fully integrated with [Roswell](https://github.com/roswell/roswell), the Common Lisp environment manager. You can use the project in three ways:\n\n1. **Make commands** (recommended for development)\n2. **Roswell script** (great for automation and CI)\n3. **Direct Roswell commands** (for maximum control)\n\n### Available Make Commands\n\n```bash\nmake help       # Show all available commands\nmake install    # Install dependencies\nmake test       # Run all tests\nmake demo       # Interactive demo\nmake validate   # Validate installation\nmake schedule   # Run scheduler with test data\nmake benchmark  # Performance benchmarks\nmake repl       # Start interactive REPL\nmake clean      # Clean generated files\n```\n\n### Roswell Script\n\nThe `roswell/email-scheduler.ros` script provides a convenient CLI interface:\n\n```bash\n./roswell/email-scheduler.ros help                                    # Show help\n./roswell/email-scheduler.ros test                                    # Run tests\n./roswell/email-scheduler.ros demo                                    # Interactive demo\n./roswell/email-scheduler.ros schedule --contacts 1000               # Run scheduler\n./roswell/email-scheduler.ros repl                                   # Start REPL\n```\n\n## Quick Start\n\n### Prerequisites\n\n- [Roswell](https://github.com/roswell/roswell) (Common Lisp environment manager)\n- SQLite\n\nRoswell will automatically manage SBCL and Quicklisp for you.\n\n### Installation\n\n1. Clone the repository:\n```bash\ngit clone \u003crepository-url\u003e\ncd cl_email_schedule\n```\n\n2. Install dependencies and validate:\n```bash\nmake install     # Install SBCL and load system\nmake validate    # Validate installation\n```\n\n**Alternative manual installation:**\n```bash\nros install sbcl-bin           # Install SBCL via Roswell\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --eval \"(ql:register-local-projects)\" --eval \"(ql:quickload :email-scheduler)\"\n```\n\n### Basic Usage\n\n#### Interactive Demo\n```bash\nmake demo\n```\n\n**Or using Roswell directly:**\n```bash\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --load run-tests.lisp --eval \"(demo-scheduler)\"\n```\n\n**Or using the Roswell script:**\n```bash\n./roswell/email-scheduler.ros demo\n```\n\n#### Create Test Contact and Preview Schedules\n```bash\n# Start interactive REPL\nmake repl\n```\n\n**Or using Roswell directly:**\n```bash\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --eval \"(ql:register-local-projects)\" --eval \"(ql:quickload :email-scheduler)\" --eval \"(in-package :email-scheduler.repl)\"\n```\n\nThen in the REPL:\n```lisp\n;; Create a test contact\n(defparameter *contact* \n  (test-contact :email \"john@example.com\" \n                :state :ca \n                :birthday \"1980-03-15\"\n                :effective-date \"2020-01-01\"))\n\n;; Preview what emails would be scheduled\n(preview-schedules *contact*)\n\n;; Explain why a specific date might be excluded\n(explain-exclusion *contact* \"2024-03-15\")\n```\n\n#### Run the Scheduler\n```bash\n# Quick scheduler run with 100 contacts\nmake schedule\n\n# Or with custom parameters using the Roswell script\n./roswell/email-scheduler.ros schedule --contacts 1000 --db-path \"my-scheduler.db\"\n```\n\n**Or manually in REPL:**\n```lisp\n;; Set up test environment with 1000 contacts\n(email-scheduler:setup-test-environment :contact-count 1000)\n\n;; Run scheduler\n(email-scheduler:run-scheduler :db-path \"test-scheduler.db\")\n```\n\n## Architecture\n\n### Core Components\n\n1. **Domain Model** (`src/domain.lisp`)\n   - CLOS classes for contacts, email types, and schedules\n   - Multiple dispatch for polymorphic behavior\n\n2. **DSL** (`src/dsl.lisp`)\n   - Macros for defining state rules and campaigns\n   - Reader macros for date literals\n\n3. **Rules Engine** (`src/rules.lisp`)\n   - State-specific exclusion window logic\n   - Campaign configuration management\n\n4. **Scheduling Logic** (`src/scheduling.lisp`)\n   - Anniversary-based email calculation\n   - Campaign email processing\n   - Exclusion rule application\n\n5. **Database Layer** (`src/database.lisp`)\n   - SQLite integration with SXQL DSL\n   - Transaction management and audit trails\n\n6. **Load Balancer** (`src/load-balancer.lisp`)\n   - Email volume distribution\n   - Deterministic jitter for clustering prevention\n\n### State Rules DSL\n\nDefine state-specific exclusion rules using the DSL:\n\n```lisp\n;; California: 30 days before to 60 days after birthday\n(defstate :ca\n  (birthday-window :before 30 :after 60))\n\n;; Nevada: Special rule using month start\n(defstate :nv\n  (birthday-window :before 0 :after 60 :use-month-start t))\n\n;; Connecticut: Year-round exclusion\n(defstate :ct (year-round-exclusion))\n```\n\n### Campaign DSL\n\nDefine campaign types with flexible configuration:\n\n```lisp\n(defcampaign rate-increase\n  :respect-exclusions t\n  :enable-followups t\n  :days-before 14\n  :priority 1)\n```\n\n## Email Types\n\n### Anniversary-Based Emails\n- **Birthday**: Sent 14 days before contact's birthday\n- **Effective Date**: Sent 30 days before policy anniversary\n- **AEP**: Annual Enrollment Period emails (September 15th)\n- **Post Window**: Sent after exclusion windows end\n\n### Campaign-Based Emails\n- **Rate Increase**: Premium change notifications\n- **Seasonal Promotions**: Configurable marketing campaigns\n- **Initial Blast**: System introduction emails\n\n### Follow-up Emails\n- Intelligent follow-up based on user engagement\n- Multiple follow-up types based on behavior analysis\n\n## State Compliance\n\nThe system automatically handles state-specific regulations:\n\n### Birthday Window States\n- **CA**: 30 days before to 60 days after\n- **ID**: 0 days before to 63 days after\n- **NV**: 0 days before to 60 days after (month start)\n- **KY, OK**: 0 days before to 60 days after\n- **MD, VA**: 0 days before to 30 days after\n- **OR**: 0 days before to 31 days after\n\n### Effective Date Window States\n- **MO**: 30 days before to 33 days after\n\n### Year-Round Exclusion States\n- **CT, MA, NY, WA**: No marketing emails sent\n\n## Configuration\n\nConfiguration can be provided via JSON file:\n\n```json\n{\n  \"timezone\": \"America/Chicago\",\n  \"batch_size\": 10000,\n  \"birthday_days_before\": 14,\n  \"effective_date_days_before\": 30,\n  \"daily_cap_percentage\": 0.07,\n  \"load_balancing\": {\n    \"ed_smoothing_window_days\": 5,\n    \"catch_up_spread_days\": 7\n  }\n}\n```\n\n## Testing\n\n### Run All Tests\n```bash\nmake test\n```\n\n### Run Specific Test Suites\n```bash\n./roswell/email-scheduler.ros test     # All tests via script\n```\n\n**Or using Roswell directly:**\n```bash\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --load run-tests.lisp --eval \"(run-rules-tests)\"        # State rules and exclusion logic\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --load run-tests.lisp --eval \"(run-scheduling-tests)\"   # Email scheduling logic\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --load run-tests.lisp --eval \"(run-database-tests)\"     # Database operations\n```\n\n### Performance Testing\n```bash\nmake benchmark\n```\n\n**Or using Roswell directly:**\n```bash\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --load run-tests.lisp --eval \"(run-performance-tests)\"\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --load run-tests.lisp --eval \"(benchmark-scheduler)\"\n```\n\n## Interactive Development\n\nThe system provides extensive REPL support for interactive development:\n\n```bash\n# Start interactive session\nmake repl\n```\n\n**Or using the Roswell script:**\n```bash\n./roswell/email-scheduler.ros repl\n```\n\n**Or using Roswell directly:**\n```bash\nros run --eval \"(pushnew (truename \\\".\\\") ql:*local-project-directories*)\" --eval \"(ql:register-local-projects)\" --eval \"(ql:quickload :email-scheduler)\" --eval \"(email-scheduler.repl:start-repl)\"\n```\n\nThen in the REPL:\n```lisp\n;; Create test contacts\n(defparameter *ca-contact* (test-contact :state :ca))\n(defparameter *ny-contact* (test-contact :state :ny))\n\n;; Show state rules\n(show-rules :ca)\n(show-campaigns 'rate-increase)\n\n;; Trace scheduling decisions\n(trace-scheduling *ca-contact*)\n\n;; Run demo\n(demo)\n```\n\n## Database Schema\n\nThe system uses SQLite with the following key tables:\n\n- `contacts`: Contact information and demographics\n- `email_schedules`: Scheduled emails with status and metadata\n- `campaign_types`: Reusable campaign configurations\n- `campaign_instances`: Specific campaign executions\n- `contact_campaigns`: Campaign targeting relationships\n- `scheduler_checkpoints`: Audit trail and recovery points\n\n## Performance\n\nThe system is designed to handle large-scale processing:\n\n- **Streaming Processing**: Handles 3M+ contacts without memory exhaustion\n- **Parallel Processing**: Uses lparallel for batch operations\n- **Load Balancing**: Prevents email clustering and server overload\n- **Optimized Queries**: Efficient database operations with proper indexing\n\n### Benchmarks\n\nTypical performance on modern hardware:\n- 1,000 contacts: ~0.1 seconds\n- 10,000 contacts: ~1.0 seconds\n- 100,000 contacts: ~10 seconds\n\n## Error Handling\n\nThe system uses Common Lisp's condition system for robust error handling:\n\n```lisp\n;; Automatic error recovery\n(with-error-handling\n  (schedule-emails-streaming db-path run-id))\n\n;; Contact-level error handling with restarts\n(with-contact-processing (contact)\n  (calculate-schedules contact))\n```\n\nAvailable restarts:\n- `skip-contact`: Skip invalid contact and continue\n- `retry-batch`: Retry failed batch operation\n- `use-default`: Use default configuration values\n\n## Extensions\n\nThe system is designed for extensibility:\n\n### Adding New Email Types\n```lisp\n(defclass custom-email (email-type)\n  ((custom-field :initarg :custom-field :accessor custom-field)))\n\n(defmethod calculate-send-date ((email-type custom-email) contact today)\n  ;; Custom scheduling logic\n  )\n```\n\n### Adding New State Rules\n```lisp\n(defstate :new-state\n  (birthday-window :before 15 :after 45)\n  (effective-date-window :before 20 :after 20))\n```\n\n### Custom Smoothing Rules\n```lisp\n(define-smoothing-rule custom-smoothing\n    (\u003e (count-emails-by-type day-schedules 'custom-email) 10)\n  (redistribute-with-jitter schedules date 7))\n```\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## Support\n\n- Run `(validate-installation)` to check system health\n- Use `(demo-scheduler)` for interactive exploration\n- Check test suite with `(run-all-tests)`\n- Use REPL tools for debugging: `(trace-scheduling contact)`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrex41%2Fcl_email_schedule","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyrex41%2Fcl_email_schedule","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyrex41%2Fcl_email_schedule/lists"}