diff --git a/src/email-lexoffice-import/README.md b/src/email-lexoffice-import/README.md index ab7e3b8..81dffb4 100755 --- a/src/email-lexoffice-import/README.md +++ b/src/email-lexoffice-import/README.md @@ -8,17 +8,23 @@ This is an simple tool which logs into your email account and imports all invoic ## Environment Variables -| Name | Type | Default Value | Description | -|-------------------------------------|--------|---------------|-------------------------------------------------------------------| -| `INPUT__MAIL` | string | `null` | An sender email address to filter for. | -| `INPUT__MODE` | string | `null` | The mode to use, see below. | -| `IMAP_HOST` | number | `null` | The IMAP host. | -| `IMAP_USER` | string | `null` | The IMAP user. | -| `IMAP_PASSWORD` | bool | `null` | The IMAP password. | -| `IMAP_TLS` | bool | `true` | Whether to use TLS. | -| `IMAP_PORT` | number | `993` | The IMAP port. | -| `LEXOFFICE_KEY` | string | `null` | The lexoffice API key. | -| `SCHEDULER` | number | `0 */3 * * *` | The scheduler to use, cron syntax. | +| Name | Type | Default Value | Description | Required | +|-------------------------------------|--------|---------------|------------------------------------------------------------------------------|----------| +| `INPUT__MAIL` | string | `null` | An sender email address to filter for. A single '*' to match all is allowed. | yes | +| `INPUT__MODE` | string | `null` | The mode to use, see below. | no | +| `IMAP_HOST` | number | `null` | The IMAP host. | yes | +| `IMAP_USER` | string | `null` | The IMAP user. | yes | +| `IMAP_PASSWORD` | bool | `null` | The IMAP password. | yes | +| `IMAP_TLS` | bool | `true` | Whether to use TLS. | yes | +| `IMAP_PORT` | number | `993` | The IMAP port. | yes | +| `SMTP_HOST` | number | `null` | The SMTP host. | no | +| `SMTP_USER` | string | `null` | The SMTP user. | no | +| `SMTP_PASSWORD` | bool | `null` | The SMTP password. | no | +| `SMTP_TLS` | bool | `true` | Whether to use TLS. | no | +| `SMTP_PORT` | number | `587` | The SMTP port. | no | +| `LEXOFFICE_KEY` | string | `null` | The lexoffice API key. | yes | +| `SCHEDULER` | number | `0 */3 * * *` | The scheduler to use, cron syntax or 'now' for just one execution. | no | +| `REDIRECT_UNPARSABLE` | string | `null` | The email address to redirect unparsable emails to. Requires SMTP. | no | ### Possible Modes diff --git a/src/email-lexoffice-import/index.js b/src/email-lexoffice-import/index.js index 3adb592..6051910 100644 --- a/src/email-lexoffice-import/index.js +++ b/src/email-lexoffice-import/index.js @@ -1,5 +1,6 @@ import Imap from 'imap' import Axios from 'axios' +import NodeMailer from 'nodemailer' import { FormData, File } from 'formdata-node' import { scheduleJob } from 'node-schedule' import { simpleParser } from 'mailparser' @@ -33,6 +34,15 @@ const cfg = { checkServerIdentity: () => undefined, }, }, + smtp: { + host: env.SMTP_HOST, + port: Number(env.SMTP_PORT || 587), + secure: true, + auth: { + user: env.SMTP_USER, + pass: env.SMTP_PASSWORD, + }, + }, input: Object .keys(env) .filter(key => key.startsWith('INPUT_') && key.endsWith('_MAIL')) @@ -44,6 +54,7 @@ const cfg = { })), lexofficeKey: env.LEXOFFICE_KEY, scheduler: env.SCHEDULER || '0 */3 * * *', + redirectUnparsable: env.REDIRECT_UNPARSABLE, } console.log('Read config:') @@ -64,6 +75,26 @@ const handleErr = async err => { process.exit(1) } +// redirect unparsable mails +const redirectUnparsable = async (mail, id) => { + if (!cfg.redirectUnparsable) return; + if (!cfg.smtp.host) throw new Error('Cannot redirect unparsable mails: SMTP host not configured') + console.log(`Redirecting mail ${id} to "${cfg.redirectUnparsable}"...`) + const transporter = NodeMailer.createTransport(cfg.smtp) + await transporter.sendMail({ + from: cfg.smtp.user, + to: cfg.redirectUnparsable, + subject: `Invoices - Unparsable mail: ${id}`, + text: 'Hello, this is your invoices bot. I was unable to parse the email in the attachments. Please check it manually.', + attachments: [ + { + filename: 'mail.eml', + content: mail, + } + ] + }) +} + // job function const jobFunction = async () => { console.log('Starting job at ' + new Date().toISOString()) @@ -103,12 +134,17 @@ const jobFunction = async () => { const pdf = parsed.attachments.find(a => a.contentType === 'application/pdf') if (!pdf) { console.log(`No PDF found in mail ${uid}, skipping...`) + redirectUnparsable(mail, uid) continue; } const sender = parsed.from.value[0].address console.log(`Processing mail ${uid} from "${sender}"...`) + let found = false; for (const i of cfg.input) { - if (sender !== i.mail) continue; + const emailCheck = String(i.mail).split('*') + if (emailCheck.length === 2 && !(sender.startsWith(emailCheck[0]) && sender.endsWith(emailCheck[1]))) continue; + else if (sender !== i.mail) continue; + found = true; console.log(`Processing mail ${uid} for input "${i.key}"...`) switch (i.mode) { case 'just-upload': @@ -136,6 +172,10 @@ const jobFunction = async () => { console.log(`Marking mail ${uid} as seen...`) await new Promise(resolve => imap.addFlags(uid, '\\Seen', resolve)) } + if (!found) { + console.log(`No input found for mail ${uid}, skipping...`) + redirectUnparsable(mail, uid) + } } console.log('Disconnecting from IMAP server...') await new Promise(resolve => imap.closeBox(resolve)) diff --git a/src/email-lexoffice-import/package.json b/src/email-lexoffice-import/package.json index 9de7913..10860f2 100644 --- a/src/email-lexoffice-import/package.json +++ b/src/email-lexoffice-import/package.json @@ -5,6 +5,7 @@ "formdata-node": "^6.0.3", "imap": "^0.8.19", "mailparser": "^3.6.6", - "node-schedule": "^2.1.1" + "node-schedule": "^2.1.1", + "nodemailer": "^6.9.8" } } diff --git a/src/email-lexoffice-import/pnpm-lock.yaml b/src/email-lexoffice-import/pnpm-lock.yaml index a880dce..b24bb7a 100644 --- a/src/email-lexoffice-import/pnpm-lock.yaml +++ b/src/email-lexoffice-import/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: node-schedule: specifier: ^2.1.1 version: 2.1.1 + nodemailer: + specifier: ^6.9.8 + version: 6.9.8 packages: