├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── README.md ├── screenshots │ ├── 1-homepage.png │ ├── 2-summary-table.png │ ├── 3-email-page.png │ ├── 4-email-reviews.png │ └── 5-security-tips.png └── workflows │ ├── credits.yml │ ├── deploy-gh-pages.yml │ └── mirror.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── email-provider-data.yml └── web ├── astro.config.mjs ├── package.json ├── public ├── banner.png ├── favicon.png ├── favicon.svg └── fonts │ ├── Roboto-Bold.woff2 │ ├── Roboto-Mono-regular.woff2 │ ├── RobotoMono-Bold.ttf │ ├── RobotoMono-Italic-VariableFont_wght.ttf │ ├── RobotoMono-Italic.ttf │ ├── RobotoMono-Regular.ttf │ └── RobotoMono-VariableFont_wght.ttf ├── src ├── components │ ├── EmailComparisonTable.ts │ ├── Footer.astro │ ├── Hero.astro │ ├── Icon.ts │ ├── Intro.astro │ ├── MetaTags.astro │ ├── Nav.astro │ ├── ProviderLinks.astro │ ├── Reviews.ts │ └── ServiceInfo.astro ├── env.d.ts ├── layouts │ └── Layout.astro ├── pages │ ├── about.astro │ ├── email-security-tips.astro │ ├── email │ │ ├── [provider].astro │ │ └── [provider] │ │ │ └── summary.astro │ └── index.astro ├── styles │ ├── colors.scss │ ├── globals.scss │ └── typography.scss ├── types │ ├── MailServices.ts │ └── js-yaml.d.ts └── utils │ ├── best-practices-data.ts │ ├── config.ts │ └── transform-data.ts ├── tsconfig.json └── yarn.lock /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | alicia@omg.lol. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [lissy93] 2 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 |

Email Comparison

2 |

3 |
4 | An objective comparison of private and/or secure email services 5 |
6 | 🌐 email-comparison.as93.net
7 |

8 | 9 | --- 10 | 11 | ## About 12 | 13 | The aim of Email-Comparison is to provide an objective comparison of so-called private and/or secure email services, so that you can make an informed decision about which provider to use. 14 | 15 | This is a community-maintained resource, so if you spot something that should be updated/added/removed, please submit a pull request or raise an issue. Or share your thoughts and feedback for an email provider, by writing a short review. 16 | 17 | - 📧 All data is stored in [`email-provider-data.yml`](https://github.com/Lissy93/email-comparison/blob/master/email-provider-data.yml) (in YAML format). 18 | - ⭐ All mail service reviews are submitted [here, via GitHub Discussions](https://github.com/Lissy93/email-comparison/discussions/categories/reviews). 19 | - 🌐 Website published to [email-comparison.as93.net](https://email-comparison.as93.net/) is built with Lit on Astro. 20 | 21 | --- 22 | 23 | ## Web Application 24 | 25 | The info is most easily consumed via the website, which can be accessed at: 26 | **[email-comparison.as93.net](https://email-comparison.as93.net)** 27 | 28 | ### Screenshots 29 | 30 |

31 | 32 | 33 | 34 |

35 | 36 | ### Contributing 37 | Contributions of any type are welcome! 38 | 39 | To submit changes, fork the repository, edit the file, commit changes, then submit a pull request back to this repo. 40 | If you're new to open source, I've put together some guides in [Git-In](https://github.com/Lissy93/git-into-open-source/), but feel free to reach out if you need any support. 41 | Once merged, changes will be automatically reflected on the website. 42 | 43 | Be sure to follow our [Code of Conduct](https://github.com/Lissy93/email-comparison/blob/main/.github/CODE_OF_CONDUCT.md). 44 | 45 | ### Running Locally 46 | 47 | Prerequisites: [Git](https://git-scm.com/), [Node.js](https://nodejs.org/) and either [NPM](https://npmjs.org/) or [Yarn](https://yarnpkg.com/) 48 | 49 | 1. Get the code: `git clone https://github.com/Lissy93/email-comparison.git` 50 | 2. Jump into the working directory: `cd email-comparison/web` 51 | 3. Install dependencies: `yarn install` 52 | 4. Start the development server: `yarn dev` 53 | 5. Then pop open [`localhost:4321`](http://localhost:4321) and you're off to the races! 54 | 55 | ### Deploying 56 | 57 | When you're ready, you can deploy the app by running `yarn build` and upload the contents of `./dist` to any web server/ static hosting provider or CDN - or simply run with `yarn start` 58 | 59 | The public app is currently deployed to GitHub Pages at [lissy93.github.io/email-comparison](https://lissy93.github.io/email-comparison/), and Vercel at [email-comparison.as93.net](https://email-comparison.as93.net/) 60 | 61 | --- 62 | 63 | ## Mirror 64 | 65 | There's a (non-Microsoft) mirror of this repository hosted on CodeBerg, at [codeberg.org/alicia/email-comparison](https://codeberg.org/alicia/email-comparison) 66 | 67 | --- 68 | 69 | ## Credits 70 | 71 | ### Contributors 72 | 73 | 74 | 75 | 76 | 83 | 90 | 97 | 104 | 111 | 118 | 119 | 126 | 133 | 140 | 147 |
77 | 78 | Lissy93 79 |
80 | Alicia Sykes 81 |
82 |
84 | 85 | liss-bot 86 |
87 | Alicia Bot 88 |
89 |
91 | 92 | strboul 93 |
94 | Metin Yazici 95 |
96 |
98 | 99 | isolume 100 |
101 | Nerd 102 |
103 |
105 | 106 | amilich 107 |
108 | Andrew Milich 109 |
110 |
112 | 113 | titanism 114 |
115 | Titanism 116 |
117 |
120 | 121 | lamtrinhdev 122 |
123 | LamTrinh.Dev 124 |
125 |
127 | 128 | Mailfence-team 129 |
130 | Mailfence Team 131 |
132 |
134 | 135 | TheFrenchGhosty 136 |
137 | TheFrenchGhosty 138 |
139 |
141 | 142 | momobobe 143 |
144 | Momobobe 145 |
146 |
148 | 149 | 150 | 151 | ### Sponsors 152 | 153 | 154 | 155 | 156 | 163 | 170 | 177 | 184 | 191 | 198 | 199 | 206 | 213 | 220 | 227 | 234 | 241 | 242 | 249 | 256 | 263 | 270 | 277 | 284 |
157 | 158 | vincentkoc 159 |
160 | Vincent Koc 161 |
162 |
164 | 165 | BrianCurliss 166 |
167 | Brian Curliss 168 |
169 |
171 | 172 | AnandChowdhary 173 |
174 | Anand Chowdhary 175 |
176 |
178 | 179 | bile0026 180 |
181 | Zach Biles 182 |
183 |
185 | 186 | UlisesGascon 187 |
188 | Ulises Gascón 189 |
190 |
192 | 193 | digitalarche 194 |
195 | Digital Archeology 196 |
197 |
200 | 201 | InDieTasten 202 |
203 | InDieTasten 204 |
205 |
207 | 208 | araguaci 209 |
210 | Araguaci 211 |
212 |
214 | 215 | bmcgonag 216 |
217 | Brian McGonagill 218 |
219 |
221 | 222 | vlad-tim 223 |
224 | Vlad 225 |
226 |
228 | 229 | helixzz 230 |
231 | HeliXZz 232 |
233 |
235 | 236 | mryesiller 237 |
238 | Göksel Yeşiller 239 |
240 |
243 | 244 | getumbrel 245 |
246 | Umbrel 247 |
248 |
250 | 251 | OlliVHH 252 |
253 | HamburgerJung 254 |
255 |
257 | 258 | frankdez93 259 |
260 | Frankdez93 261 |
262 |
264 | 265 | terminaltrove 266 |
267 | Terminal Trove 268 |
269 |
271 | 272 | st617 273 |
274 | St617 275 |
276 |
278 | 279 | hudsonrock-partnerships 280 |
281 | Hudsonrock-partnerships 282 |
283 |
285 | 286 | 287 | --- 288 | 289 | ## License 290 | 291 | > _**[Lissy93/Email-Comparison](https://github.com/Lissy93/email-comparison)** is licensed under [MIT](https://github.com/Lissy93/email-comparison/blob/HEAD/LICENSE) © [Alicia Sykes](https://aliciasykes.com) 2024._
292 | > For information, see TLDR Legal > MIT 293 | 294 |
295 | Expand License 296 | 297 | ``` 298 | The MIT License (MIT) 299 | Copyright (c) Alicia Sykes 300 | 301 | Permission is hereby granted, free of charge, to any person obtaining a copy 302 | of this software and associated documentation files (the "Software"), to deal 303 | in the Software without restriction, including without limitation the rights 304 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell 305 | copies of the Software, and to permit persons to whom the Software is furnished 306 | to do so, subject to the following conditions: 307 | 308 | The above copyright notice and this permission notice shall be included install 309 | copies or substantial portions of the Software. 310 | 311 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 312 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANT ABILITY, FITNESS FOR A 313 | PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 314 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 315 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 316 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 317 | ``` 318 | 319 |
320 | 321 | 322 | 323 |

324 | © Alicia Sykes 2024
325 | Licensed under MIT
326 |
327 | Thanks for visiting :) 328 |

329 | 330 | 331 | 343 | 344 | -------------------------------------------------------------------------------- /.github/screenshots/1-homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/.github/screenshots/1-homepage.png -------------------------------------------------------------------------------- /.github/screenshots/2-summary-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/.github/screenshots/2-summary-table.png -------------------------------------------------------------------------------- /.github/screenshots/3-email-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/.github/screenshots/3-email-page.png -------------------------------------------------------------------------------- /.github/screenshots/4-email-reviews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/.github/screenshots/4-email-reviews.png -------------------------------------------------------------------------------- /.github/screenshots/5-security-tips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/.github/screenshots/5-security-tips.png -------------------------------------------------------------------------------- /.github/workflows/credits.yml: -------------------------------------------------------------------------------- 1 | # Inserts list of community members into ./README.md 2 | name: 💓 Inserts Contributors & Sponsors 3 | on: 4 | workflow_dispatch: # Manual dispatch 5 | schedule: 6 | - cron: '45 1 * * 0' # At 01:45 on Sunday. 7 | 8 | jobs: 9 | # Job #1 - Fetches sponsors and inserts table into readme 10 | insert-sponsors: 11 | runs-on: ubuntu-latest 12 | name: Inserts Sponsors 💓 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Updates readme with sponsors 17 | uses: JamesIves/github-sponsors-readme-action@1.0.5 18 | with: 19 | token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }} 20 | file: .github/README.md 21 | 22 | # Job #2 - Fetches contributors and inserts table into readme 23 | insert-contributors: 24 | runs-on: ubuntu-latest 25 | name: Inserts Contributors 💓 26 | steps: 27 | - name: Updates readme with contributors 28 | uses: akhilmhdh/contributors-readme-action@v2.3.4 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }} 31 | with: 32 | image_size: 80 33 | readme_path: .github/README.md 34 | columns_per_row: 6 35 | commit_message: 'docs: Updates contributors list' 36 | committer_username: liss-bot 37 | committer_email: liss-bot@d0h.co 38 | -------------------------------------------------------------------------------- /.github/workflows/deploy-gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Build and Deploy to GH Pages 2 | on: 3 | push: 4 | workflow_dispatch: 5 | permissions: 6 | contents: write 7 | env: 8 | # BASE_URL: email-comparison 9 | SITE: https://lissy93.github.io/email-comparison 10 | jobs: 11 | build-and-deploy: 12 | concurrency: ci-${{ github.ref }} 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 🛎️ 16 | uses: actions/checkout@v3 17 | - name: Install and Build 🔧 18 | run: | 19 | cd web 20 | yarn 21 | yarn build 22 | - name: Deploy 🚀 23 | uses: JamesIves/github-pages-deploy-action@v4.3.3 24 | with: 25 | branch: gh-pages 26 | folder: web/dist 27 | -------------------------------------------------------------------------------- /.github/workflows/mirror.yml: -------------------------------------------------------------------------------- 1 | # Pushes the contents of the repo to the Codeberg mirror 2 | name: 🪞 Mirror to Codeberg 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '45 0 * * 0' 7 | jobs: 8 | codeberg: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: { fetch-depth: 0 } 13 | - uses: pixta-dev/repository-mirroring-action@v1 14 | with: 15 | target_repo_url: git@codeberg.org:alicia/email-comparison.git 16 | ssh_private_key: ${{ secrets.CODEBERG_SSH }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | web/dist/ 3 | 4 | # generated types 5 | web/.astro/ 6 | 7 | # dependencies 8 | web/node_modules/ 9 | 10 | # logs 11 | web/npm-debug.log* 12 | web/yarn-debug.log* 13 | web/yarn-error.log* 14 | web/pnpm-debug.log* 15 | 16 | # environment variables 17 | web/.env 18 | web/.env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build the application 2 | FROM node:20-alpine as build 3 | WORKDIR /app 4 | COPY web/package.json web/yarn.lock ./ 5 | RUN yarn install --frozen-lockfile 6 | COPY web/ . 7 | RUN yarn build 8 | 9 | # Stage 2: Serve the application from Nginx 10 | FROM nginx:alpine 11 | COPY --from=build /app/dist /usr/share/nginx/html 12 | EXPOSE 80 13 | CMD ["nginx", "-g", "daemon off;"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Alicia Sykes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /email-provider-data.yml: -------------------------------------------------------------------------------- 1 | 2 | # List of mail providers (in no order) 3 | # Level indicates color (0 = N/A, 1 = Great, 2 = Okay, 3 = Bad) 4 | mailProviders: 5 | 6 | # ProtonMail 7 | - name: ProtonMail 8 | link: https://proton.me/ 9 | icon: https://i.ibb.co/J3jvgkR/protonmail.png 10 | discussionId: 17 11 | description: > 12 | Founded in 2013 in Switzerland by Andy Yen. It offers end-to-end encryption 13 | and is under Switzerland's privacy laws. Proton AG also provides VPN, calendar, 14 | file storage, and password manager services. 15 | jurisdiction: 16 | text: Switzerland 17 | level: 1 18 | encryption: 19 | text: PGP 20 | level: 1 21 | openSource: 22 | text: 'Only frontend' 23 | level: 2 24 | onionSite: 25 | text: 'Yes' 26 | level: 1 27 | pricing: 28 | text: | 29 | Free Plan - 500MB, 1 address, no custom domain. 30 | Paid plans start from €5 and allow for additional 31 | features, custom domain, and increased message volume 32 | level: 1 33 | customDomain: 34 | text: | 35 | Yes 36 | (Plus Plan, €5.00/m) 37 | level: 2 38 | aliases: 39 | text: | 40 | Yes 41 | (Professional Plan, €8.00) 42 | level: 2 43 | webClientAccess: 44 | text: Yes, but through Bridge 45 | level: 2 46 | securityAudit: 47 | text: Yes (2021, by Securitum) 48 | level: 1 49 | acceptsCrypto: 50 | text: Yes (BTC only) 51 | level: 1 52 | personalInfoRequired: 53 | text: Email for One-time Verification 54 | level: 1 55 | mobileApp: 56 | text: Yes (Android and iOS) 57 | level: 1 58 | activeDevelopment: 59 | text: 'Yes' 60 | level: 1 61 | 62 | # Tuta 63 | - name: Tuta 64 | link: https://tuta.com/ 65 | icon: https://i.ibb.co/HhHL1S4/tutanota.png 66 | discussionId: 21 67 | description: > 68 | A German-based service launched in 2011, Tuta is open-source and offers a 69 | fully encrypted mailbox, calendar, and address book. Tuta uses their own 70 | (RSA + AES based) encryption method, to protect more than just the email body 71 | (including subject, attachments, calendar, contacts, notifications). 72 | jurisdiction: 73 | text: Germany (14 Eyes) 74 | level: 1 75 | encryption: 76 | text: Hybrid AES + RSA 77 | level: 2 78 | openSource: 79 | text: Client Apps Only 80 | level: 2 81 | onionSite: 82 | text: 'No' 83 | level: 3 84 | pricing: 85 | text: | 86 | Free Plan - 1GB, 1 address, no custom domain. 87 | Paid plans start from €1 / month, and allow 88 | for a custom domain, 5 alias addresses and 89 | improved search and filters 90 | level: 1 91 | customDomain: 92 | text: | 93 | Yes 94 | (Premium Plan, €1.00/m) 95 | level: 2 96 | aliases: 97 | text: | 98 | Yes 99 | (€1 / month per 20 aliases) 100 | No catch-all 101 | level: 2 102 | webClientAccess: 103 | text: 'No' 104 | level: 3 105 | securityAudit: 106 | text: Apparently, but not published 107 | level: 2 108 | acceptsCrypto: 109 | text: No (But ProxyStore gift cards accepted 110 | level: 2 111 | personalInfoRequired: 112 | text: None 113 | level: 1 114 | mobileApp: 115 | text: Yes (Android and iOS) 116 | level: 1 117 | activeDevelopment: 118 | text: 'Yes' 119 | level: 1 120 | 121 | # Criptext 122 | - name: Criptext 123 | link: https://criptext.com/ 124 | icon: https://i.ibb.co/tKhzqJR/criptext.png 125 | discussionId: 28 126 | description: > 127 | A decentralized mail provider that doesn't require the use of any cloud storage 128 | to store or sync messages. Instead emails are received using the client apps 129 | directly, where they're encrypted using the Signal protocol. 130 | jurisdiction: 131 | text: United States (5 Eyes) 132 | level: 3 133 | encryption: 134 | text: Signal Protocol 135 | level: 2 136 | openSource: 137 | text: 'Only frontend' 138 | level: 2 139 | onionSite: 140 | text: N/A (No webmail) 141 | level: 0 142 | pricing: 143 | text: | 144 | Free Plan - ∞ emails, 25 MB per email 145 | (∞ attachments per account), no custom domain. 146 | Buisness plan ($15/month) allows for a custom domain, 147 | 50 MB attachments, email analytics, read receipts, aliases, 148 | and a catch-all email. 149 | level: 1 150 | customDomain: 151 | text: |- 152 | Yes 153 | (Business plan, $15/m) 154 | level: 2 155 | aliases: 156 | text: |- 157 | Yes 158 | (Business plan, $15/m) 159 | level: 2 160 | webClientAccess: 161 | text: 'No' 162 | level: 3 163 | securityAudit: 164 | text: 'No' 165 | level: 3 166 | acceptsCrypto: 167 | text: 'No' 168 | level: 3 169 | personalInfoRequired: 170 | text: 'None' 171 | level: 1 172 | mobileApp: 173 | text: Yes (Android, iOS and desktop) 174 | level: 1 175 | activeDevelopment: 176 | text: 'No' 177 | level: 3 178 | 179 | # Mailfence 180 | - name: Mailfence 181 | link: https://mailfence.com/ 182 | icon: https://i.ibb.co/CPjDhfY/mailfence.png 183 | discussionId: 22 184 | description: > 185 | An OpenPGP-based mail provider, published by ContactOffice Group in 2013. 186 | Mailfence focuses on offering support for standard protocols, making it 187 | compatible with any PGP-enabled mail client. 188 | jurisdiction: 189 | text: Belgium (14 Eyes) 190 | level: 1 191 | encryption: 192 | text: PGP 193 | level: 1 194 | openSource: 195 | text: 'No' 196 | level: 3 197 | onionSite: 198 | text: 'No' 199 | level: 3 200 | pricing: 201 | text: | 202 | Free Plan - 500MB, 1 address, no custom domain. 203 | Paid plans start from €2.50/m and allow for custom 204 | domain, 10 aliases, mail client access and 205 | increased message volume 206 | level: 1 207 | customDomain: 208 | text: | 209 | Yes 210 | (Entry Plan, €2.50/m) 211 | level: 2 212 | aliases: 213 | text: | 214 | Yes 215 | (Entry Plan, €2.50/m) 216 | level: 2 217 | webClientAccess: 218 | text: Entry Plan (IMAP, POP3, SMTP) 219 | level: 2 220 | securityAudit: 221 | text: 'No' 222 | level: 3 223 | acceptsCrypto: 224 | text: Yes (BTC and LTC) 225 | level: 1 226 | personalInfoRequired: 227 | text: Recovery Email Only 228 | level: 1 229 | mobileApp: 230 | text: 'Yes' 231 | level: 1 232 | activeDevelopment: 233 | text: 'Unknown' 234 | level: 0 235 | 236 | # Mailbox.org 237 | - name: Mailbox.org 238 | link: https://mailbox.org/ 239 | icon: https://i.ibb.co/jy6wQ4r/mailbox-org.png 240 | discussionId: 29 241 | description: > 242 | German-based PGP-encrypted mail service, operating since 2014. Mailbox.org is 243 | powered by green energy, and also comes bundled with OpenTalk for secure video calls. 244 | jurisdiction: 245 | text: Germany (14 Eyes) 246 | level: 1 247 | encryption: 248 | text: PGP 249 | level: 1 250 | openSource: 251 | text: 'No' 252 | level: 3 253 | onionSite: 254 | text: 'No' 255 | level: 3 256 | pricing: 257 | text: | 258 | No free plan. Starts at €1 / month. 259 | Increases to €9 for all features and storage 260 | level: 2 261 | customDomain: 262 | text: | 263 | Yes 264 | (Standard Plan, €3.00/m) 265 | level: 2 266 | aliases: 267 | text: | 268 | Yes 269 | 25 aliases (Standard Plan). 270 | level: 2 271 | webClientAccess: 272 | text: Yes (IMAP, POP3) 273 | level: 1 274 | securityAudit: 275 | text: 'No' 276 | level: 3 277 | acceptsCrypto: 278 | text: 'No' 279 | level: 3 280 | personalInfoRequired: 281 | text: | 282 | Requires full name, location, 283 | mobile number and recovery email 284 | level: 3 285 | mobileApp: 286 | text: 'No' 287 | level: 3 288 | activeDevelopment: 289 | text: 'Unknown' 290 | level: 0 291 | 292 | # Soverin 293 | - name: Soverin 294 | link: https://soverin.net/ 295 | icon: https://i.ibb.co/HF10yMr/soverin.png 296 | discussionId: 30 297 | description: > 298 | European no-thrills mail hosting for your custom domain, with a Roundcube 299 | based web client. Founded in 2014 in the Netherlands, their encryption 300 | options are limited 301 | jurisdiction: 302 | text: Netherlands (9 Eyes) 303 | level: 2 304 | encryption: 305 | text: PGP 306 | level: 1 307 | openSource: 308 | text: 'No' 309 | level: 3 310 | onionSite: 311 | text: 'No' 312 | level: 3 313 | pricing: 314 | text: | 315 | No free plan, flat fee of €3.25 / month for everything 316 | level: 2 317 | customDomain: 318 | text: | 319 | Yes 320 | (Paid Plan, €3.25) 321 | level: 2 322 | aliases: 323 | text: | 324 | Yes 325 | (Paid Plan, €3.25) 326 | level: 2 327 | webClientAccess: 328 | text: Yes (IMAP) 329 | level: 1 330 | securityAudit: 331 | text: 'No' 332 | level: 3 333 | acceptsCrypto: 334 | text: 'No' 335 | level: 3 336 | personalInfoRequired: 337 | text: Requires phone number 338 | level: 3 339 | mobileApp: 340 | text: 'No' 341 | level: 3 342 | activeDevelopment: 343 | text: 'Unknown' 344 | level: 0 345 | 346 | # Posteo 347 | - name: Posteo 348 | link: https://posteo.de/en 349 | icon: https://i.ibb.co/mBCHSx3/posteo.png 350 | discussionId: 23 351 | description: > 352 | Founded in 2009 by Patrik & Sabrina Löhr, the Berlin-based mail provider 353 | offers a simple and affordable mail service using only green energy. 354 | jurisdiction: 355 | text: Germany (14 Eyes) 356 | level: 1 357 | encryption: 358 | text: PGP 359 | level: 1 360 | openSource: 361 | text: 'Yes' 362 | level: 1 363 | onionSite: 364 | text: 'No' 365 | level: 3 366 | pricing: 367 | text: No free plan. Pricing is €1 / month 368 | level: 2 369 | customDomain: 370 | text: 'No' 371 | level: 3 372 | aliases: 373 | text: | 374 | Yes 375 | (€0.10/m per alias) 376 | No catch-all 377 | level: 2 378 | webClientAccess: 379 | text: Yes (IMAP, POP3) 380 | level: 1 381 | securityAudit: 382 | text: 'No' 383 | level: 3 384 | acceptsCrypto: 385 | text: No (Card, PayPal, Bank Transfer, Cash) 386 | level: 3 387 | personalInfoRequired: 388 | text: Payment Info Only 389 | level: 1 390 | mobileApp: 391 | text: 'No' 392 | level: 3 393 | activeDevelopment: 394 | text: 'Partial' 395 | level: 2 396 | 397 | # Runbox 398 | - name: Runbox 399 | link: https://runbox.com/ 400 | icon: https://i.ibb.co/68dSFpz/runbox.png 401 | description: > 402 | Operating since 2000 in Olso, Runbox emphasizes minimal data retention and 403 | leverages Norway's privacy laws. 404 | jurisdiction: 405 | text: Norway (14 Eyes) 406 | level: 1 407 | encryption: 408 | text: PGP 409 | level: 1 410 | openSource: 411 | text: Client Apps Only 412 | level: 2 413 | onionSite: 414 | text: 'No' 415 | level: 3 416 | pricing: 417 | text: | 418 | No free plan. Starts at €14.95 / year for a 419 | single domain, going up to €69.95 / year for 420 | more storage and domains 421 | level: 2 422 | customDomain: 423 | text: | 424 | Yes 425 | (Micro Plan, €1.25/m) 426 | level: 2 427 | aliases: 428 | text: | 429 | Yes 430 | €3.95 / year per 5 aliases 431 | No catch-all 432 | level: 2 433 | webClientAccess: 434 | text: Yes (IMAP, POP, SMTP) 435 | level: 1 436 | securityAudit: 437 | text: 'No' 438 | level: 3 439 | acceptsCrypto: 440 | text: Yes (BTC only) 441 | level: 1 442 | personalInfoRequired: 443 | text: Full name, recovery email 444 | level: 2 445 | mobileApp: 446 | text: 'No' 447 | level: 3 448 | activeDevelopment: 449 | text: 'Yes' 450 | level: 1 451 | 452 | # Kolab Now 453 | - name: Kolab Now 454 | link: https://kolabnow.com/ 455 | icon: https://i.ibb.co/nL7Hyr1/kolab-now.png 456 | discussionId: 26 457 | description: > 458 | A Swiss-based service offering a suite of online collaboration tools, 459 | including email. It operates under Swiss data protection laws. 460 | jurisdiction: 461 | text: Switzerland 462 | level: 1 463 | encryption: 464 | text: PGP 465 | level: 1 466 | openSource: 467 | text: 'Yes' 468 | level: 1 469 | onionSite: 470 | text: 'No' 471 | level: 3 472 | pricing: 473 | text: | 474 | No free plan. Starts at CHF 5.00 / mo, basic 475 | features only. Groupware account is CHF 9.90 476 | level: 2 477 | customDomain: 478 | text: | 479 | Yes 480 | (Group Plan, CHF 9.90/m) 481 | level: 2 482 | aliases: 483 | text: | 484 | Yes 485 | (Group Plan Only, CHF 9.90/ m) 486 | level: 2 487 | webClientAccess: 488 | text: Yes (IMAP, POP, SMTP) 489 | level: 1 490 | securityAudit: 491 | text: 'No' 492 | level: 3 493 | acceptsCrypto: 494 | text: No (Card, PayPal, Bank Transfer) 495 | level: 3 496 | personalInfoRequired: 497 | text: Full name, recovery email 498 | level: 2 499 | mobileApp: 500 | text: 'No' 501 | level: 3 502 | activeDevelopment: 503 | text: 'No' 504 | level: 3 505 | 506 | # CounterMail 507 | - name: CounterMail 508 | link: https://countermail.com/ 509 | icon: https://i.ibb.co/4TGTqZX/countermail.png 510 | discussionId: 20 511 | description: > 512 | Operating from Sweden since 2008, CounterMail uses diskless web servers to 513 | prevent data retention. The service supports end-to-end encryption and is 514 | unique for its USB key option, which adds an additional layer of security 515 | jurisdiction: 516 | text: Sweden (14 Eyes) 517 | level: 1 518 | encryption: 519 | text: PGP 520 | level: 1 521 | openSource: 522 | text: 'No' 523 | level: 3 524 | onionSite: 525 | text: 'No' 526 | level: 3 527 | pricing: 528 | text: | 529 | No free plan. Pricing is $4.83 / month 530 | level: 2 531 | customDomain: 532 | text: | 533 | Yes 534 | (Paid Plan + $15 setup fee) 535 | level: 2 536 | aliases: 537 | text: | 538 | Yes 539 | Max 10 text aliases 540 | No catch-all 541 | level: 2 542 | webClientAccess: 543 | text: Yes (IMAP) 544 | level: 1 545 | securityAudit: 546 | text: 'No' 547 | level: 3 548 | acceptsCrypto: 549 | text: Yes (BTC only) 550 | level: 1 551 | personalInfoRequired: 552 | text: None 553 | level: 1 554 | mobileApp: 555 | text: 'No' 556 | level: 3 557 | activeDevelopment: 558 | text: 'Yes' 559 | level: 1 560 | 561 | # StartMail 562 | - name: StartMail 563 | link: https://www.startmail.com/ 564 | icon: https://i.ibb.co/s6D3jKr/startmail.png 565 | discussionId: 27 566 | description: > 567 | From the creators of StartPage, StartMail was launched in 2014 in the 568 | Netherlands, offering PGP encrypted email and aliases. 569 | jurisdiction: 570 | text: Netherlands (9 Eyes) 571 | level: 2 572 | encryption: 573 | text: PGP 574 | level: 1 575 | openSource: 576 | text: 'No' 577 | level: 3 578 | onionSite: 579 | text: 'No' 580 | level: 3 581 | pricing: 582 | text: | 583 | No free plan. Starts at $5 / month, 584 | but without custom domain feature 585 | level: 2 586 | customDomain: 587 | text: | 588 | Yes 589 | (Domain Plan, $5.85) 590 | level: 2 591 | aliases: 592 | text: | 593 | Yes 594 | Unlimited aliases 595 | Catch-all (only for custom domains) 596 | level: 2 597 | webClientAccess: 598 | text: Yes (IMAP, SMTP) 599 | level: 1 600 | securityAudit: 601 | text: Apparently, but not published 602 | level: 2 603 | acceptsCrypto: 604 | text: Yes (BTC only, but only personal accounts) 605 | level: 2 606 | personalInfoRequired: 607 | text: None 608 | level: 1 609 | mobileApp: 610 | text: No (PWA Only) 611 | level: 2 612 | activeDevelopment: 613 | text: 'Yes' 614 | level: 1 615 | 616 | # Disroot 617 | - name: Disroot 618 | link: https://disroot.org/ 619 | icon: https://i.ibb.co/7NqHScM/disroot.png 620 | discussionId: 25 621 | description: > 622 | A Netherlands-based platform providing decentralized services, Disroot is 623 | community-driven and focuses on hosting open-source applications, 624 | funded by donations. 625 | jurisdiction: 626 | text: Netherlands (9 Eyes) 627 | level: 2 628 | encryption: 629 | text: | 630 | PGP 631 | (Off-by-default) 632 | level: 2 633 | openSource: 634 | text: 'No' 635 | level: 3 636 | onionSite: 637 | text: 'No' 638 | level: 3 639 | pricing: 640 | text: Free, payment set for each additional feature 641 | level: 1 642 | customDomain: 643 | text: | 644 | Yes 645 | (Donation required) 646 | level: 2 647 | aliases: 648 | text: 'No' 649 | level: 3 650 | webClientAccess: 651 | text: Yes (IMAP, POP, SMTP) 652 | level: 1 653 | securityAudit: 654 | text: 'No' 655 | level: 3 656 | acceptsCrypto: 657 | text: Yes (BTC, XMR and FAIR) 658 | level: 1 659 | personalInfoRequired: 660 | text: Email for One-time Verification 661 | level: 1 662 | mobileApp: 663 | text: No (Mobile app, but no mail) 664 | level: 3 665 | activeDevelopment: 666 | text: 'Yes' 667 | level: 1 668 | 669 | # Hushmail 670 | - name: Hushmail 671 | link: https://hushmail.com/ 672 | icon: https://i.ibb.co/HVhZVxT/hushmail.png 673 | discussionId: 31 674 | description: > 675 | A Canadian-based service founded in 1999, Hushmail was one of the early providers 676 | of encrypted email. While aimed more at small businesses needing complacence, 677 | healthcare and law companies, they also offer a personal plan. 678 | Hushmail also offer a secure web forms feature, ESIGN/UETA-compliant electronic 679 | signatures and a private messenger. 680 | jurisdiction: 681 | text: Canada (5 Eyes) 682 | level: 3 683 | encryption: 684 | text: PGP 685 | level: 1 686 | openSource: 687 | text: 'No' 688 | level: 3 689 | onionSite: 690 | text: 'No' 691 | level: 3 692 | pricing: 693 | text: | 694 | No free plan. Starts at $5.99 / month, 695 | rising to $7.99 for email archiving feature 696 | level: 2 697 | customDomain: 698 | text: | 699 | Yes 700 | (Business Plan, $5.99/m) 701 | level: 2 702 | aliases: 703 | text: | 704 | Yes 705 | (Small Business Plan, $5.99/m) 706 | level: 2 707 | webClientAccess: 708 | text: Yes (POP, SMTP) 709 | level: 1 710 | securityAudit: 711 | text: 'No' 712 | level: 3 713 | acceptsCrypto: 714 | text: 'No' 715 | level: 3 716 | personalInfoRequired: 717 | text: Full name, recovery email 718 | level: 2 719 | mobileApp: 720 | text: No, but planned 721 | level: 3 722 | activeDevelopment: 723 | text: 'Unknown' 724 | level: 0 725 | 726 | # LavaBit 727 | - name: LavaBit 728 | link: https://lavabit.com/ 729 | icon: https://i.ibb.co/ww9xcWz/lavabit.png 730 | discussionId: 32 731 | description: > 732 | Relaunched in 2017, known for its involvement in the Snowden revelations, 733 | LavaBit offers the Dark Mail encryption model. LavaBit's founder also worked 734 | on developing the DIME (Dark Internet Mail Environment) protocol 735 | jurisdiction: 736 | text: United States (5 Eyes) 737 | level: 3 738 | encryption: 739 | text: DIME 740 | level: 2 741 | openSource: 742 | text: 'No' 743 | level: 3 744 | onionSite: 745 | text: 'No' 746 | level: 3 747 | pricing: 748 | text: | 749 | Free plan, but invite only. 750 | Pricing ranges from $30 - $60 / year, 751 | depending on storage requirements 752 | level: 2 753 | customDomain: 754 | text: 'No' 755 | level: 3 756 | aliases: 757 | text: 'No' 758 | level: 3 759 | webClientAccess: 760 | text: 'No' 761 | level: 3 762 | securityAudit: 763 | text: 'No' 764 | level: 3 765 | acceptsCrypto: 766 | text: 'No' 767 | level: 3 768 | personalInfoRequired: 769 | text: Full name, recovery email 770 | level: 2 771 | mobileApp: 772 | text: 'No' 773 | level: 3 774 | activeDevelopment: 775 | text: 'Unknown' 776 | level: 0 777 | 778 | # Forward Email 779 | - name: Forward Email 780 | link: https://forwardemail.net 781 | icon: https://github.com/forwardemail/forwardemail.net/raw/master/assets/img/logo-square-30-30.svg 782 | discussionId: 19 783 | description: > 784 | An email forwarding service that allows users to create custom email addresses 785 | using their own domain. It operates without storing emails on its servers and 786 | adheres to open standards for email forwarding. 787 | jurisdiction: 788 | text: United States (5 Eyes) 789 | level: 3 790 | encryption: 791 | text: PGP 792 | level: 1 793 | openSource: 794 | text: 'Yes' 795 | level: 1 796 | onionSite: 797 | text: N/A (No webmail) 798 | level: 0 799 | pricing: 800 | text: | 801 | Free Plan - (only forwarding) unlimited domains, aliases, and custom domains. 802 | Paid plans start from $3/mo and allow SMTP, IMAP, POP3, and API access 803 | level: 2 804 | customDomain: 805 | text: 'Yes' 806 | level: 1 807 | aliases: 808 | text: | 809 | Yes 810 | (Unlimited) 811 | level: 1 812 | webClientAccess: 813 | text: | 814 | Yes (Enhanced and Team Plans) 815 | (SMTP, IMAP, POP3) 816 | level: 2 817 | securityAudit: 818 | text: 'No' 819 | level: 3 820 | acceptsCrypto: 821 | text: 'No' 822 | level: 3 823 | personalInfoRequired: 824 | text: None 825 | level: 1 826 | mobileApp: 827 | text: 'No' 828 | level: 3 829 | activeDevelopment: 830 | text: 'Yes' 831 | level: 1 832 | -------------------------------------------------------------------------------- /web/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import sitemap from '@astrojs/sitemap'; 3 | 4 | import lit from '@astrojs/lit'; 5 | 6 | const siteMapFilter = (page) => !page.includes('/summary'); 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | integrations: [lit(), sitemap({ filter: siteMapFilter })], 11 | site: process.env.SITE, 12 | base: process.env.BASE_URL, 13 | }); 14 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-comparison", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "check": "astro check", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/check": "^0.4.1", 15 | "@astrojs/lit": "^4.0.1", 16 | "@astrojs/sitemap": "^3.0.5", 17 | "@webcomponents/template-shadowroot": "^0.2.1", 18 | "astro": "^4.2.1", 19 | "js-yaml": "^4.1.0", 20 | "lit": "^3.1.1", 21 | "marked": "^11.1.1", 22 | "sass": "^1.70.0", 23 | "typescript": "^5.3.3" 24 | }, 25 | "devDependencies": { 26 | "@types/js-yaml": "^4.0.9" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/banner.png -------------------------------------------------------------------------------- /web/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/favicon.png -------------------------------------------------------------------------------- /web/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/public/fonts/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /web/public/fonts/Roboto-Mono-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/Roboto-Mono-regular.woff2 -------------------------------------------------------------------------------- /web/public/fonts/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /web/public/fonts/RobotoMono-Italic-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/RobotoMono-Italic-VariableFont_wght.ttf -------------------------------------------------------------------------------- /web/public/fonts/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /web/public/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /web/public/fonts/RobotoMono-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lissy93/email-comparison/d1d4219aa0100c42bcc787007d64a779eaacaf02/web/public/fonts/RobotoMono-VariableFont_wght.ttf -------------------------------------------------------------------------------- /web/src/components/EmailComparisonTable.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { LitElement, html, css } from 'lit'; 4 | import { customElement, state, property } from 'lit/decorators.js';; 5 | import yaml from 'js-yaml'; 6 | 7 | import type { MailServices, MailProvider, TextAndLevel } from '../types/MailServices'; 8 | import './Icon.ts' 9 | 10 | 11 | @customElement('email-comparison-table') 12 | export class EmailComparisonTable extends LitElement { 13 | 14 | @state() private isLoading: boolean = true; 15 | @property({ attribute: false, type: Array }) mailProviders: MailProvider[] = []; 16 | @state() private sortedColumn: string | null = null; 17 | @state() private sortAscending: boolean = true; 18 | 19 | static dataPoints = [ 20 | 'jurisdiction', 21 | 'encryption', 22 | 'openSource', 23 | 'onionSite', 24 | 'pricing', 25 | 'customDomain', 26 | 'aliases', 27 | 'webClientAccess', 28 | 'securityAudit', 29 | 'acceptsCrypto', 30 | 'personalInfoRequired', 31 | 'mobileApp', 32 | 'activeDevelopment' 33 | ] as const; 34 | 35 | static tableHeadings: Record = { 36 | jurisdiction: 'Jurisdiction', 37 | encryption: 'Encryption', 38 | openSource: 'Open Source', 39 | onionSite: 'Onion Site', 40 | pricing: 'Pricing', 41 | customDomain: 'Custom Domain', 42 | aliases: 'Aliases', 43 | webClientAccess: 'Mail Client Support', 44 | securityAudit: 'Security Audit', 45 | acceptsCrypto: 'Accepts Crypto', 46 | personalInfoRequired: 'Personal Info Required', 47 | mobileApp: 'Mobile App', 48 | activeDevelopment: 'Active Development' 49 | }; 50 | 51 | static styles = css` 52 | section { 53 | max-width: 1000px; 54 | margin: 0 auto; 55 | width: 80vw; 56 | padding: 1.8rem; 57 | } 58 | 59 | h2 { 60 | color: var(--foreground-head); 61 | font-size: 2rem; 62 | margin: 0.5rem 0; 63 | } 64 | 65 | .intro { 66 | margin: 0 0 0.5rem 0; 67 | font-size: 0.8rem; 68 | } 69 | 70 | .table-container { 71 | overflow-x: auto; 72 | border-radius: 3px; 73 | border: 1px solid var(--foreground-light); 74 | &::-webkit-scrollbar { 75 | height: 10px; 76 | background-color: var(--background); 77 | } 78 | &::-webkit-scrollbar-thumb { 79 | background-color: var(--primary); 80 | border-radius: 4px; 81 | } 82 | &::-webkit-scrollbar-track { 83 | background-color: var(--background); 84 | } 85 | } 86 | 87 | table { 88 | min-width: max-content; 89 | border-collapse: collapse; 90 | background-color: var(--background); 91 | color: var(--foreground-body); 92 | } 93 | 94 | th, td { 95 | border: 1px solid var(--foreground-light); 96 | padding: 0.5rem; 97 | text-align: left; 98 | max-width: 10rem; 99 | overflow: hidden; 100 | text-overflow: ellipsis; 101 | white-space: nowrap; 102 | vertical-align: top; 103 | transition: all 0.2s ease-in-out; 104 | a { 105 | color: var(--foreground); 106 | text-decoration: none; 107 | &:hover { 108 | text-decoration: underline; 109 | } 110 | } 111 | } 112 | 113 | th { 114 | position: sticky; 115 | top: 0; 116 | background: var(--foreground-lighter); 117 | border-top: none; 118 | z-index: 2; 119 | border-right: 1px solid var(--foreground-light); 120 | cursor: pointer; 121 | transition: all 0.2s ease-in-out; 122 | &:hover { 123 | color: var(--primary); 124 | } 125 | } 126 | 127 | tbody tr td:first-child { 128 | position: sticky; 129 | left: 0; 130 | background-color: var(--background); 131 | z-index: 1; 132 | color: var(--foreground); 133 | } 134 | 135 | tbody tr td:first-child a { 136 | color: var(--foreground); 137 | text-decoration: none; 138 | &:hover { text-decoration: underline; } 139 | } 140 | 141 | th:first-child { 142 | left: 0; 143 | z-index: 3; 144 | background: var(--background); 145 | } 146 | 147 | .level-1 { 148 | background-color: var(--green); 149 | } 150 | .level-2 { 151 | background-color: var(--amber); 152 | } 153 | .level-3 { 154 | background-color: var(--red); 155 | } 156 | .level-0, .level-other { 157 | background-color: var(--grey); 158 | } 159 | 160 | tr:hover td:not(:first-child) { 161 | white-space: break-spaces; 162 | } 163 | `; 164 | 165 | firstUpdated() { 166 | if (this.mailProviders.length === 0) { 167 | this.fetchData(); 168 | } 169 | } 170 | 171 | async fetchData() { 172 | try { 173 | const response = await fetch('https://raw.githubusercontent.com/Lissy93/email-comparison/master/src/data.yml'); 174 | const text = await response.text(); 175 | const data = yaml.load(text); 176 | this.mailProviders = (data as MailServices).mailProviders; 177 | } catch (error) { 178 | console.error('Error fetching data:', error); 179 | } finally { 180 | this.isLoading = false; 181 | } 182 | } 183 | 184 | toggleSort(column: string) { 185 | if (this.sortedColumn === column) { 186 | this.sortAscending = !this.sortAscending; 187 | } else { 188 | this.sortedColumn = column; 189 | this.sortAscending = true; 190 | } 191 | this.sortData(); 192 | } 193 | 194 | sortData() { 195 | this.mailProviders = [...this.mailProviders].sort((a, b) => { 196 | let aValue = this.getValue(a, this.sortedColumn); 197 | let bValue = this.getValue(b, this.sortedColumn); 198 | 199 | if (typeof aValue === 'number' && typeof bValue === 'number') { 200 | return this.sortAscending ? aValue - bValue : bValue - aValue; 201 | } 202 | 203 | aValue = aValue.toString().toLowerCase(); 204 | bValue = bValue.toString().toLowerCase(); 205 | 206 | if (aValue < bValue) { 207 | return this.sortAscending ? -1 : 1; 208 | } 209 | if (aValue > bValue) { 210 | return this.sortAscending ? 1 : -1; 211 | } 212 | return 0; 213 | }); 214 | } 215 | 216 | getValue(provider: MailProvider, column: string | null): string | number { 217 | if (column === 'name') { 218 | return provider.name; 219 | } 220 | const data = provider[column as keyof MailProvider] as TextAndLevel; 221 | return data.level || data.text; 222 | } 223 | 224 | renderCell(data: TextAndLevel) { 225 | const levelClass = `level-${data.level || 'other'}`; 226 | return html`${data.text}`; 227 | } 228 | 229 | renderHeaders() { 230 | return html` 231 | 232 | this.toggleSort('name')}> 233 | Provider 234 | 235 | 236 | ${EmailComparisonTable.dataPoints.map(point => html` 237 | this.toggleSort(point)}> 238 | ${EmailComparisonTable.tableHeadings[point]} 239 | 240 | ` 241 | )} 242 | 243 | `; 244 | } 245 | 246 | 247 | renderProviderRow(provider: MailProvider) { 248 | return html` 249 | 250 | 251 | icon 252 | ${provider.name} 253 | 254 | ${EmailComparisonTable.dataPoints.map(point => this.renderCell(provider[point]))} 255 | 256 | `; 257 | } 258 | 259 | 260 | renderTable() { 261 | if (this.isLoading && this.mailProviders.length === 0) { 262 | return html`

Loading...

`; 263 | } 264 | 265 | return html` 266 | 267 | 268 | ${this.renderHeaders()} 269 | 270 | 271 | ${this.mailProviders.map(provider => this.renderProviderRow(provider))} 272 | 273 |
274 | `; 275 | } 276 | 277 | render() { 278 | return html` 279 |
280 |

Summary

281 |

Scroll horizontally to view further columns, hover over a row to read, click a heading to sort.

282 |
283 | ${this.renderTable()} 284 |
285 |
286 | `; 287 | } 288 | } 289 | 290 | -------------------------------------------------------------------------------- /web/src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /web/src/components/Hero.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import './Icon.ts' 3 | --- 4 | 5 |
6 |

7 | 8 | Email Comparison 9 |

10 |

11 | An objective comparison of private
and/or secure email services 12 |

13 | 14 | 15 | View on GitHub 16 | 17 |
18 | 19 | 62 | -------------------------------------------------------------------------------- /web/src/components/Icon.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from 'lit'; 2 | import { property } from 'lit/decorators.js'; 3 | 4 | export class IconComponent extends LitElement { 5 | @property() iconName: string = ''; 6 | @property() iconSize: string = '1rem'; 7 | @property() iconWidth: string = ''; 8 | @property() iconHeight: string = ''; 9 | @property() iconColor: string = 'currentColor'; 10 | 11 | renderIcon() { 12 | const style = `width: ${this.iconWidth || this.iconSize || '1rem'}; height: ${this.iconHeight || this.iconSize || '1rem'}; fill: ${this.iconColor || 'currentColor'};`; 13 | switch (this.iconName) { 14 | case 'email': 15 | return html``; 16 | case 'github': 17 | return html``; 18 | case 'sort': 19 | return html``; 20 | case 'sorted': 21 | return html``; 22 | case 'website': 23 | return html``; 24 | case 'git': 25 | return html``; 26 | case 'skull': 27 | return html``; 28 | case 'hundred': 29 | return html``; 30 | case 'thumb': 31 | return html``; 32 | case 'question': 33 | return html``; 34 | default: 35 | return html``; 36 | } 37 | } 38 | 39 | render() { 40 | return html`${this.renderIcon()}`; 41 | } 42 | } 43 | 44 | customElements.define('icon-component', IconComponent); 45 | -------------------------------------------------------------------------------- /web/src/components/Intro.astro: -------------------------------------------------------------------------------- 1 |
2 |

About

3 |

4 | Looking for a new email provider? Care about privacy and security? You've come to the right place.
5 | The purpose of this site is give you the data you need, to make an informed decission. 6 |

7 |

8 | This is a community-maintained resource. If you spot something that needs updating, or would to suggest an addition or deletion, 9 | please either open a ticket or raise a PR in the GitHub repo. 10 |

11 | Read More 12 |
13 | 14 | 24 | -------------------------------------------------------------------------------- /web/src/components/MetaTags.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Email Comparison | Objective analysis of private / secure mail providers 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /web/src/components/Nav.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import './Icon'; 3 | const baseUrl = import.meta.env.BASE_URL; 4 | --- 5 | 6 | 18 | 19 | 51 | -------------------------------------------------------------------------------- /web/src/components/ProviderLinks.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { TransformedMailProvider } from '../types/MailServices'; 3 | import { transformMailProviders } from '../utils/transform-data'; 4 | const { mailProviders, title } = Astro.props; 5 | 6 | const baseUrl = import.meta.env.BASE_URL; 7 | 8 | const getPositives = (service: TransformedMailProvider) => { 9 | return service.attributes.filter((attribute) => attribute.level === 1).map((attribute) => attribute.title); 10 | } 11 | 12 | const sortServicesByPositives = (services: TransformedMailProvider[]) => { 13 | return services.sort((a, b) => { 14 | const aPositives = getPositives(a); 15 | const bPositives = getPositives(b); 16 | return bPositives.length - aPositives.length; 17 | }); 18 | } 19 | 20 | const makeSellingPointsString = (service: TransformedMailProvider) => { 21 | const positives = getPositives(service); 22 | const ifNone = 'No known positives (yet)'; 23 | return positives.length > 1 ? `Positives: ${positives.join(' • ')}` : ifNone; 24 | } 25 | 26 | const services = sortServicesByPositives(transformMailProviders(mailProviders)); 27 | 28 | --- 29 | 30 |
31 |

{title || 'Reports'}

32 | 44 |
45 | 46 | 96 | -------------------------------------------------------------------------------- /web/src/components/Reviews.ts: -------------------------------------------------------------------------------- 1 | import { LitElement, html, css } from 'lit'; 2 | import { marked } from 'marked'; 3 | import { customElement, property, state } from 'lit/decorators.js'; 4 | 5 | 6 | type GhCommentReactionContent = 'THUMBS_UP' | 'THUMBS_DOWN' | 'LAUGH' | 'HOORAY' | 'CONFUSED' | 'HEART' | 'ROCKET' | 'EYES'; 7 | 8 | interface GhCommentReaction { 9 | content: GhCommentReactionContent; 10 | count: number; 11 | } 12 | 13 | interface GhCommentAuthor { 14 | login: string; 15 | avatarUrl: string; 16 | name?: string; 17 | bio?: string; 18 | createdAt: string; 19 | } 20 | 21 | interface GhComment { 22 | author: GhCommentAuthor; 23 | body: string; 24 | createdAt: string; 25 | lastEditedAt: string | null; 26 | upvoteCount: number; 27 | reactions: GhCommentReaction[]; 28 | } 29 | 30 | 31 | 32 | // @customElement('github-discussion-reviews') 33 | export class Reviews extends LitElement { 34 | @property({ attribute: false, type: Number }) discussionId: number | undefined; 35 | @state() private comments: Comment[] = []; 36 | @state() private isLoading: boolean = false; 37 | 38 | static styles = css` 39 | .comment-wrapper { 40 | margin-bottom: 2rem; 41 | a { 42 | color: var(--primary); 43 | } 44 | .view-all-link { 45 | font-size: 0.8rem; 46 | @media (min-width: 800px) { 47 | float: right; 48 | margin-top: -3rem; 49 | } 50 | } 51 | } 52 | 53 | 54 | .comment { 55 | border: 1px solid var(--foreground-lighter); 56 | padding: 0.5rem; 57 | margin: 0.5rem auto; 58 | &:hover { 59 | .comment-meta { 60 | opacity: 1; 61 | } 62 | .author-avatar { 63 | transform: scale(1.1); 64 | } 65 | } 66 | } 67 | .author-info { 68 | display: flex; 69 | .author-name-stats { 70 | a { display: inline; } 71 | p { 72 | margin: 0; 73 | } 74 | .metrics, .website { 75 | font-size: 0.8rem; 76 | opacity: 0.75; 77 | } 78 | 79 | } 80 | } 81 | .author-avatar { 82 | width: 50px; 83 | height: 50px; 84 | border-radius: 50%; 85 | margin-right: 10px; 86 | transition: all 0.2s ease-in-out; 87 | } 88 | .comment-meta { 89 | cursor: default; 90 | opacity: 0.8; 91 | transition: all 0.2s ease-in-out; 92 | display: flex; 93 | justify-content: space-between; 94 | align-items: center; 95 | gap: 1rem; 96 | flex-wrap: wrap; 97 | .edited { 98 | color: var(--primary); 99 | } 100 | .date { 101 | font-size: 0.8rem; 102 | opacity: 0.75; 103 | margin: 0; 104 | } 105 | .upvotes { 106 | border: 1px solid var(--primary); 107 | height: 1.5rem; 108 | display: inline-block; 109 | width: 2.8rem; 110 | border-radius: 4px; 111 | padding: 0; 112 | margin: 0; 113 | font-size: 0.9rem; 114 | background: #ffea3024; 115 | text-align: center; 116 | } 117 | } 118 | `; 119 | 120 | connectedCallback(): void { 121 | super.connectedCallback(); 122 | if (this.discussionId) { 123 | this.fetchComments(); 124 | } 125 | } 126 | 127 | updated(changedProperties: Map): void { 128 | if (changedProperties.has('discussionId') && this.discussionId !== undefined) { 129 | this.fetchComments(); 130 | } 131 | } 132 | 133 | async fetchComments(): Promise { 134 | this.isLoading = true; 135 | const apiUrl = `https://github-discussion-comments.as93.workers.dev/?username=lissy93&repo=email-comparison&discussionId=${this.discussionId}`; 136 | 137 | try { 138 | const response = await fetch(apiUrl); 139 | if (!response.ok) { 140 | throw new Error('Failed to fetch comments'); 141 | } 142 | this.comments = await response.json(); 143 | } catch (error) { 144 | console.error('Fetch error:', error); 145 | // Handle error gracefully in the UI 146 | } finally { 147 | this.isLoading = false; 148 | } 149 | } 150 | 151 | formatFollowerCount(followerCount: number): string { 152 | return followerCount < 1000 ? followerCount.toString() : `${(followerCount / 1000).toFixed(1)}k`; 153 | } 154 | 155 | timeSinceJoined(joinedDateStr: string): string { 156 | const joinedDate = new Date(joinedDateStr); 157 | const now = new Date(); 158 | const diffYears = now.getFullYear() - joinedDate.getFullYear(); 159 | const diffMonths = now.getMonth() - joinedDate.getMonth(); 160 | const diffDays = now.getDate() - joinedDate.getDate(); 161 | 162 | if (diffYears > 0) return `${diffYears} ${diffYears === 1 ? 'year' : 'years'}`; 163 | if (diffMonths > 0) return `${diffMonths} ${diffMonths === 1 ? 'month' : 'months'}`; 164 | if (diffDays > 0) return `${diffDays} ${diffDays === 1 ? 'day' : 'days'}`; 165 | return 'Joined today'; 166 | } 167 | 168 | formatDate(dateStr: string): string { 169 | return new Date(dateStr).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); 170 | } 171 | 172 | mapReactionToEmoji(reaction: GhCommentReactionContent): string { 173 | switch (reaction) { 174 | case 'THUMBS_UP': 175 | return '👍'; 176 | case 'THUMBS_DOWN': 177 | return '👎'; 178 | case 'LAUGH': 179 | return '😂'; 180 | case 'HOORAY': 181 | return '🎉'; 182 | case 'CONFUSED': 183 | return '😕'; 184 | case 'HEART': 185 | return '❤️'; 186 | case 'ROCKET': 187 | return '🚀'; 188 | case 'EYES': 189 | return '👀'; 190 | default: 191 | return ''; 192 | } 193 | }; 194 | 195 | sanitizeHtml(html: string): string { 196 | return html.replace(/]*>([\S\s]*?)<\/script>/gmi, '') 197 | .replace(/]*>([\S\s]*?)<\/style>/gmi, ''); 198 | } 199 | 200 | makeAuthorInfo(author: GhCommentAuthor) { 201 | const authorLink = html`@${author.login}`; 202 | // const authorWebsite = author.websiteUrl ? html`${author.websiteUrl}` : null; 203 | return html` 204 |
205 | ${author.login} 206 |
207 |

${author.name ? html`${author.name} (${authorLink})` : html`${authorLink}`}

208 |

Joined ${this.timeSinceJoined(author.createdAt)} ago • ${this.formatFollowerCount(author.followers.totalCount)} followers

209 |
210 |
211 | `; 212 | } 213 | 214 | makeCommentInfo(comment: GhComment) { 215 | const date = this.formatDate(comment.createdAt); 216 | const edited = comment.lastEditedAt ? html`Edited` : null; 217 | 218 | 219 | return html` 220 |
221 |
222 | ⮝ ${comment.upvoteCount} 223 | 224 | ${ 225 | comment.reactionGroups 226 | .filter(reaction => reaction.users.totalCount > 0) 227 | .map(reaction => html` 228 | 229 | ${this.mapReactionToEmoji(reaction.content)} 230 | 231 | `)} 232 | 233 |
234 |

Posted on ${date} ${edited}

235 |
236 | `; 237 | } 238 | 239 | renderComment(comment: GhComment) { 240 | const sanitizedHtml = this.sanitizeHtml(marked(comment.body)); 241 | return html` 242 |
243 | ${this.makeAuthorInfo(comment.author)} 244 |

245 | ${this.makeCommentInfo(comment)} 246 |
247 | `; 248 | } 249 | 250 | render() { 251 | return html` 252 |
253 | 259 | ${this.isLoading ? html`

Loading user reviews from GitHub...

` : null} 260 | ${this.comments.length == 0 && !this.isLoading ? html`

Nothing yet. Why don't you be the first to leave a comment?

` : null} 261 | ${this.comments.map(comment => this.renderComment(comment))} 262 | 263 |
264 | `; 265 | } 266 | } 267 | 268 | customElements.define('github-discussion-reviews', Reviews); 269 | -------------------------------------------------------------------------------- /web/src/components/ServiceInfo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { transformMailProviders } from '../utils/transform-data'; 3 | import './Icon.ts' 4 | 5 | 6 | const { provider } = Astro.props; 7 | 8 | const metrics = transformMailProviders([provider])[0]; 9 | 10 | --- 11 | 12 |
13 |

Key Info: {provider.name}

14 | {metrics.attributes.map((attribute) => ( 15 |
16 |

{attribute.title}

17 | 18 | {attribute.level === 1 19 | ? 20 | : attribute.level === 2 21 | ? 22 | : attribute.level === 3 23 | ? 24 | : 25 | } 26 | 27 |

{attribute.value}

28 |
29 | ))} 30 |
31 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /web/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | import '../styles/colors.scss'; 4 | import '../styles/typography.scss'; 5 | import '../styles/globals.scss'; 6 | 7 | import Footer from '../components/Footer.astro'; 8 | import MetaTags from '../components/MetaTags.astro'; 9 | 10 | interface Props { 11 | title: string; 12 | dontIndex: boolean | undefined; 13 | } 14 | 15 | const { title, dontIndex } = Astro.props; 16 | --- 17 | 18 | 19 | 20 | 21 | 22 | {dontIndex && } 23 | { title && {title}} 24 | 25 | 26 | 27 |