Skip to content

Commit

Permalink
added: REDIRECT_UNPARSABLE feature
Browse files Browse the repository at this point in the history
  • Loading branch information
scolastico committed Jan 21, 2024
1 parent 7cbc18c commit f2c3b01
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 13 deletions.
28 changes: 17 additions & 11 deletions src/email-lexoffice-import/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_<name>_MAIL` | string | `null` | An sender email address to filter for. |
| `INPUT_<name>_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_<name>_MAIL` | string | `null` | An sender email address to filter for. A single '*' to match all is allowed. | yes |
| `INPUT_<name>_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

Expand Down
42 changes: 41 additions & 1 deletion src/email-lexoffice-import/index.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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'))
Expand All @@ -44,6 +54,7 @@ const cfg = {
})),
lexofficeKey: env.LEXOFFICE_KEY,
scheduler: env.SCHEDULER || '0 */3 * * *',
redirectUnparsable: env.REDIRECT_UNPARSABLE,
}

console.log('Read config:')
Expand All @@ -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())
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 2 additions & 1 deletion src/email-lexoffice-import/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
3 changes: 3 additions & 0 deletions src/email-lexoffice-import/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f2c3b01

Please sign in to comment.