n8n-nodes-jmap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # n8n-nodes-jmap
2
+
3
+ Community node for [n8n](https://n8n.io/) to interact with JMAP email servers ([RFC 8620](https://datatracker.ietf.org/doc/html/rfc8620)/[RFC 8621](https://datatracker.ietf.org/doc/html/rfc8621)).
4
+
5
+ Compatible with:
6
+ - [Apache James](https://james.apache.org/)
7
+ - [Twake Mail](https://twake.app/)
8
+ - [Stalwart Mail Server](https://stalw.art/)
9
+ - [Fastmail](https://www.fastmail.com/)
10
+ - Any JMAP-compliant email server
11
+
12
+ ## Installation
13
+
14
+ ### Community Nodes (Recommended)
15
+
16
+ 1. Go to **Settings > Community Nodes**
17
+ 2. Select **Install**
18
+ 3. Enter `n8n-nodes-jmap`
19
+ 4. Agree to the risks and click **Install**
20
+
21
+ ### Manual Installation
22
+
23
+ ```bash
24
+ cd ~/.n8n/nodes
25
+ npm install n8n-nodes-jmap
26
+ ```
27
+
28
+ Then restart n8n.
29
+
30
+ ---
31
+
32
+ ## Authentication
33
+
34
+ This node supports **three authentication methods** to connect to JMAP servers.
35
+
36
+ ### Method 1: Basic Authentication
37
+
38
+ The simplest method using email and password directly.
39
+
40
+ **Configuration:**
41
+ 1. Create a new **JMAP API** credential
42
+ 2. Set **Authentication Method** to `Basic Auth`
43
+ 3. Fill in:
44
+ - **JMAP Server URL**: `https://jmap.example.com/jmap`
45
+ - **Email**: `user@example.com`
46
+ - **Password**: Your password
47
+
48
+ **Use case:** Development, testing, or servers without OAuth2 support.
49
+
50
+ ---
51
+
52
+ ### Method 2: Bearer Token
53
+
54
+ Use a pre-obtained access token (e.g., from an external OAuth2 flow).
55
+
56
+ **Configuration:**
57
+ 1. Create a new **JMAP API** credential
58
+ 2. Set **Authentication Method** to `Bearer Token`
59
+ 3. Fill in:
60
+ - **JMAP Server URL**: `https://jmap.example.com/jmap`
61
+ - **Access Token**: Your JWT or access token
62
+
63
+ **Use case:** Integration with existing authentication systems, tokens obtained via scripts or other workflows.
64
+
65
+ ---
66
+
67
+ ### Method 3: OAuth2 / OIDC with PKCE
68
+
69
+ Full OAuth2 Authorization Code flow with PKCE (Proof Key for Code Exchange). This is the **recommended method for production** as it provides:
70
+ - Automatic token refresh
71
+ - No password storage in n8n
72
+ - Secure SSO integration
73
+
74
+ **Configuration:**
75
+ 1. Create a new **JMAP OAuth2 API** credential
76
+ 2. Fill in:
77
+ - **Client ID**: Your OIDC client ID
78
+ - **Client Secret**: Leave empty for public clients (PKCE)
79
+ - **Authorization URL**: `https://sso.example.com/oauth2/authorize`
80
+ - **Access Token URL**: `https://sso.example.com/oauth2/token`
81
+ - **JMAP Server URL**: `https://jmap.example.com/jmap`
82
+ 3. Click **Connect** to initiate the OAuth2 flow
83
+ 4. Log in with your SSO credentials
84
+
85
+ **Compatible Identity Providers:**
86
+ - [LemonLDAP::NG](https://lemonldap-ng.org/)
87
+ - [Keycloak](https://www.keycloak.org/)
88
+ - [Auth0](https://auth0.com/)
89
+ - Any OAuth2/OIDC compliant provider
90
+
91
+ #### Example: LemonLDAP::NG Configuration
92
+
93
+ ```
94
+ Authorization URL: https://sso.example.com/oauth2/authorize
95
+ Access Token URL: https://sso.example.com/oauth2/token
96
+ Client ID: my-n8n-client
97
+ Scope: openid email profile offline_access
98
+ ```
99
+
100
+ #### Example: Keycloak Configuration
101
+
102
+ ```
103
+ Authorization URL: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth
104
+ Access Token URL: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token
105
+ Client ID: n8n-jmap-client
106
+ Scope: openid email profile offline_access
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Nodes
112
+
113
+ ### JMAP Node
114
+
115
+ Main node for email operations.
116
+
117
+ | Resource | Operation | Description |
118
+ |----------|-----------|-------------|
119
+ | Email | Send | Send a new email |
120
+ | Email | Reply | Reply to an existing email |
121
+ | Email | Get | Retrieve an email by ID |
122
+ | Email | Get Many | List emails (with optional mailbox filter) |
123
+ | Email | Create Draft | Create a draft without sending |
124
+ | Email | Delete | Delete an email |
125
+ | Email | Mark as Read | Mark email as read |
126
+ | Email | Mark as Unread | Mark email as unread |
127
+ | Email | Move | Move email to another mailbox |
128
+ | Email | Add Label | Add a mailbox label to an email |
129
+ | Email | Remove Label | Remove a mailbox label from an email |
130
+ | Email | Get Labels | Get all labels for an email |
131
+ | Mailbox | Get | Get mailbox details by ID |
132
+ | Mailbox | Get Many | List all mailboxes |
133
+ | Thread | Get | Get thread details by ID |
134
+ | Thread | Get Many | Get all emails in a thread |
135
+
136
+ ### JMAP Trigger Node
137
+
138
+ Polling-based trigger for new emails.
139
+
140
+ | Event | Description |
141
+ |-------|-------------|
142
+ | New Email | Triggers on any new email |
143
+ | New Email in Mailbox | Triggers on new email in a specific mailbox |
144
+
145
+ **Options:**
146
+ - **Simple Output**: Return simplified email data
147
+ - **Include Attachments Info**: Include attachment metadata
148
+ - **Mark as Read**: Automatically mark fetched emails as read
149
+
150
+ ---
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ # Install dependencies
156
+ npm install
157
+
158
+ # Build
159
+ npm run build
160
+
161
+ # Development mode (watch)
162
+ npm run dev
163
+
164
+ # Lint
165
+ npm run lint
166
+
167
+ # Format code
168
+ npm run format
169
+ ```
170
+
171
+ ### Local Testing
172
+
173
+ ```bash
174
+ # Link for local n8n development
175
+ npm link
176
+
177
+ # In your n8n installation
178
+ npm link n8n-nodes-jmap
179
+ ```
180
+
181
+ ---
182
+
183
+ ## JMAP Protocol
184
+
185
+ JMAP (JSON Meta Application Protocol) is a modern, efficient alternative to IMAP for email access:
186
+
187
+ - **Stateless**: No persistent connections required
188
+ - **JSON-based**: Easy to parse and debug
189
+ - **Efficient**: Batched requests, delta sync
190
+ - **Standardized**: RFC 8620 (Core) and RFC 8621 (Mail)
191
+
192
+ Learn more: [jmap.io](https://jmap.io/)
193
+
194
+ ---
195
+
196
+ ## License
197
+
198
+ AGPL-3.0
199
+
200
+ ---
201
+
202
+ ## Author
203
+
204
+ [Michel-Marie Maudet](https://github.com/mmaudet)
@@ -0,0 +1,13 @@
1
+ import { ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ /**
3
+ * JMAP API Credentials for Basic Auth or Bearer Token authentication
4
+ *
5
+ * For OAuth2/OIDC with LemonLDAP, use the JmapOAuth2Api credentials instead.
6
+ */
7
+ export declare class JmapApi implements ICredentialType {
8
+ name: string;
9
+ displayName: string;
10
+ documentationUrl: string;
11
+ properties: INodeProperties[];
12
+ test: ICredentialTestRequest;
13
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JmapApi = void 0;
4
+ /**
5
+ * JMAP API Credentials for Basic Auth or Bearer Token authentication
6
+ *
7
+ * For OAuth2/OIDC with LemonLDAP, use the JmapOAuth2Api credentials instead.
8
+ */
9
+ class JmapApi {
10
+ constructor() {
11
+ this.name = 'jmapApi';
12
+ this.displayName = 'JMAP API';
13
+ this.documentationUrl = 'https://jmap.io/spec-core.html';
14
+ this.properties = [
15
+ {
16
+ displayName: 'JMAP Server URL',
17
+ name: 'serverUrl',
18
+ type: 'string',
19
+ default: 'https://jmap.example.com/jmap',
20
+ placeholder: 'https://jmap.example.com/jmap',
21
+ description: 'The base URL of the JMAP server',
22
+ required: true,
23
+ },
24
+ {
25
+ displayName: 'Authentication Method',
26
+ name: 'authMethod',
27
+ type: 'options',
28
+ options: [
29
+ {
30
+ name: 'Basic Auth',
31
+ value: 'basicAuth',
32
+ description: 'Authenticate with email and password',
33
+ },
34
+ {
35
+ name: 'Bearer Token',
36
+ value: 'bearerToken',
37
+ description: 'Authenticate with an existing access token',
38
+ },
39
+ ],
40
+ default: 'basicAuth',
41
+ },
42
+ // Basic Auth fields
43
+ {
44
+ displayName: 'Email',
45
+ name: 'email',
46
+ type: 'string',
47
+ placeholder: 'user@example.com',
48
+ default: '',
49
+ required: true,
50
+ displayOptions: {
51
+ show: {
52
+ authMethod: ['basicAuth'],
53
+ },
54
+ },
55
+ },
56
+ {
57
+ displayName: 'Password',
58
+ name: 'password',
59
+ type: 'string',
60
+ typeOptions: {
61
+ password: true,
62
+ },
63
+ default: '',
64
+ required: true,
65
+ displayOptions: {
66
+ show: {
67
+ authMethod: ['basicAuth'],
68
+ },
69
+ },
70
+ },
71
+ // Bearer Token field
72
+ {
73
+ displayName: 'Access Token',
74
+ name: 'accessToken',
75
+ type: 'string',
76
+ typeOptions: {
77
+ password: true,
78
+ },
79
+ default: '',
80
+ required: true,
81
+ description: 'The access token (e.g., from OAuth2/OIDC)',
82
+ displayOptions: {
83
+ show: {
84
+ authMethod: ['bearerToken'],
85
+ },
86
+ },
87
+ },
88
+ ];
89
+ this.test = {
90
+ request: {
91
+ baseURL: '={{$credentials.serverUrl}}',
92
+ url: '/session',
93
+ method: 'GET',
94
+ headers: {
95
+ Accept: 'application/json',
96
+ },
97
+ auth: {
98
+ username: '={{$credentials.email}}',
99
+ password: '={{$credentials.password}}',
100
+ },
101
+ },
102
+ };
103
+ }
104
+ }
105
+ exports.JmapApi = JmapApi;
@@ -0,0 +1,27 @@
1
+ import type { ICredentialType, INodeProperties, Icon } from 'n8n-workflow';
2
+ /**
3
+ * JMAP OAuth2 API Credentials for LemonLDAP::NG SSO
4
+ *
5
+ * Uses Authorization Code Flow with PKCE (public client, no secret required)
6
+ */
7
+ export declare class JmapOAuth2Api implements ICredentialType {
8
+ name: string;
9
+ displayName: string;
10
+ documentationUrl: string;
11
+ extends: string[];
12
+ icon: Icon;
13
+ properties: INodeProperties[];
14
+ oauth2: {
15
+ tokenExpiredStatusCode: number;
16
+ grantType: string;
17
+ scope: string;
18
+ tokenRequestMethod: "POST";
19
+ authorizeUrl: {
20
+ url: string;
21
+ };
22
+ accessTokenUrl: {
23
+ url: string;
24
+ };
25
+ clientAuthentication: "body";
26
+ };
27
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JmapOAuth2Api = void 0;
4
+ /**
5
+ * JMAP OAuth2 API Credentials for LemonLDAP::NG SSO
6
+ *
7
+ * Uses Authorization Code Flow with PKCE (public client, no secret required)
8
+ */
9
+ class JmapOAuth2Api {
10
+ constructor() {
11
+ this.name = 'jmapOAuth2Api';
12
+ this.displayName = 'JMAP OAuth2 API';
13
+ this.documentationUrl = 'https://jmap.io/spec-core.html';
14
+ this.extends = ['oAuth2Api'];
15
+ this.icon = {
16
+ light: 'file:../nodes/Jmap/jmap.svg',
17
+ dark: 'file:../nodes/Jmap/jmap.svg',
18
+ };
19
+ this.properties = [
20
+ {
21
+ displayName: 'Client ID',
22
+ name: 'clientId',
23
+ type: 'string',
24
+ default: '',
25
+ required: true,
26
+ placeholder: 'my-client-id',
27
+ hint: 'OIDC Client ID registered with your identity provider',
28
+ },
29
+ {
30
+ displayName: 'Client Secret',
31
+ name: 'clientSecret',
32
+ type: 'string',
33
+ typeOptions: { password: true },
34
+ default: '',
35
+ required: false,
36
+ hint: 'Leave empty for public clients using PKCE',
37
+ },
38
+ {
39
+ displayName: 'Authorization URL',
40
+ name: 'authUrl',
41
+ type: 'string',
42
+ default: '',
43
+ required: true,
44
+ placeholder: 'https://sso.example.com/oauth2/authorize',
45
+ hint: 'OAuth2/OIDC authorization endpoint',
46
+ },
47
+ {
48
+ displayName: 'Access Token URL',
49
+ name: 'accessTokenUrl',
50
+ type: 'string',
51
+ default: '',
52
+ required: true,
53
+ placeholder: 'https://sso.example.com/oauth2/token',
54
+ hint: 'OAuth2/OIDC token endpoint',
55
+ },
56
+ {
57
+ displayName: 'JMAP Server URL',
58
+ name: 'jmapServerUrl',
59
+ type: 'string',
60
+ default: '',
61
+ required: true,
62
+ placeholder: 'https://jmap.example.com/jmap',
63
+ hint: 'The JMAP API endpoint URL',
64
+ },
65
+ // OAuth2 configuration - hidden fields
66
+ {
67
+ displayName: 'Scope',
68
+ name: 'scope',
69
+ type: 'hidden',
70
+ default: 'openid email profile offline_access',
71
+ },
72
+ {
73
+ displayName: 'Auth URI Query Parameters',
74
+ name: 'authQueryParameters',
75
+ type: 'hidden',
76
+ default: '',
77
+ },
78
+ {
79
+ displayName: 'Grant Type',
80
+ name: 'grantType',
81
+ type: 'hidden',
82
+ default: 'pkce',
83
+ },
84
+ {
85
+ displayName: 'Authentication',
86
+ name: 'authentication',
87
+ type: 'hidden',
88
+ default: 'body',
89
+ },
90
+ ];
91
+ // OAuth2 flow configuration
92
+ this.oauth2 = {
93
+ tokenExpiredStatusCode: 401,
94
+ grantType: 'pkce',
95
+ scope: 'openid email profile offline_access',
96
+ tokenRequestMethod: 'POST',
97
+ authorizeUrl: { url: '={{ $credentials.authUrl }}' },
98
+ accessTokenUrl: { url: '={{ $credentials.accessTokenUrl }}' },
99
+ clientAuthentication: 'body',
100
+ };
101
+ }
102
+ }
103
+ exports.JmapOAuth2Api = JmapOAuth2Api;
@@ -0,0 +1,121 @@
1
+ import type { IExecuteFunctions, ILoadOptionsFunctions, IPollFunctions, IDataObject } from 'n8n-workflow';
2
+ export interface IJmapSession {
3
+ accounts: {
4
+ [key: string]: IJmapAccount;
5
+ };
6
+ primaryAccounts: {
7
+ [key: string]: string;
8
+ };
9
+ username: string;
10
+ apiUrl: string;
11
+ downloadUrl: string;
12
+ uploadUrl: string;
13
+ eventSourceUrl: string;
14
+ state: string;
15
+ capabilities: {
16
+ [key: string]: IDataObject;
17
+ };
18
+ }
19
+ export interface IJmapAccount {
20
+ name: string;
21
+ isPersonal: boolean;
22
+ isReadOnly: boolean;
23
+ accountCapabilities: {
24
+ [key: string]: IDataObject;
25
+ };
26
+ }
27
+ export interface IJmapRequest {
28
+ using: string[];
29
+ methodCalls: [string, IDataObject, string][];
30
+ }
31
+ export interface IJmapResponse {
32
+ methodResponses: [string, IDataObject, string][];
33
+ sessionState: string;
34
+ }
35
+ export declare const JMAP_CAPABILITIES: {
36
+ CORE: string;
37
+ MAIL: string;
38
+ SUBMISSION: string;
39
+ VACATION_RESPONSE: string;
40
+ JAMES_SHARES: string;
41
+ JAMES_QUOTA: string;
42
+ };
43
+ /**
44
+ * Get JMAP session from the server
45
+ */
46
+ export declare function getJmapSession(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions): Promise<IJmapSession>;
47
+ /**
48
+ * Make a JMAP API request
49
+ */
50
+ export declare function jmapApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, methodCalls: [string, IDataObject, string][], using?: string[]): Promise<IJmapResponse>;
51
+ /**
52
+ * Get the primary account ID for mail
53
+ */
54
+ export declare function getPrimaryAccountId(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions): Promise<string>;
55
+ /**
56
+ * Get all mailboxes for an account
57
+ */
58
+ export declare function getMailboxes(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string): Promise<IDataObject[]>;
59
+ /**
60
+ * Find a mailbox by name
61
+ */
62
+ export declare function findMailboxByName(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string, name: string): Promise<IDataObject | undefined>;
63
+ /**
64
+ * Find a mailbox by role
65
+ */
66
+ export declare function findMailboxByRole(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string, role: string): Promise<IDataObject | undefined>;
67
+ /**
68
+ * Query emails with filters
69
+ */
70
+ export declare function queryEmails(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string, filter?: IDataObject, sort?: IDataObject[], limit?: number, position?: number): Promise<{
71
+ ids: string[];
72
+ total: number;
73
+ }>;
74
+ /**
75
+ * Get emails by IDs
76
+ */
77
+ export declare function getEmails(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string, ids: string[], properties?: string[], fetchTextBodyValues?: boolean, fetchHTMLBodyValues?: boolean): Promise<IDataObject[]>;
78
+ /**
79
+ * Create and send an email
80
+ */
81
+ export declare function sendEmail(this: IExecuteFunctions, accountId: string, email: IDataObject, identityId: string): Promise<IDataObject>;
82
+ /**
83
+ * Create a draft email
84
+ */
85
+ export declare function createDraft(this: IExecuteFunctions, accountId: string, email: IDataObject): Promise<IDataObject>;
86
+ /**
87
+ * Get identities
88
+ */
89
+ export declare function getIdentities(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string): Promise<IDataObject[]>;
90
+ /**
91
+ * Update email keywords
92
+ */
93
+ export declare function updateEmailKeywords(this: IExecuteFunctions, accountId: string, emailId: string, keywords: IDataObject): Promise<IDataObject>;
94
+ /**
95
+ * Move email to a different mailbox
96
+ */
97
+ export declare function moveEmail(this: IExecuteFunctions, accountId: string, emailId: string, targetMailboxId: string): Promise<IDataObject>;
98
+ /**
99
+ * Add a label (mailbox) to an email
100
+ */
101
+ export declare function addLabel(this: IExecuteFunctions, accountId: string, emailId: string, mailboxId: string): Promise<IDataObject>;
102
+ /**
103
+ * Remove a label (mailbox) from an email
104
+ */
105
+ export declare function removeLabel(this: IExecuteFunctions, accountId: string, emailId: string, mailboxId: string): Promise<IDataObject>;
106
+ /**
107
+ * Get labels (mailboxes) for an email with their names
108
+ */
109
+ export declare function getLabels(this: IExecuteFunctions, accountId: string, emailId: string): Promise<IDataObject[]>;
110
+ /**
111
+ * Delete emails
112
+ */
113
+ export declare function deleteEmails(this: IExecuteFunctions, accountId: string, emailIds: string[]): Promise<IDataObject>;
114
+ /**
115
+ * Get threads
116
+ */
117
+ export declare function getThreads(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, accountId: string, ids: string[]): Promise<IDataObject[]>;
118
+ /**
119
+ * Download an attachment blob
120
+ */
121
+ export declare function downloadBlob(this: IExecuteFunctions, accountId: string, blobId: string, name: string, type: string): Promise<Buffer>;