@invoiceleaf/integration-smtp-mail 1.0.1

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,21 @@
1
+ # SMTP Mail Integration
2
+
3
+ SMTP + IMAP integration for InvoiceLeaf.
4
+
5
+ ## Features
6
+
7
+ - Action: Send email through SMTP
8
+ - Scheduled trigger: Crawl IMAP mailbox for PDF attachments
9
+ - Automatic import of discovered PDFs into InvoiceLeaf
10
+ - Dedupe based on message UID + attachment checksum
11
+
12
+ ## Notes
13
+
14
+ - SMTP is used for outbound sending.
15
+ - IMAP is used for mailbox crawling.
16
+ - This integration requires runtime support for:
17
+ - `context.data.importDocument(...)`
18
+ - `context.state.get/set/delete(...)`
19
+ - `context.email.sendSmtpEmail(...)`
20
+ - `context.email.testSmtpImapConnection(...)`
21
+ - `context.email.crawlImapPdfAttachments(...)`
@@ -0,0 +1,4 @@
1
+ import type { IntegrationHandler, ScheduleInput } from '@invoiceleaf/integration-sdk';
2
+ import type { CrawlResult, SmtpMailConfig } from '../types.js';
3
+ export declare const crawlPdfAttachments: IntegrationHandler<ScheduleInput, CrawlResult, SmtpMailConfig>;
4
+ //# sourceMappingURL=crawlPdfAttachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawlPdfAttachments.d.ts","sourceRoot":"","sources":["../../src/handlers/crawlPdfAttachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,kBAAkB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC1G,OAAO,KAAK,EAAE,WAAW,EAAwB,cAAc,EAAE,MAAM,aAAa,CAAC;AA2DrF,eAAO,MAAM,mBAAmB,EAAE,kBAAkB,CAAC,aAAa,EAAE,WAAW,EAAE,cAAc,CAuE9F,CAAC"}
@@ -0,0 +1,110 @@
1
+ import { buildAttachmentStateKey } from '../utils/dedupe.js';
2
+ function getPrefix(config) {
3
+ return config.stateKeyPrefix || 'smtp-mail';
4
+ }
5
+ function getDedupeTtlSeconds(config) {
6
+ return config.dedupeTtlSeconds && config.dedupeTtlSeconds > 0
7
+ ? config.dedupeTtlSeconds
8
+ : 90 * 24 * 60 * 60;
9
+ }
10
+ function getImportSource(config) {
11
+ return config.importSource || 'smtp-mail';
12
+ }
13
+ async function importAttachment(context, attachment, stateKey, ttlSeconds) {
14
+ const existing = await context.state.get(stateKey);
15
+ if (existing) {
16
+ return 'duplicate';
17
+ }
18
+ try {
19
+ const result = await context.data.importDocument({
20
+ fileName: attachment.fileName,
21
+ contentType: attachment.contentType,
22
+ contentBase64: attachment.contentBase64,
23
+ source: getImportSource(context.config),
24
+ externalRef: `${attachment.uid}:${attachment.fileName}:${attachment.checksum}`,
25
+ metadata: {
26
+ uid: attachment.uid,
27
+ subject: attachment.subject,
28
+ from: attachment.from,
29
+ date: attachment.date,
30
+ },
31
+ });
32
+ if (result.duplicate) {
33
+ return 'duplicate';
34
+ }
35
+ await context.state.set(stateKey, result.documentId, { ttlSeconds });
36
+ return 'imported';
37
+ }
38
+ catch (error) {
39
+ context.logger.error('Attachment import failed', {
40
+ uid: attachment.uid,
41
+ fileName: attachment.fileName,
42
+ error: error.message,
43
+ });
44
+ return 'failed';
45
+ }
46
+ }
47
+ export const crawlPdfAttachments = async (input, context) => {
48
+ const result = {
49
+ success: true,
50
+ scannedMessages: 0,
51
+ scannedAttachments: 0,
52
+ imported: 0,
53
+ duplicates: 0,
54
+ skipped: 0,
55
+ failed: 0,
56
+ };
57
+ const maxMessages = context.config.maxMessagesPerRun || 100;
58
+ const maxAttachments = context.config.maxAttachmentsPerMessage || 10;
59
+ const statePrefix = getPrefix(context.config);
60
+ const dedupeTtlSeconds = getDedupeTtlSeconds(context.config);
61
+ try {
62
+ context.logger.info('Starting SMTP crawl run', {
63
+ scheduledTime: input.scheduledTime,
64
+ maxMessages,
65
+ maxAttachments,
66
+ });
67
+ const crawl = await context.email.crawlImapPdfAttachments({
68
+ imapHost: context.config.imapHost,
69
+ imapPort: context.config.imapPort,
70
+ imapSecure: context.config.imapSecure,
71
+ imapUsername: context.config.imapUsername,
72
+ imapPassword: context.config.imapPassword,
73
+ imapFolder: context.config.imapFolder || 'INBOX',
74
+ searchFilter: context.config.searchFilter,
75
+ maxMessagesPerRun: maxMessages,
76
+ maxAttachmentsPerMessage: maxAttachments,
77
+ markAsSeen: true,
78
+ moveToFolder: context.config.processedFolder,
79
+ });
80
+ result.scannedMessages = crawl.messages;
81
+ result.scannedAttachments = crawl.attachments;
82
+ if (crawl.items.length === 0) {
83
+ result.skipped = crawl.messages;
84
+ }
85
+ for (const attachment of crawl.items) {
86
+ const stateKey = buildAttachmentStateKey(statePrefix, attachment);
87
+ const status = await importAttachment(context, attachment, stateKey, dedupeTtlSeconds);
88
+ if (status === 'imported') {
89
+ result.imported += 1;
90
+ }
91
+ else if (status === 'duplicate') {
92
+ result.duplicates += 1;
93
+ }
94
+ else {
95
+ result.failed += 1;
96
+ }
97
+ await context.state.set(`${statePrefix}:lastProcessedUid`, attachment.uid, {
98
+ ttlSeconds: dedupeTtlSeconds,
99
+ });
100
+ }
101
+ }
102
+ catch (error) {
103
+ result.success = false;
104
+ result.error = `Crawl failed: ${error.message}`;
105
+ context.logger.error('SMTP crawl failed', { error: error.message });
106
+ }
107
+ result.message = `Scanned ${result.scannedMessages} messages, imported ${result.imported} PDFs`;
108
+ return result;
109
+ };
110
+ //# sourceMappingURL=crawlPdfAttachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawlPdfAttachments.js","sourceRoot":"","sources":["../../src/handlers/crawlPdfAttachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { sendEmail } from './sendEmail.js';
2
+ export { testConnection } from './testConnection.js';
3
+ export { crawlPdfAttachments } from './crawlPdfAttachments.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { sendEmail } from './sendEmail.js';
2
+ export { testConnection } from './testConnection.js';
3
+ export { crawlPdfAttachments } from './crawlPdfAttachments.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { IntegrationHandler } from '@invoiceleaf/integration-sdk';
2
+ import type { HandlerResult, SendEmailInput, SmtpMailConfig } from '../types.js';
3
+ export declare const sendEmail: IntegrationHandler<SendEmailInput, HandlerResult, SmtpMailConfig>;
4
+ //# sourceMappingURL=sendEmail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendEmail.d.ts","sourceRoot":"","sources":["../../src/handlers/sendEmail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGjF,eAAO,MAAM,SAAS,EAAE,kBAAkB,CAAC,cAAc,EAAE,aAAa,EAAE,cAAc,CA2CvF,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { toAddressList } from '../utils/dedupe.js';
2
+ export const sendEmail = async (input, context) => {
3
+ try {
4
+ if (!input.subject || (!input.text && !input.html)) {
5
+ return {
6
+ success: false,
7
+ error: 'subject and at least one body field (text or html) are required',
8
+ };
9
+ }
10
+ const result = await context.email.sendSmtpEmail({
11
+ smtpHost: context.config.smtpHost,
12
+ smtpPort: context.config.smtpPort,
13
+ smtpSecure: context.config.smtpSecure,
14
+ smtpUsername: context.config.smtpUsername,
15
+ smtpPassword: context.config.smtpPassword,
16
+ fromAddress: context.config.fromAddress,
17
+ to: toAddressList(input.to),
18
+ cc: toAddressList(input.cc),
19
+ bcc: toAddressList(input.bcc),
20
+ replyTo: input.replyTo,
21
+ subject: input.subject,
22
+ text: input.text,
23
+ html: input.html,
24
+ attachments: input.attachments,
25
+ });
26
+ context.logger.info('SMTP sendEmail succeeded', { messageId: result.messageId });
27
+ return {
28
+ success: true,
29
+ message: 'Email sent successfully',
30
+ details: { messageId: result.messageId },
31
+ };
32
+ }
33
+ catch (error) {
34
+ context.logger.error('SMTP sendEmail failed', { error: error.message });
35
+ return {
36
+ success: false,
37
+ error: `Failed to send email: ${error.message}`,
38
+ };
39
+ }
40
+ };
41
+ //# sourceMappingURL=sendEmail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendEmail.js","sourceRoot":"","sources":["../../src/handlers/sendEmail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,CAAC,MAAM,SAAS,GAAsE,KAAK,EAC/F,KAAK,EACL,OAA2C,EACnB,EAAE;IAC1B,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,iEAAiE;aACzE,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC;YAC/C,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;YACrC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;YACzC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;YACzC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW;YACvC,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC;YAC7B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;QAEjF,OAAO;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,yBAAyB;YAClC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;SACzC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACnF,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,yBAA0B,KAAe,CAAC,OAAO,EAAE;SAC3D,CAAC;IACJ,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { IntegrationHandler } from '@invoiceleaf/integration-sdk';
2
+ import type { HandlerResult, SmtpMailConfig } from '../types.js';
3
+ export declare const testConnection: IntegrationHandler<unknown, HandlerResult, SmtpMailConfig>;
4
+ //# sourceMappingURL=testConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testConnection.d.ts","sourceRoot":"","sources":["../../src/handlers/testConnection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEjE,eAAO,MAAM,cAAc,EAAE,kBAAkB,CAAC,OAAO,EAAE,aAAa,EAAE,cAAc,CA8BrF,CAAC"}
@@ -0,0 +1,29 @@
1
+ export const testConnection = async (_input, context) => {
2
+ try {
3
+ await context.email.testSmtpImapConnection({
4
+ smtpHost: context.config.smtpHost,
5
+ smtpPort: context.config.smtpPort,
6
+ smtpSecure: context.config.smtpSecure,
7
+ smtpUsername: context.config.smtpUsername,
8
+ smtpPassword: context.config.smtpPassword,
9
+ imapHost: context.config.imapHost,
10
+ imapPort: context.config.imapPort,
11
+ imapSecure: context.config.imapSecure,
12
+ imapUsername: context.config.imapUsername,
13
+ imapPassword: context.config.imapPassword,
14
+ imapFolder: context.config.imapFolder || 'INBOX',
15
+ });
16
+ return {
17
+ success: true,
18
+ message: 'SMTP and IMAP connections are valid',
19
+ };
20
+ }
21
+ catch (error) {
22
+ context.logger.error('Connection test failed', { error: error.message });
23
+ return {
24
+ success: false,
25
+ error: `Connection test failed: ${error.message}`,
26
+ };
27
+ }
28
+ };
29
+ //# sourceMappingURL=testConnection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testConnection.js","sourceRoot":"","sources":["../../src/handlers/testConnection.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,cAAc,GAA+D,KAAK,EAC7F,MAAM,EACN,OAA2C,EACnB,EAAE;IAC1B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC;YACzC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;YACrC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;YACzC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;YACzC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ;YACjC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;YACrC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;YACzC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;YACzC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,OAAO;SACjD,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,qCAAqC;SAC/C,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACpF,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,2BAA4B,KAAe,CAAC,OAAO,EAAE;SAC7D,CAAC;IACJ,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SMTP Mail integration handler exports.
3
+ * Manifest is defined in manifest.json at package root.
4
+ */
5
+ export { sendEmail, testConnection, crawlPdfAttachments } from './handlers/index.js';
6
+ export type { SmtpMailConfig, SendEmailInput, SendEmailAttachmentInput, HandlerResult, CrawlResult, } from './types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAErF,YAAY,EACV,cAAc,EACd,cAAc,EACd,wBAAwB,EACxB,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * SMTP Mail integration handler exports.
3
+ * Manifest is defined in manifest.json at package root.
4
+ */
5
+ export { sendEmail, testConnection, crawlPdfAttachments } from './handlers/index.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,61 @@
1
+ export interface SmtpMailConfig {
2
+ smtpHost: string;
3
+ smtpPort: number;
4
+ smtpSecure?: boolean;
5
+ smtpUsername: string;
6
+ smtpPassword: string;
7
+ fromAddress: string;
8
+ imapHost: string;
9
+ imapPort: number;
10
+ imapSecure?: boolean;
11
+ imapUsername: string;
12
+ imapPassword: string;
13
+ imapFolder?: string;
14
+ processedFolder?: string;
15
+ searchFilter?: string;
16
+ maxMessagesPerRun?: number;
17
+ maxAttachmentsPerMessage?: number;
18
+ stateKeyPrefix?: string;
19
+ dedupeTtlSeconds?: number;
20
+ importSource?: string;
21
+ }
22
+ export interface SendEmailAttachmentInput {
23
+ fileName: string;
24
+ contentType?: string;
25
+ contentBase64: string;
26
+ }
27
+ export interface SendEmailInput {
28
+ to: string | string[];
29
+ cc?: string | string[];
30
+ bcc?: string | string[];
31
+ subject: string;
32
+ text?: string;
33
+ html?: string;
34
+ replyTo?: string;
35
+ attachments?: SendEmailAttachmentInput[];
36
+ }
37
+ export interface HandlerResult {
38
+ success: boolean;
39
+ message?: string;
40
+ error?: string;
41
+ details?: Record<string, unknown>;
42
+ }
43
+ export interface CrawledPdfAttachment {
44
+ uid: number;
45
+ fileName: string;
46
+ contentType: string;
47
+ contentBase64: string;
48
+ checksum: string;
49
+ subject?: string;
50
+ from?: string;
51
+ date?: string;
52
+ }
53
+ export interface CrawlResult extends HandlerResult {
54
+ scannedMessages: number;
55
+ scannedAttachments: number;
56
+ imported: number;
57
+ duplicates: number;
58
+ skipped: number;
59
+ failed: number;
60
+ }
61
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IAEpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAElC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { CrawledPdfAttachment } from '../types.js';
2
+ export declare function toAddressList(value?: string | string[]): string[];
3
+ export declare function buildAttachmentStateKey(prefix: string, attachment: CrawledPdfAttachment): string;
4
+ //# sourceMappingURL=dedupe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../../src/utils/dedupe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAKjE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB,GAAG,MAAM,CAEhG"}
@@ -0,0 +1,10 @@
1
+ export function toAddressList(value) {
2
+ if (!value) {
3
+ return [];
4
+ }
5
+ return Array.isArray(value) ? value : [value];
6
+ }
7
+ export function buildAttachmentStateKey(prefix, attachment) {
8
+ return `${prefix}:att:${attachment.uid}:${attachment.fileName}:${attachment.checksum}`;
9
+ }
10
+ //# sourceMappingURL=dedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../src/utils/dedupe.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAc,EAAE,UAAgC;IACtF,OAAO,GAAG,MAAM,QAAQ,UAAU,CAAC,GAAG,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;AACzF,CAAC"}
package/manifest.json ADDED
@@ -0,0 +1,182 @@
1
+ {
2
+ "id": "integration-smtp-mail",
3
+ "name": "SMTP Mail",
4
+ "description": "Send email through SMTP and crawl IMAP inboxes for PDF attachments to import into InvoiceLeaf.",
5
+ "version": "1.0.0",
6
+ "author": {
7
+ "name": "InvoiceLeaf",
8
+ "email": "support@invoiceleaf.com",
9
+ "url": "https://invoiceleaf.com"
10
+ },
11
+ "icon": "mail",
12
+ "category": "communication",
13
+ "tags": ["smtp", "imap", "email", "inbox", "attachments"],
14
+ "dataAccess": ["documents"],
15
+ "externalAuth": [],
16
+ "triggers": [
17
+ {
18
+ "id": "crawl-email-pdf-attachments",
19
+ "type": "schedule",
20
+ "name": "Crawl Email PDF Attachments",
21
+ "description": "Checks IMAP mailbox for PDF attachments and imports them into InvoiceLeaf",
22
+ "schedule": "*/15 * * * *",
23
+ "handler": "crawlPdfAttachments",
24
+ "configurable": true
25
+ }
26
+ ],
27
+ "actions": [
28
+ {
29
+ "id": "send-email",
30
+ "name": "Send Email",
31
+ "description": "Send an email through SMTP",
32
+ "handler": "sendEmail",
33
+ "icon": "send"
34
+ },
35
+ {
36
+ "id": "test-connection",
37
+ "name": "Test SMTP/IMAP Connection",
38
+ "description": "Verify SMTP and IMAP credentials",
39
+ "handler": "testConnection",
40
+ "icon": "check"
41
+ }
42
+ ],
43
+ "configSchema": {
44
+ "type": "object",
45
+ "properties": {
46
+ "smtpHost": {
47
+ "type": "string",
48
+ "title": "SMTP Host",
49
+ "description": "SMTP server hostname (for example, smtp.office365.com)"
50
+ },
51
+ "smtpPort": {
52
+ "type": "number",
53
+ "title": "SMTP Port",
54
+ "default": 587,
55
+ "minimum": 1,
56
+ "maximum": 65535
57
+ },
58
+ "smtpSecure": {
59
+ "type": "boolean",
60
+ "title": "SMTP Secure (TLS)",
61
+ "description": "Use secure SMTP transport (usually true for port 465)",
62
+ "default": false
63
+ },
64
+ "smtpUsername": {
65
+ "type": "string",
66
+ "title": "SMTP Username"
67
+ },
68
+ "smtpPassword": {
69
+ "type": "string",
70
+ "title": "SMTP Password",
71
+ "format": "password"
72
+ },
73
+ "fromAddress": {
74
+ "type": "string",
75
+ "title": "From Address",
76
+ "format": "email"
77
+ },
78
+ "imapHost": {
79
+ "type": "string",
80
+ "title": "IMAP Host",
81
+ "description": "IMAP server hostname (for example, outlook.office365.com)"
82
+ },
83
+ "imapPort": {
84
+ "type": "number",
85
+ "title": "IMAP Port",
86
+ "default": 993,
87
+ "minimum": 1,
88
+ "maximum": 65535
89
+ },
90
+ "imapSecure": {
91
+ "type": "boolean",
92
+ "title": "IMAP Secure (TLS)",
93
+ "default": true
94
+ },
95
+ "imapUsername": {
96
+ "type": "string",
97
+ "title": "IMAP Username"
98
+ },
99
+ "imapPassword": {
100
+ "type": "string",
101
+ "title": "IMAP Password",
102
+ "format": "password"
103
+ },
104
+ "imapFolder": {
105
+ "type": "string",
106
+ "title": "IMAP Folder",
107
+ "default": "INBOX"
108
+ },
109
+ "processedFolder": {
110
+ "type": "string",
111
+ "title": "Processed Folder",
112
+ "description": "Optional destination folder after successful import"
113
+ },
114
+ "searchFilter": {
115
+ "type": "string",
116
+ "title": "Search Filter",
117
+ "description": "Supported values: UNSEEN, SEEN, ALL",
118
+ "default": "UNSEEN"
119
+ },
120
+ "maxMessagesPerRun": {
121
+ "type": "number",
122
+ "title": "Max Messages Per Run",
123
+ "default": 100,
124
+ "minimum": 1,
125
+ "maximum": 500
126
+ },
127
+ "maxAttachmentsPerMessage": {
128
+ "type": "number",
129
+ "title": "Max Attachments Per Message",
130
+ "default": 10,
131
+ "minimum": 1,
132
+ "maximum": 50
133
+ },
134
+ "stateKeyPrefix": {
135
+ "type": "string",
136
+ "title": "State Key Prefix",
137
+ "default": "smtp-mail"
138
+ },
139
+ "dedupeTtlSeconds": {
140
+ "type": "number",
141
+ "title": "Dedupe TTL (seconds)",
142
+ "default": 7776000,
143
+ "minimum": 3600
144
+ },
145
+ "importSource": {
146
+ "type": "string",
147
+ "title": "Import Source",
148
+ "default": "smtp-mail"
149
+ }
150
+ },
151
+ "required": [
152
+ "smtpHost",
153
+ "smtpPort",
154
+ "smtpUsername",
155
+ "smtpPassword",
156
+ "fromAddress",
157
+ "imapHost",
158
+ "imapPort",
159
+ "imapUsername",
160
+ "imapPassword"
161
+ ]
162
+ },
163
+ "ui": {
164
+ "configGroups": [
165
+ {
166
+ "title": "SMTP",
167
+ "description": "Configure outbound email delivery",
168
+ "fields": ["smtpHost", "smtpPort", "smtpSecure", "smtpUsername", "smtpPassword", "fromAddress"]
169
+ },
170
+ {
171
+ "title": "IMAP",
172
+ "description": "Configure inbox crawling",
173
+ "fields": ["imapHost", "imapPort", "imapSecure", "imapUsername", "imapPassword", "imapFolder", "processedFolder", "searchFilter"]
174
+ },
175
+ {
176
+ "title": "Crawler Limits",
177
+ "description": "Control per-run workload and dedupe",
178
+ "fields": ["maxMessagesPerRun", "maxAttachmentsPerMessage", "stateKeyPrefix", "dedupeTtlSeconds", "importSource"]
179
+ }
180
+ ]
181
+ }
182
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@invoiceleaf/integration-smtp-mail",
3
+ "version": "1.0.1",
4
+ "description": "SMTP + IMAP integration for InvoiceLeaf: send emails and crawl PDF attachments for import",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc --watch",
11
+ "clean": "rm -rf dist",
12
+ "prepublishOnly": "npm run clean && npm run build",
13
+ "typecheck": "tsc --noEmit"
14
+ },
15
+ "keywords": [
16
+ "invoiceleaf",
17
+ "integration",
18
+ "smtp",
19
+ "imap",
20
+ "email"
21
+ ],
22
+ "author": "InvoiceLeaf <support@invoiceleaf.com>",
23
+ "license": "MIT",
24
+ "peerDependencies": {
25
+ "@invoiceleaf/integration-sdk": "^1.5.0"
26
+ },
27
+ "dependencies": {},
28
+ "devDependencies": {
29
+ "@invoiceleaf/integration-sdk": "file:../../integration-sdk",
30
+ "@types/node": "^22.10.0",
31
+ "typescript": "^5.7.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "manifest.json",
39
+ "README.md"
40
+ ]
41
+ }