{"id":46453413,"url":"https://github.com/cansinacarer/maillistshield-com","last_synced_at":"2026-03-06T01:01:44.835Z","repository":{"id":290476214,"uuid":"875862758","full_name":"cansinacarer/maillistshield-com","owner":"cansinacarer","description":"Mail List Shield - SaaS","archived":false,"fork":false,"pushed_at":"2025-12-03T23:22:54.000Z","size":11211,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-07T04:16:37.492Z","etag":null,"topics":["continuous-deployment","continuous-integration","dev-containers","docker","event-driven-microservices","flask","full-stack-web-development","github-actions","loki","object-relational-mapping","rabbitmq","sphinx","sphinx-autoapi","stripe"],"latest_commit_sha":null,"homepage":"https://maillistshield.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cansinacarer.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-10-21T01:53:02.000Z","updated_at":"2025-12-03T23:22:57.000Z","dependencies_parsed_at":"2025-08-25T03:14:13.623Z","dependency_job_id":"80cf25c8-5c5a-4818-aa13-bb85a9423987","html_url":"https://github.com/cansinacarer/maillistshield-com","commit_stats":null,"previous_names":["cansinacarer/maillistshield-com"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/cansinacarer/maillistshield-com","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cansinacarer%2Fmaillistshield-com","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cansinacarer%2Fmaillistshield-com/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cansinacarer%2Fmaillistshield-com/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cansinacarer%2Fmaillistshield-com/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cansinacarer","download_url":"https://codeload.github.com/cansinacarer/maillistshield-com/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cansinacarer%2Fmaillistshield-com/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30156845,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T22:39:40.138Z","status":"ssl_error","status_checked_at":"2026-03-05T22:39:24.771Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":["continuous-deployment","continuous-integration","dev-containers","docker","event-driven-microservices","flask","full-stack-web-development","github-actions","loki","object-relational-mapping","rabbitmq","sphinx","sphinx-autoapi","stripe"],"created_at":"2026-03-06T01:01:44.115Z","updated_at":"2026-03-06T01:01:44.765Z","avatar_url":"https://github.com/cansinacarer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"MailListShield Logo\" height=\"50\" src=\"app/static/media/mail-list-shield-logo.png#gh-light-mode-only\"\u003e\n    \u003cimg alt=\"MailListShield Logo\" height=\"50\" src=\"app/static/media/mail-list-shield-logo-dark-mode.png#gh-dark-mode-only\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    An email validation SaaS built with microservices architecture\n\u003c/p\u003e\n\n\u003c!-- Links --\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://maillistshield.com\"\u003e\u003cimg alt=\"Live Demo\" src=\"https://img.shields.io/badge/Live%20Demo-blueviolet?logo=Google%20Chrome\u0026logoColor=white\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://documenter.getpostman.com/view/39218943/2sB3QDxDUr\"\u003e\u003cimg alt=\"API Docs\" src=\"https://img.shields.io/badge/API%20Docs-blue?\u0026logo=read-the-docs\u0026logoColor=white\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://cansinacarer.github.io/My-Base-SaaS-Flask/\"\u003e\u003cimg alt=\"Developer Docs\" src=\"https://img.shields.io/badge/Developer%20Docs-blue?\u0026logo=read-the-docs\u0026logoColor=white\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://deepwiki.com/cansinacarer/maillistshield-com\"\u003e\u003cimg alt=\"DeepWiki\" src=\"https://deepwiki.com/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.producthunt.com/products/mail-list-shield?embed=true\u0026utm_source=badge-featured\u0026utm_medium=badge\u0026utm_source=badge-mail\u0026#0045;list\u0026#0045;shield\" target=\"_blank\"\u003e\n        \u003cpicture\u003e\n            \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1045775\u0026theme=dark\u0026t=1764802768331\"\u003e\n            \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1045775\u0026theme=light\u0026t=1764802529738\"\u003e\n            \u003cimg alt=\"Mail\u0026#0032;List\u0026#0032;Shield - Remove\u0026#0032;spam\u0026#0032;traps\u0026#0032;and\u0026#0032;invalid\u0026#0032;addresses\u0026#0032;from\u0026#0032;your\u0026#0032;email\u0026#0032;list | Product Hunt\" src=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1045775\u0026theme=light\u0026t=1764802529738\" height=\"20\" /\u003e\n        \u003c/picture\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003c!-- Status --\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://status.maillistshield.com/status/maillistshield\"\u003e\u003cimg alt=\"Uptime\" src=\"https://status.maillistshield.com/api/badge/5/uptime\"\u003e\u003c/a\u003e\n    \u003cimg alt=\"Last Commit\" src=\"https://img.shields.io/github/last-commit/cansinacarer/maillistshield-com?color=blue\"\u003e\n    \u003cimg alt=\"Test Coverage\" src=\"tests/coverage/coverage-badge.svg\"\u003e\n    \u003ca href=\"https://github.com/psf/black\"\u003e\u003cimg alt=\"Code Style\" src=\"https://img.shields.io/badge/code%20style-black-000000\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003c!-- CI/CD --\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/deploy.yml\"\u003e\u003cimg alt=\"Build \u0026 Deploy\" src=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/deploy.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/pre-commit.yml\"\u003e\u003cimg alt=\"Pre-Commit Hooks\" src=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/pre-commit.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/test.yml\"\u003e\u003cimg alt=\"Run Tests\" src=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/test.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/semantic-release.yml\"\u003e\u003cimg alt=\"Semantic Release\" src=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/semantic-release.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/docs.yml\"\u003e\u003cimg alt=\"Build \u0026 Deploy Sphinx Docs\" src=\"https://github.com/cansinacarer/maillistshield-com/actions/workflows/docs.yml/badge.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Architecture\n\n```mermaid\nsequenceDiagram\n  participant flask as 🌶️ 1. Flask Application\n  participant db as 🐘 Postgres Database\n  participant s3 as ☁️ S3 Bucket\n  participant fis as 📁 2. File Intake Service\n  participant f2vqp as 📤 3. File to Validation Queue Publisher\n  participant rabbit as 🐰 RabbitMQ\n  participant vo as ⚙️ 5. Validation Orchestrator\n  participant evw as ✉️ 4. Email Validation Worker\n  participant rfg as 📊 6. Results File Generator\n\n  flask -\u003e\u003e s3: Uploads a batch validation job to validation/uploaded/\n  flask -\u003e\u003e db: Records the job as pending_start\n  s3 -\u003e\u003e fis: Clean up the file, calculate cost\n  fis -\u003e\u003e db: Deduct credits from user, update status to file_accepted\n  fis -\u003e\u003e s3: Upload cleaned file to validation/in-progress/\n  s3 -\u003e\u003e f2vqp: Parse the cleaned file\n  f2vqp -\u003e\u003e rabbit: Create a queue per file, publish each row as a message\n  f2vqp -\u003e\u003e db: Update status to file_queued\n  rabbit -\u003e\u003e vo: Consume N message per queue with Round-Robin\n  vo -\u003e\u003e evw: API call to send each message, retrieve result\n  vo -\u003e\u003e db: Update progress\n  db -\u003e\u003e flask: Update progress in the UI\n  vo -\u003e\u003e rabbit: Enqueue the results in each files' queue\n  rabbit -\u003e\u003e rfg: Drain results queue of the file when expected message count is reached, build result file\n  rfg -\u003e\u003e s3: Upload the result file to /validation/completed\n  rfg -\u003e\u003e db: Set status to file_validation_in_progress or file_completed, save the name of the results file\n  db -\u003e\u003e flask: Generate download link for results file\n```\n\nThis application consists of 6 event driven services:\n\n1. [Flask SaaS](https://github.com/cansinacarer/maillistshield-com) (this repository)\n2. [File Intake Service](https://github.com/cansinacarer/maillistshield-file-intake-service)\n3. [File to Validation Queue Publisher](https://github.com/cansinacarer/maillistshield-file-to-validation-queue-publisher)\n4. [Email Validation Worker](https://github.com/cansinacarer/maillistshield-validation-worker)\n5. [Validation Orchestrator](https://github.com/cansinacarer/maillistshield-validation-orchestrator)\n6. [Results File Generator](https://github.com/cansinacarer/maillistshield-results-file-generator)\n\n[See a more detailed architecture diagram →](https://app.diagrams.net/#Uhttps://raw.githubusercontent.com/cansinacarer/maillistshield-com/main/docs/drawio/mls-service-architecture.drawio)\n\n## Tech Stack\n\n| Category | Technologies |\n|----------|-------------|\n| **Backend** | Python, Flask |\n| **Database** | PostgreSQL, SQLAlchemy ORM |\n| **Message Queue** | RabbitMQ |\n| **Infrastructure** | Docker, AWS S3, CapRover (PaaS deployment) |\n| **Security** | OAuth 2.0, TOTP 2FA, reCAPTCHA, CSRF/XSS protection |\n| **Observability** | Loki, Grafana, Uptime Kuma |\n| **CI/CD** | GitHub Actions, Semantic Release |\n| **Payments** | Stripe (subscriptions + one-time purchases) |\n\n## Database Model\n\n\u003cdetails\u003e\n\u003csummary\u003eSee the ER diagram\u003c/summary\u003e\n\n```mermaid\n    erDiagram\n        \n        Users {\n            int id PK\n            string email\n            string password\n            string role\n            string stripe_customer_id\n            int tier_id FK\n            bigint credits\n            datetime cancel_at\n            string firstName\n            string lastName\n            int newsletter\n            datetime member_since\n            datetime last_login\n            string email_confirmation_code\n            datetime last_confirmation_codes_sent\n            int number_of_email_confirmation_codes_sent\n            int email_confirmed\n            string google_avatar_url\n            boolean avatar_uploaded\n            string totp_secret\n            int totp_enabled\n        }\n        \n        Tiers {\n            int id PK\n            string name\n            string label\n            string stripe_price_id\n        }\n\n        APIKeys {\n            int id PK\n            int user_id FK\n            string key_hash\n            string label\n            datetime created_at\n            datetime expires_at\n            datetime last_used\n            boolean is_active\n        }\n\n        BatchJobs {\n            int id PK\n            string uid\n            int user_id FK\n            string status\n            string original_file_name\n            string uploaded_file\n            string accepted_file\n            string results_file\n            int row_count\n            bigint last_pick_row\n            datetime last_pick_time\n            string source\n            int header_row\n            string email_column\n            datetime uploaded\n            datetime started\n            datetime finished\n            string result\n        }\n\n        Users }o--|| Tiers : \"has\"\n        Users ||--o{ APIKeys : \"owns\"\n        Users ||--o{ BatchJobs : \"creates\"\n```\n\n\u003c/details\u003e\n\n## Features of the Flask Application\n\n### 🧑‍💻 Developer Experience\n\n- Dev containers:\n\n  - **Flask** container with pre-configured with:\n    - VSCode launch.json for debugging the Flask app,\n    - Prettier for HTML, CSS, and JS formatting,\n    - Pre-commit hooks for code quality checks,\n    - Markdownlint for Markdown formatting,\n    - Black for Python code formatting,\n    - Commitlint for commit message linting.\n\n  - **Postgres** as a development database,\n\n  - **pgAdmin** pre-connected to the development,\n\n  - **docs** serving the built html files of the Sphinx documentation..\n\n- CI/CD pipelines with GitHub Actions to:\n  - Run pre-commit hooks,\n  - Run tests,\n  - Automate semantic release for versioning and changelog generation,\n  - Build and deploy the documentation,\n  - Build and deploy the app to production.\n\n### ☁️ Deployment\n\n- 🐳 Dockerized Flask for stateless continuous deployment for scalability,\n- 🗄️ Database model abstracted with ORM,\n- 📦 S3 object storage with pre-signed URLs.\n\n### 💳 Stripe Integrations\n\n- Subscriptions,\n  - Different subscription tiers,\n  - Billing page with Invoices,\n  - Integration mechanism:\n    - To begin a subscription, we send the user to Stripe with a checkout session,\n    - Then listen to Stripe webhook events to process the results,\n    - We set the Products in Stripe, then insert their prices into the Tiers table.\n\n- One-off credit purchases for pre-paid metered usage.\n\n### 🔒 Authentication\n\n- Sign up flow,\n  - Sign up with Google option,\n  - Email validation requirement,\n\n- Two factor authentication (TOTP only),\n- Forgot password flow,\n- reCAPTCHA v2 for sign up and login forms,\n- Account details page where the user can:\n  - Upload a profile picture (stored in S3),\n  - Change profile details like first \u0026 last name.\n\n### 📧 Transactional Emails with SMTP\n\n- About Stripe subscription changes:\n  - Confirmation,\n  - Cancellation,\n  - Expiration.\n\n- Email verification on registration,\n- Forgot password.\n\n### 🚨 Security\n\n- Cross-Site Request Forgery (CSRF) protection in all forms,\n- Rate limiting: App-wide and form specific limits,\n- Cross-Site Scripting (XSS) protection,\n- Cross-Origin Resource Sharing (CORS) protection.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcansinacarer%2Fmaillistshield-com","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcansinacarer%2Fmaillistshield-com","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcansinacarer%2Fmaillistshield-com/lists"}