├── .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 |
148 |
149 |
150 |
151 | ### Sponsors
152 |
153 |
154 |
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 |
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 |
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 |
7 |
8 |
9 | Email Comparison
10 |
11 |
17 |
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(/