Repository: hyperjumptech/monika (ts)

Issue:
Mailgun notification not working in node 18
**Describe the bug**
Running monika with mailgun notification and node 18 will show error `fetch failed`. Confirmed from this issue https://github.com/mailgun/mailgun.js/issues/275, it is due to the node.js v18 by default enables experimental global fetch API `node-fetch`

**To Reproduce**
Steps to reproduce the behavior:
1. use node v18.x.x . confirm it by running `node -v`
2. set mailgun notification like in the [docs](https://monika.hyperjump.tech/guides/notifications#mailgun)
3. run monika normally with `npm start`
4. See error

**Expected behavior**
monika running normally without error

**Screenshots**
![image](https://github.com/hyperjumptech/monika/assets/5974862/e9e6a3d9-6a93-4146-94c9-6c2daa79a0fd)

**Desktop (please complete the following information):**
 - OS: confirmed on Ubuntu 22 & Mac OS Monterey




Pull request description:
Fix: mailgun notification handle node 18
# Monika Pull Request (PR)

## What feature/issue does this PR add
1. Fix #1104 by changing how to send mailgun notification from using npm library to using http request.

## How did you implement / how did you fix it
1. remove `mailgun.js` npm package
2. extract `getContent` function
3. change implementation to use `sendHttpRequest` in `send` function
4. add `sendWithCustomContent` function to support [Neosnse](https://neosense.bgnlab.id/)

## How to test
1. add config for mailgun notification
5. run monika with node 18
6. monika running without error
7. get email that monika is starting 
![Inked2](https://github.com/hyperjumptech/monika/assets/5974862/f7a7e2cf-81d6-4820-88a1-44679b0d6ad9)



Patch:
diff --git a/README.md b/README.md
index ef634075..5acf9259 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,8 @@ To start developing, clone this repository, then install the dependencies:
 
 ```bash
 git clone git@github.com:hyperjumptech/monika.git
-npm run build -w packages/notification
 npm ci
+npm run build -w packages/notification
 ```
 
 Then, to run Monika from the source,
@@ -34,7 +34,7 @@ npm start
 - on Windows
 
 ```bash
-.\bin\run.cmd
+.\bin\dev.cmd
 ```
 
 To keep the formatting consistent, run the following command to format the source code:
@@ -49,6 +49,18 @@ Once you have made the changes, open a Pull Request and explain the issue your c
 
 For contribution details on how to add custom notifications [see the New Notifications guide here](https://monika.hyperjump.tech/guides/new-notifications).
 
+### Applying changes in monorepo workspaces folder (`packages/notification`)
+
+If you are developing feature/fix related to notification by making changes inside the `packages/notification` folder, it's easier to temporarily change the dependency source in `package.json` to be like this
+
+```
+"@hyperjumptech/monika-notification": "*"
+```
+
+it will sync `@hyperjumptech/monika-notification` package `node_modules` to any changes in the `packages/notification` folder without running build on every code change.
+
+See this [docs](https://turbo.build/repo/docs/handbook/workspaces#workspaces-which-depend-on-each-other) for further detail
+
 ### How to Test Probe Locally
 
 If you need to test a probe locally, there are predefined services in /dev/docker-compose.yaml. You are **encouraged** to add other services that can be probed by Monika. Run `cd dev && docker compose up` to run those services.
diff --git a/package-lock.json b/package-lock.json
index e7726345..bf1503a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -45,7 +45,6 @@
         "lodash": "^4.17.21",
         "macaddress": "^0.5.2",
         "mailgen": "^2.0.15",
-        "mailgun.js": "^3.5.4",
         "mariadb": "^3.0.2",
         "mongodb": "^4.10.0",
         "mongodb-uri": "^0.9.7",
@@ -5105,11 +5104,6 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
-    "node_modules/base-64": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
-      "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
-    },
     "node_modules/base32.js": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz",
@@ -5273,11 +5267,6 @@
         "readable-stream": "^3.4.0"
       }
     },
-    "node_modules/bluebird": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
-      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
-    },
     "node_modules/body-parser": {
       "version": "1.20.1",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -6049,19 +6038,6 @@
         "node": ">= 0.10"
       }
     },
-    "node_modules/clone-deep": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
-      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
-      "dependencies": {
-        "is-plain-object": "^2.0.4",
-        "kind-of": "^6.0.2",
-        "shallow-clone": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/clone-response": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@@ -6472,14 +6448,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/data-uri-to-buffer": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
-      "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==",
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/date-fns": {
       "version": "2.30.0",
       "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -7775,19 +7743,6 @@
         "reusify": "^1.0.4"
       }
     },
-    "node_modules/fetch-blob": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz",
-      "integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==",
-      "engines": {
-        "node": "^10.17.0 || >=12.3.0"
-      },
-      "peerDependenciesMeta": {
-        "domexception": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/figures": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -10061,14 +10016,6 @@
         "json-buffer": "3.0.1"
       }
     },
-    "node_modules/kind-of": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
-      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/kitx": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/kitx/-/kitx-2.1.0.tgz",
@@ -10077,41 +10024,6 @@
         "@types/node": "^12.0.2"
       }
     },
-    "node_modules/ky": {
-      "version": "0.25.1",
-      "resolved": "https://registry.npmjs.org/ky/-/ky-0.25.1.tgz",
-      "integrity": "sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sindresorhus/ky?sponsor=1"
-      }
-    },
-    "node_modules/ky-universal": {
-      "version": "0.8.2",
-      "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.8.2.tgz",
-      "integrity": "sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==",
-      "dependencies": {
-        "abort-controller": "^3.0.0",
-        "node-fetch": "3.0.0-beta.9"
-      },
-      "engines": {
-        "node": ">=10.17"
-      },
-      "funding": {
-        "url": "https://github.com/sindresorhus/ky-universal?sponsor=1"
-      },
-      "peerDependencies": {
-        "ky": ">=0.17.0",
-        "web-streams-polyfill": ">=2.0.0"
-      },
-      "peerDependenciesMeta": {
-        "web-streams-polyfill": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/levn": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -10514,21 +10426,6 @@
         "juice": "^8.0.0"
       }
     },
-    "node_modules/mailgun.js": {
-      "version": "3.7.3",
-      "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-3.7.3.tgz",
-      "integrity": "sha512-DHP9v6dNPRM2puOx4HVJVjQKWzgzpQ5Fh1ICW632qaDVgd/QqGRhOjCoHe12JJqrFkhgDvXBhENYeZDHYdkJHQ==",
-      "dependencies": {
-        "base-64": "^1.0.0",
-        "bluebird": "^3.7.2",
-        "ky": "^0.25.1",
-        "ky-universal": "^0.8.2",
-        "url": "^0.11.0",
-        "url-join": "0.0.1",
-        "web-streams-polyfill": "^3.0.1",
-        "webpack-merge": "^5.4.0"
-      }
-    },
     "node_modules/make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -11673,22 +11570,6 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/node-fetch": {
-      "version": "3.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz",
-      "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==",
-      "dependencies": {
-        "data-uri-to-buffer": "^3.0.1",
-        "fetch-blob": "^2.1.1"
-      },
-      "engines": {
-        "node": "^10.17 || >=12.3"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/node-fetch"
-      }
-    },
     "node_modules/node-gyp": {
       "version": "8.4.1",
       "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
@@ -14761,17 +14642,6 @@
       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
     },
-    "node_modules/shallow-clone": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
-      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
-      "dependencies": {
-        "kind-of": "^6.0.2"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -16635,20 +16505,6 @@
         "punycode": "^2.1.0"
       }
     },
-    "node_modules/url": {
-      "version": "0.11.1",
-      "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz",
-      "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==",
-      "dependencies": {
-        "punycode": "^1.4.1",
-        "qs": "^6.11.0"
-      }
-    },
-    "node_modules/url-join": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz",
-      "integrity": "sha512-H6dnQ/yPAAVzMQRvEvyz01hhfQL5qRWSEt7BX8t9DqnPw9BjMb64fjIRq76Uvf1hkHp+mTZvEVJ5guXOT0Xqaw=="
-    },
     "node_modules/url-parse": {
       "version": "1.5.10",
       "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
@@ -16658,11 +16514,6 @@
         "requires-port": "^1.0.0"
       }
     },
-    "node_modules/url/node_modules/punycode": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-      "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
-    },
     "node_modules/util": {
       "version": "0.12.5",
       "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -16892,14 +16743,6 @@
         "webidl-conversions": "^3.0.0"
       }
     },
-    "node_modules/web-streams-polyfill": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
-      "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
     "node_modules/webidl-conversions": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -16908,18 +16751,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/webpack-merge": {
-      "version": "5.9.0",
-      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz",
-      "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==",
-      "dependencies": {
-        "clone-deep": "^4.0.1",
-        "wildcard": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=10.0.0"
-      }
-    },
     "node_modules/whatwg-url": {
       "version": "11.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
@@ -17012,11 +16843,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/wildcard": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
-      "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ=="
-    },
     "node_modules/windows-release": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz",
@@ -18356,7 +18182,6 @@
         "form-data": "^4.0.0",
         "joi": "^17.4.0",
         "mailgen": "^2.0.15",
-        "mailgun.js": "^3.5.4",
         "msw": "^0.36.4",
         "nodemailer": "6.9.4",
         "pino": "8.14.1",
diff --git a/package.json b/package.json
index 3b43f375..f0612e0c 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,6 @@
     "posttest": "eslint . --ext .ts --config .eslintrc",
     "prepack": "npm run clean && tsc -b && oclif manifest",
     "start": "npm run prepack && ./bin/dev",
-    "start:windows": "npm run prepack && /bin/dev",
     "start:prod": "npm run prepack && ./bin/run",
     "test": "npm run clean && cross-env NODE_ENV=test nyc --reporter=lcov --extension .ts mocha --config .mocharc.json --forbid-only \"{packages,src,test}/**/*.test.ts\" --exclude test/cli.test.ts",
     "cli-test": "npx bats test/bats/cli.bats",
@@ -90,7 +89,6 @@
     "lodash": "^4.17.21",
     "macaddress": "^0.5.2",
     "mailgen": "^2.0.15",
-    "mailgun.js": "^3.5.4",
     "mariadb": "^3.0.2",
     "mongodb": "^4.10.0",
     "mongodb-uri": "^0.9.7",
diff --git a/packages/notification/channel/index.ts b/packages/notification/channel/index.ts
index 7e0ef7d7..3ce180f9 100644
--- a/packages/notification/channel/index.ts
+++ b/packages/notification/channel/index.ts
@@ -94,7 +94,7 @@ type NotificationChannel<T = any> = {
   send: (notificationData: T, message: NotificationMessage) => Promise<void>
   sendWithCustomContent?: (
     notificationData: T,
-    customContent: teams.Content // TODO: add other Content type from other channel
+    customContent: T
   ) => Promise<void>
   additionalStartupMessage?: (notificationData: T) => string
 }
diff --git a/packages/notification/channel/mailgun.ts b/packages/notification/channel/mailgun.ts
index bdbc47dd..afaf1301 100644
--- a/packages/notification/channel/mailgun.ts
+++ b/packages/notification/channel/mailgun.ts
@@ -22,17 +22,24 @@
  * SOFTWARE.                                                                      *
  **********************************************************************************/
 
-import Mailgun from 'mailgun.js'
-import formData from 'form-data'
+import FormData from 'form-data'
 import Joi from 'joi'
 import type { NotificationMessage } from '.'
 import Mailgen from 'mailgen'
+import { sendHttpRequest } from '../utils/http'
 
-type NotificationData = {
+export type NotificationData = {
   apiKey: string
   domain: string
   recipients: string[]
-  username?: string
+}
+
+export type Content = {
+  from: string
+  subject: string
+  html: string
+  text: string
+  headers?: { References: string }
 }
 
 export const validator = Joi.object().keys({
@@ -42,17 +49,70 @@ export const validator = Joi.object().keys({
     .label('Mailgun Recipients'),
   apiKey: Joi.string().required().label('Mailgun API Key'),
   domain: Joi.string().required().label('Mailgun Domain'),
-  username: Joi.string().label('Mailgun Username'),
 })
 
+const isContent = (data: NotificationMessage | Content): data is Content => {
+  const keys: Array<keyof Content> = ['from', 'subject', 'html', 'text']
+
+  return keys.every((k) => Object.prototype.hasOwnProperty.call(data, k))
+}
+
+const _send = async (
+  { apiKey, domain, recipients }: NotificationData,
+  data: NotificationMessage | Content
+): Promise<void> => {
+  const url = `https://api.mailgun.net/v3/${domain}/messages`
+  const auth = Buffer.from('api:' + apiKey).toString('base64')
+  const theData = isContent(data)
+    ? data
+    : getContent({ body: data.body, subject: data.subject, domain })
+
+  const formData = new FormData()
+  formData.append('from', theData.from)
+  formData.append('subject', theData.subject)
+  formData.append('html', theData.html)
+  formData.append('text', theData.text)
+  for (const email of recipients) formData.append('to', email)
+  if (theData.headers?.References)
+    formData.append('h:References', theData.headers?.References)
+
+  await sendHttpRequest({
+    method: 'POST',
+    url,
+    headers: {
+      Authorization: 'Basic ' + auth,
+    },
+    data: formData,
+  })
+}
+
 export const send = async (
-  { apiKey: key, domain, recipients, username = 'api' }: NotificationData,
-  { body, subject }: NotificationMessage
+  notificationData: NotificationData,
+  notificationMessage: NotificationMessage
+): Promise<void> => {
+  await _send(notificationData, notificationMessage)
+}
+
+export const sendWithCustomContent = async (
+  notificationData: NotificationData,
+  content: Content
 ): Promise<void> => {
+  await _send(notificationData, content)
+}
+
+function getContent({
+  body,
+  subject,
+  domain,
+}: {
+  body: string
+  subject: string
+  domain: string
+}): Content {
   // TODO: Read from ENV Variables
-  const DEFAULT_EMAIL = 'monika@hyperjump.tech'
   const DEFAULT_SENDER_NAME = 'Monika'
-  const to = recipients?.join(',')
+  const from = `${DEFAULT_SENDER_NAME} <mailgun@${domain}>`
+
   const mailGenerator = new Mailgen({
     theme: 'default',
     product: {
@@ -68,17 +128,13 @@ export const send = async (
   }
   const html = mailGenerator.generate(email)
   const text = mailGenerator.generatePlaintext(email)
-  const mailgun = new Mailgun(formData)
-  const mg = mailgun.client({ username, key })
-  const data = {
-    from: `${DEFAULT_SENDER_NAME} <${DEFAULT_EMAIL}>`,
-    to,
+
+  return {
+    from,
     subject,
     html,
     text,
   }
-
-  await mg.messages.create(domain, data)
 }
 
 export function additionalStartupMessage({
diff --git a/packages/notification/channel/pagerduty.ts b/packages/notification/channel/pagerduty.ts
index f7850f3c..1df99b2e 100644
--- a/packages/notification/channel/pagerduty.ts
+++ b/packages/notification/channel/pagerduty.ts
@@ -1,3 +1,27 @@
+/**********************************************************************************
+ * MIT License                                                                    *
+ *                                                                                *
+ * Copyright (c) 2021 Hyperjump Technology                                        *
+ *                                                                                *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy   *
+ * of this software and associated documentation files (the "Software"), to deal  *
+ * in the Software without restriction, including without limitation the rights   *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      *
+ * copies of the Software, and to permit persons to whom the Software is          *
+ * furnished to do so, subject to the following conditions:                       *
+ *                                                                                *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software.                                *
+ *                                                                                *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  *
+ * SOFTWARE.                                                                      *
+ **********************************************************************************/
+
 /**********************************************************************************
  * MIT License                                                                    *
  *                                                                                *
@@ -24,6 +48,7 @@
 
 import Joi from 'joi'
 import type { NotificationMessage } from '.'
+import { sendHttpRequest } from '../utils/http'
 
 type PagerDutyConfig = {
   key: string
@@ -69,12 +94,13 @@ export async function send(
     }
 
     const options = {
+      url,
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify(eventPayload),
+      data: JSON.stringify(eventPayload),
     }
 
-    await fetch(url, options)
+    await sendHttpRequest(options)
   }
 }
 
diff --git a/packages/notification/channel/teams.ts b/packages/notification/channel/teams.ts
index 52c8e146..c06dad86 100644
--- a/packages/notification/channel/teams.ts
+++ b/packages/notification/channel/teams.ts
@@ -26,7 +26,7 @@ import Joi from 'joi'
 import { sendHttpRequest } from '../utils/http'
 import type { NotificationMessage } from '.'
 
-type NotificationData = {
+export type NotificationData = {
   url: string
 }
 
diff --git a/packages/notification/package.json b/packages/notification/package.json
index 55b7dcd7..54806d17 100644
--- a/packages/notification/package.json
+++ b/packages/notification/package.json
@@ -30,7 +30,6 @@
     "form-data": "^4.0.0",
     "joi": "^17.4.0",
     "mailgen": "^2.0.15",
-    "mailgun.js": "^3.5.4",
     "msw": "^0.36.4",
     "nodemailer": "6.9.4",
     "pino": "8.14.1",


Test patch:
diff --git a/test/plugins/prometheus/index.test.ts b/test/plugins/prometheus/index.test.ts
index 6f54a347..16fab162 100644
--- a/test/plugins/prometheus/index.test.ts
+++ b/test/plugins/prometheus/index.test.ts
@@ -25,6 +25,7 @@
 import { expect, test } from '@oclif/test'
 import path from 'path'
 import cmd from '../../../src'
+import axios from 'axios'
 
 const { resolve } = path
 
@@ -42,7 +43,7 @@ describe('Prometheus plugin', () => {
       )
       .it('runs Prometheus metric server', async (ctx) => {
         // act
-        const res = await fetch('http://localhost:4444/metrics')
+        const res = await axios.get('http://localhost:4444/metrics')
 
         // assert
         expect(ctx.stdout).to.contain('Starting Monika.')
@@ -73,7 +74,7 @@ describe('Prometheus plugin', () => {
       .it('runs Prometheus metric server but return 405', async () => {
         try {
           // act
-          await fetch('http://localhost:4446/metrics', { method: 'POST' })
+          await axios.post('http://localhost:4446/metrics')
         } catch (error: any) {
           // assert
           expect(error.response.status).to.equal(405)