un / inbox Goto Github PK
View Code? Open in Web Editor NEWModern email for teams and professionals. A replacement for outdated email technology and tools. Alt to hey.com, front.com, missiveapp.com
Home Page: https://uninbox.com
License: Other
Modern email for teams and professionals. A replacement for outdated email technology and tools. Alt to hey.com, front.com, missiveapp.com
Home Page: https://uninbox.com
License: Other
when uploading a new avatar, the upload does succeed and the image is accessible
but the avatar on the page does not refresh to new value
we should return the parsed signatures from the parsed email for use in app
We have some mitigations in place, but we are not entirely protected from CSRF attacks
There is a handy nuxt-module for CSRF protection, but it generally relies on using the custom fetch composable for full operations: https://github.com/morgbn/nuxt-csurf
the module also has limitations and provides little abstraction
Luckily, we use tRPC, and all app API stuff happens over tRPC (except for logout)
so we can add the CSRF token to the header in the tRPC client and create a tRPC middleware to validate the token
The generation of the initial CSRF token would need to be done, stored somewhere serverside, and have a way to link it to a user session.
As per the CSRF mitigation guide: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern
Current thoughts to implement:
When user hits login page, generate "unattached" CSRF token, store in cache, return to user
When user logs in, generate new CSRF, add to userSession data stored in cache, return to user
tRPC client should inject the CSRF token into the headers
Open to input and ideas
Local dev dosnt work directly on a windows machine
Windows users should instead set up and use WSL
@McPizza0 created a lengthy guide to set thing up as a video: https://www.youtube.com/watch?v=xL-nwGpQ0Ng
we need to summarize this into steps and potentially add it to a linked doc from the main readme
Original issue:
I ran into this while tryna set-up my local dev server on my windows machine.
Windows users encounter errors running scripts due to differences in command syntax. I think the scripts across all apps have been written in a ways that isn't exactly cross-compactible, the current script look like they run only on Unix-based systems
(I think π¬).
pnpm run dev
Scripts should run without errors on Windows.
Windows throws an error because it does not recognize the syntax PORT
. Notice the specific error in the log below...
> [email protected] dev C:\Users\User\Documents\Projects\open-source\uninbox
> dotenv -e .env.local -- turbo run dev --filter=!@uninbox/landing
// [minimised]
@uninbox/web-app:dev:
@uninbox/web-app:dev: > @uninbox/web-app@ dev C:\Users\User\Documents\Projects\open-source\uninbox\apps\web-app
@uninbox/web-app:dev: > PORT=3000 nuxt dev --host
@uninbox/web-app:dev:
@uninbox/web-app:dev: 'PORT' is not recognized as an internal or external command, // <---- π π π π π π
@uninbox/web-app:dev: operable program or batch file.
@uninbox/web-app:dev: βELIFECYCLEβ Command failed with exit code 1.
@uninbox/postal-puppet:build:puppet:
@uninbox/postal-puppet:build:puppet: > @uninbox/[email protected] build:puppet C:\Users\User\Documents\Projects\open-source\uninbox\packages\postal-puppet
@uninbox/postal-puppet:build:puppet: > unbuild
@uninbox/postal-puppet:build:puppet:
@uninbox/storage:dev:
@uninbox/storage:dev: > @uninbox/storage@ dev C:\Users\User\Documents\Projects\open-source\uninbox\apps\storage
@uninbox/storage:dev: > PORT=3200 npx nitropack dev
@uninbox/storage:dev:
@uninbox/storage:dev: 'PORT' is not recognized as an internal or external command, // <---- π π π π π π
@uninbox/storage:dev: operable program or batch file.
@uninbox/storage:dev: βELIFECYCLEβ Command failed with exit code 1.
// [minimised]
Modify the dev
scripts in all apps
package.json using the format below in places where PORT
are set
"dev": "set PORT=3*** && <command>"
Replace 3***
with appropriately stated port and replace <command>
with the already existing command
Update all scripts in the repository to use cross-platform compatible syntax.
we use vue-email for email templating and need some transactional email templates created
these are the needed emails and their props:
verify recovery email
props: {
username: string;
verification code: string;
verification link: string;
}
login code
props: {
username: string;
login code: string;
}
invite
props: {
inviterName: string;
inviterAvatarId: string
orgName: string;
orgAvatarId: string;
inviteCode: string;
expiryDate: datetime;
}
templates should be created in apps/web-app/emails
the folder contains a vercel email that can be used as a template
be sure to check https://vuemail.net/ docs
Currently:
When adding a new group, you can enter the name, description and color via the add new group modal
new modal file path: ./apps/web-app/components/settings/addNewGroup.vue
We need to add a toggle to also create an email identity for the group, based on the add new email modal
All the logic and inputs already exist, see add new email modal for code: ./apps/web-app/components/settings/addNewEmail.vue
the new toggleable input options should be: address, domain and send name. reuse the same input validation that currently exists. the group should be set as the "deliver messages to".
we should re-use the same trpc endpoint that creates email identities to avoid duplicating backend logic/code: $trpc.org.mail.emailIdentities.createNewEmailIdentity
--
If you want to work on this, please tag me
some settings pages have a < back arrow to go back to main page
this link does not work correctly on some
these are usually on the details page of one entry (single domain, single group etc)
we should save the last time a user has logged in to the database
this could be done when we create a session for the user
the data would be used to check for active/inactive users by combining lastLoginAt
with a check on active sessions
Q: Can I use UnInbox for my personal email?
A: Yes, most of our features are designed for collaboration, but everything should work if its just you.
Suggestion: Yes, most of our features are designed for collaboration, but everything should work even if it's just you.
Q: Why are custom domains not available on the free plan?
A: There is a high risk of spam and abuse with custom domains, and this could damage the all our sending infrastructure.
We require a paid plan as verification and to cover the additional technical requirements of ensuring safe email sending across our platform.
Suggestion: There is a high risk of spam and abuse with custom domains, and this could damage our infrastructure.
We require a paid plan as verification and to cover the additional technical requirements of ensuring safe email sending across our platform.
Q: Will you have a calendar?
A: It's planned, but will come after the email app is stable.
Suggestion: Our initial focus is ensuring the stability of the email app and a seamless experience for its users. We'll introduce new features, including the calendar, once the app is stable.
Q: Can I migrate my emails from Google or another provider?
Q: We want
to make De-Googling and migrating from other providers
super easy. Its not yet possible but its on our priorities list.
Suggestion: We aim
to make De-Googling and migrating from other email service providers
super easy. While it's not possible yet, it's on our list of priorities.
Q: Will your prices ever increase?
A: Yes, as time goes on things become more expensive. BUT, when you have an active subscription, the price is locked in till your subscription expires.
Suggestion: Yes, over time, prices may increase due to rising costs. However, if you have an active subscription, your price will be locked in until your subscription expires.
Q: Can I use UnInbox with another email client?
A: No, we are built on very different technologies and theres
simply no way to make them compatible. If you really need to use another email client, check for another service or use our forwarding feature.
Suggestion: No, we are built on very different technologies and there's
simply no way to make them compatible. If you really need to use another email client, check for another service or use our forwarding feature.
Q: Can I forward mail in from another service?
A: Yes, you get a personal email address with UnInbox, and a dedicated forwarding address. Any emails sent to your forwarding address will appear in your UnInbox. You can also forward all emails for a custom domain to your UnInbox organization.
Suggestion: Can I forward emails from another email service provider?
Q: Will you do what Skiff did and abandon us?
A: No, we do not plan on getting acquired, that's not our end goal. If we were ever to shut down, our code will remain freely available for you to self-host and we will take some steps to ensure the online platform can remain active.
Suggestion: No, we do not plan on getting acquired, that's not our end goal. If we were ever to shut down, our code will remain freely available for you to self-host and we will take some steps to ensure that the online platform will remain active.
We use Node v20 for our development
to ensure contributors can run the code on their system without odd node conflicts
we should add the following to all package.json
files across the repo
"engines": {
"node": ">=20",
"pnpm": ">=8"
}
just an internal bug to track
we need to update the way we store and use avatars to ensure caching of local images are updated, and to reduce console errors when an avatar is not set.
changes needed:
avatarId
field to the following tables: userProfiles
, orgs
, userGroups
, contacts
storage
app avatar.post
api endpoint to generate avatarId using nanoIdLong
, use for s3 upload (with new object key of (${typeObject.value}_${avatarId}/${size.name}
), save to db table and return avatarId to webappuserProfiles
, orgs
, userGroups
, contacts
to also return the avatarId
columnUnUiAvatar
component to accept new avatarId
prop for use in useUtils().generateAvatarUrl()
generateAvatarUrl
to use new avatarId
UnUiAvatar
component instances to pass in new avatarId
propawaiting final confirmation
when a user, org, group or contact does not have an avatar image, an error is logged out to the console from the Avatar component ![image](https://github.com/uninbox/UnInbox/assets/17185737/141d654f-4c08-4a72-b4e4-5ce5f32debb4) https://github.com/nuxt/ui/blob/dev/src/runtime/components/elements/Avatar.vue
we do not know if an object has an avatar image or not
so we always pass a URL to the avatar component, which tries to fetch the avatar
we need a way to suppress these error logs, or hide them in another way
if we cant remove them, we may need to add a hasAvatar
boolean to the profile tables for each object type
@cstrnt thoughts
when a user updates their profile or org profile avatar, the browser still uses the locally cached image
this is because avatar URLs are fixed and do not change with updates
we have a workaround in place to append a timestamp to the avatar URLs, but this means that the app has to re-fetch the avatar from the server every single time, incurring latency and cost ($) for transport
https://github.com/uninbox/UnInbox/blob/904fb08d27f45b2555cead718ea06b36cc7784b9/apps/web-app/composables/utils.ts#L32
we need a better permanent fix for this issue
possible solution:
updatedAt
column to userProfile
, orgs
, userGroups
& contacts
tables. append the updatedAt
value to generateAvatarUrl
functionthoughts @cstrnt ?
Currently we have some ids stored as numbers, some as strings (see here for more details: #49 (comment))
Those should be normalized so we don't have to do conversions anymore when comparing them.
Also the current conversions should be removed
Our backend and front end are split across two apps
each of them needs to know which tiptap extensions are installed/used
currently this is set manually in each app, but could become de-synced if changed in only one
we should create a new package to handle tiptap types and export the extensions from there
we should also create different sets of extensions for future use (tiptapConversations
for email stuff, tiptapDocuments
for docs etc..)
@BlankParticle has offered to do this
the current avatar upload components are fugly
across the app we use round avatars, the current implementation is from legacy code/design
we need a new avatar/upload component that shows existing/blank avatar and a button to upload the avatar
please submit examples/designs before working on this issue
places to change:
/apps/web-app/pages/[orgSlug]/settings/org/index.vue
/apps/web-app/pages/[orgSlug]/settings/user/profiles.vue
/apps/web-app/pages/join/profile.vue
we should add a playground to make it easier when developing the mailtools package
when clicking the logout button, it fails to clear the cookies and log the user out
When attempting to navigate to the oss-friends page, I encounter a 500 internal server error.
In the About
section of UnInbox repo, it says
Alt to hey.com, front.com, missive.com
I presume you meant missiveapp.com
, because missive.com
is a p0rn site.
At https://uninbox.com/ , the x-axis data is overflowing a few pixels , showing a ugly scroll bar on the horizontal axis ,
it can be easily rectified easily with simple CSS ,
Hey contributor π
currently: In few place input validation is broken. [Empty Spaces OR White Spaces can be entered/updated]
[please check in other places too]
Update Request : Show throw Errors in below the input fields OR Should show TOAST messages .
like this one below [ only on page based field (not on the components) ]
&
[on components just need to show error below input fields ]
issue appeared where page navigation was being blocked (url updates but new page content does not render
no clear errors in the logs
after some diagnosis I discover that 1 single trpc call was the cause
the trpc call exists on the main layout navigation, thus blocking almost all navigation
if the to page includes a layout change, navigation works correctly
if the to page does not include a layout change, the URL will update, network calls on the to page will execute, but nothing new will render
to unblock the app, I removed the call here: 4f771db
we need to identify the cause and fix the issue so we can re-implement the profile
When users add a new passkey, we should determine which authenticator created the passkey
to do this we should extract the aaguid
and do a lookup for the name and icons
here is a great implementation by Hanko: https://github.com/teamhanko/hanko/pull/1303/files#diff-0ee85ca590e4ad6694aa28de2d438ea7184e2fe81f08110e936bae88cd647de9
it the old version
There appears to be demand for a desktop app.
https://discord.com/channels/1113119653246545961/1207658838925385770
I propose we use Tauri to begin with. It's lighter than Electron, allows us to reuse our existing web-app frontend, and can support localhost if needed for self-hosting via their 1st-party localhost plugin for production use.
Tauri will allow us to iterate on top of our existing web app while providing more stable data access interfaces (via their rust bindings to sql) and because it's cross-platform, allows us to bring an enhanced desktop experience very quickly to all desktop users.
There is an option for mobile builds, but that is still in beta. imo, it would be better for us to design a separate mobile application optimised for that platform, rather than deriving off of the web-app's mobile layout.
When generating user session - we should pass in the user's device (type/name) and browser.
This will be stored in the session data, and used to give users an easy way to manage their sessions from the UI
notes:
user-agent
is not reliable since all chromium browser report as chromegetComputedStyle(document.documentElement).getPropertyValue('--arc-palette-title') ? 'Is Arc' : 'Is Not Arc'
!navigator.brave
or typeof navigator?.brave?.isBrave === 'function'
with the adopted Tempo mail parser, it dosnt remove the signatures and quotes from several providers
we should add additional attributes for it to remove
also, there are often lots of additional styles leftover from the various email clients, we should look to remove these too!
when going to the domain detail page to check the dns settings, some debug information is visible on the page
Once #79 merges, we need to add a new security settings page so users can manage their own security
they should be able to:
rules:
noUncheckedIndexedAccess: true
I've tried creating an account in firefox where I don't have any passkey setup.
Therefore the setup failed and an error was logged to the console but the UI still displayed a loading state.
Optimally there should be a toast giving some feedback to the user
currently the mail parser removes simple tracking pixels by the size of the image or by known urls(only one for now)
we should expand the list of URLs to include other known trackers.
heres some links to other URL lists:
https://github.com/Foundry376/Mailspring/blob/e7daf5abf255aedfceadb33ef7209dc9101074a0/app/internal_packages/remove-tracking-pixels/lib/main.ts#L42
https://github.com/leavemealone-app/email-trackers/blob/master/trackers.txt
https://github.com/nylas/nylas-mail/blob/e16cb1982e944ae7edb69b1abef1436dd16b442d/packages/client-app/internal_packages/remove-tracking-pixels/lib/main.es6#L32
https://github.com/leggett/simplify-trackers/blob/56f01d5d96e7f0f09f4a664c5faafba52a2e1fce/ruby/Trackers.rb#L70
https://github.com/OneClickLab/ugly-email-trackers/blob/master/list.txt
we should check the license on each of these, and use as many as we can
I thought this was already implemented, but a user has a profile per organization
when creating a new org, or joining a new one as an existing user, we should duplicate their current profile and create a new one for the user
currently the users existing profile is used
Across the repo we currently use tRPC mutate
composable provided by nuxt-trpc, where the returned value === data
const newDomainResponse = await $trpc.org.mail.domains.createNewDomain.mutate({
domainName: newDomainNameValue.value
});
there are many instances where tRPC will throw an error (we use tRPC to handle all backend errors)
the tRPC client handles these by showing a toast message with the error
we dont have any way to reset loading state for buttons on the page, requiring a full refresh to submit again
nuxt-trpc recently added a useMutation
composable : wobsoriano/trpc-nuxt#132
the above example would become
const createNewDomainTrpc =
$trpc.org.mail.domains.createNewDomain.useMutation();
await createNewDomainTrpc.mutate({
domainName: newDomainNameValue.value
});
the createNewDomainTrpc
object now contains a status
key with possible values 'idle' | 'pending' | 'success' | 'error'
we can check if tRPC threw an error and reset the form loading state
if (createNewDomainTrpc.status.value === 'error') {
buttonLoading.value = false;
// show any additional errors/toast messages
} else {
// continue
}
we should switch to using `useMutation` as per above examples across the app and implement form status/button resets where needed
this will ensure consistent dx and give access to the additional keys for future use
Once #79 merges, we will need to add TOTP based 2fa
We are now using Lucia Auth, and its sister package Oslo
Oslo provides helpers to work with TOTP: https://oslo.js.org/reference/otp/
This should be set up in the new Personal settings > security
page as per: #80
when adding a new domain, we use the createNewDomain
procedure in apps/web-app/server/trpc/routers/orgRouter/mail/domainsRouter.ts
this validates the domain actually exists by trying to resolve the name servers for the input domain name.
it is assumed that every single registered domain has nameservers set.
await dns.promises.resolveNs(domainName).catch(() => {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Domain does not exist or is not registered'
});
});
subdomains do not have their own nameservers and this the error is thrown
fix:
ideally, if the input domain contains more than one .
we would split it at the first occurring .
and keep going till theres only 1 .
left. if still no results, then throw the error
potential issues:
this could throw some false positives when the input is domain.co.uk
(a valid domain) since its also possible to register domain.uk
and co.uk
has some name servers
mitigation:
domains will not be available for sending till dns is verified anyway. and 3 days after adding and not being verified, domains are disabled
thanks @daallgeier for highlighting this issue
Problem
Currently, the only way to use UnInbox is by Passkey Auth. While passkeys are a super nice technology and are really secure they're not that well known (yet) and this might hinder people from using UnInbox and that should never be a blocker.
Constraints:
Proposed Solution
Implement a One Time Code login method. When the user wants to sign-up / login they will need to enter an email address.
They will receive an email containing a code containing 6 characters (number/uppercase letters). They can then enter this code in combination with their email to login or signup.
Technical Details
to view the page in action, you need to add a domain.
This page uses a custom accordion that was created before we switched to NuxtUI library
NuxtUI has a nice accordion component: https://ui.nuxt.com/elements/accordion#multiple
we will need to replace the current badges with the already existing UnUiBadge component
The Accordion should render the same data that exists now
reach out on discord if you have any questions
File location: apps/landing-page/pages/oss-friends.vue
This page is part of a Open Source projects network
The current page was made quite a while ago and never updated
The design of the page should be updated to use components from NuxtUi: https://ui.nuxt.com/getting-started
Heres some inspirations; https://documenso.com/oss-friends https://www.hanko.io/oss-friends https://infisical.com/infisical-friends
To get the data, we need to query the api located at https://formbricks.com/api/oss-friends
The link to the oss-page should be added to the Footer
component
Finally, we should set the route rule to cache the page with a ttl of 24 hours (swr mode): https://nuxt.com/docs/guide/concepts/rendering#route-rules
query is returning the correct data and the data pushing is also working fine
postal server only refreshes the DNS once every 24 hours
when adding a new domain in web, users can refresh the DNS
if the dns is valid, we enable sending
currently, we dont ask postal to refresh the dns
if the user starts sending before postal has been refreshed it will lead to all outgoing emails to be labled as "via ...."
to fix this, we need to add a new postal puppet to refresh the domain's dns, and trigger it to run each time the web dns is checked
if a user dosnt have a default org (or any org) they will be redirected and get tons of errors
best place to handle this would be in the org redirect: apps/web-app/pages/redirect.vue
we have a "new org" form that would be a good candidate as a destination: /apps/web-app/pages/[orgSlug]/settings/org/new.vue
but we cant redirect here as its under the [orgSlug]
folder and would send back to redirect
the new page would need to factor in the auth middleware to ensure users can navigate to it
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.