feat: IMAP Driver Integration (#12576)

### Added IMAP integration 

This PR adds support for connecting email accounts via IMAP protocol,
allowing users to sync their emails without OAuth.

#### DB Changes:
- Added customConnectionParams and connectionType fields to
ConnectedAccountWorkspaceEntity

#### UI:
- Added settings pages for creating and editing IMAP connections with
proper validation and connection testing.
- Implemented reconnection flows for handling permission issues.

#### Backend:
- Built ImapConnectionModule with corresponding resolver and service for
managing IMAP connections.
- Created MessagingIMAPDriverModule to handle IMAP client operations,
message fetching/parsing, and error handling.

#### Dependencies:
Integrated `imapflow` and `mailparser` libraries with their type
definitions to handle the IMAP protocol communication.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
neo773
2025-06-30 01:02:15 +05:30
committed by GitHub
parent 3c5595e4ff
commit 7c8d362772
80 changed files with 3588 additions and 113 deletions

View File

@ -12,8 +12,9 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { Button } from 'twenty-ui/input';
import { isDefined } from 'twenty-shared/utils';
import { IconArrowBackUp } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
const StyledWrapper = styled.div`
display: flex;
@ -41,6 +42,12 @@ const StyledButtonContainer = styled.div<{ isMobile: boolean }>`
box-sizing: border-box;
`;
const ALLOWED_REPLY_PROVIDERS = [
ConnectedAccountProvider.GOOGLE,
ConnectedAccountProvider.MICROSOFT,
ConnectedAccountProvider.IMAP_SMTP_CALDAV,
];
export const CommandMenuMessageThreadPage = () => {
const setMessageThread = useSetRecoilComponentStateV2(
messageThreadComponentState,
@ -58,6 +65,7 @@ export const CommandMenuMessageThreadPage = () => {
messageChannelLoading,
connectedAccountProvider,
lastMessageExternalId,
connectedAccountConnectionParameters,
} = useEmailThreadInCommandMenu();
useEffect(() => {
@ -83,10 +91,14 @@ export const CommandMenuMessageThreadPage = () => {
return (
connectedAccountHandle &&
connectedAccountProvider &&
ALLOWED_REPLY_PROVIDERS.includes(connectedAccountProvider) &&
(connectedAccountProvider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV ||
isDefined(connectedAccountConnectionParameters?.SMTP)) &&
lastMessage &&
messageThreadExternalId != null
);
}, [
connectedAccountConnectionParameters,
connectedAccountHandle,
connectedAccountProvider,
lastMessage,
@ -108,6 +120,8 @@ export const CommandMenuMessageThreadPage = () => {
url = `https://mail.google.com/mail/?authuser=${connectedAccountHandle}#all/${messageThreadExternalId}`;
window.open(url, '_blank');
break;
case ConnectedAccountProvider.IMAP_SMTP_CALDAV:
throw new Error('Account provider not supported');
case null:
throw new Error('Account provider not provided');
default:

View File

@ -139,6 +139,7 @@ export const useEmailThreadInCommandMenu = () => {
connectedAccount: {
id: true,
provider: true,
connectionParameters: true,
},
},
skip: !lastMessageChannelId,
@ -175,12 +176,16 @@ export const useEmailThreadInCommandMenu = () => {
? messageChannelData[0]?.connectedAccount
: null;
const connectedAccountProvider = connectedAccount?.provider ?? null;
const connectedAccountConnectionParameters =
connectedAccount?.connectionParameters;
return {
thread,
messages: messagesWithSender,
messageThreadExternalId,
connectedAccountHandle,
connectedAccountProvider,
connectedAccountConnectionParameters,
threadLoading: messagesLoading,
messageChannelLoading,
lastMessageExternalId,