{"id":23974713,"url":"https://github.com/oldium/microsoft-smtp-oauth2-proxy","last_synced_at":"2026-05-21T00:08:09.345Z","repository":{"id":270873722,"uuid":"911681149","full_name":"oldium/microsoft-smtp-oauth2-proxy","owner":"oldium","description":"A simple SMTP server with basic authentication that proxies to Microsoft SMTP server with OAuth2 authentication","archived":false,"fork":false,"pushed_at":"2026-02-15T21:07:49.000Z","size":1305,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-02-16T03:57:51.397Z","etag":null,"topics":["microsoft","oauth2","outlook","proxy","server","smtp"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oldium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-03T15:39:28.000Z","updated_at":"2026-02-15T21:07:48.000Z","dependencies_parsed_at":"2025-02-24T16:38:25.328Z","dependency_job_id":"698d9deb-471e-4462-83f3-0678935f8742","html_url":"https://github.com/oldium/microsoft-smtp-oauth2-proxy","commit_stats":null,"previous_names":["oldium/microsoft-smtp-oauth2-proxy"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/oldium/microsoft-smtp-oauth2-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oldium%2Fmicrosoft-smtp-oauth2-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oldium%2Fmicrosoft-smtp-oauth2-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oldium%2Fmicrosoft-smtp-oauth2-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oldium%2Fmicrosoft-smtp-oauth2-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oldium","download_url":"https://codeload.github.com/oldium/microsoft-smtp-oauth2-proxy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oldium%2Fmicrosoft-smtp-oauth2-proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33281447,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-20T15:12:43.734Z","status":"ssl_error","status_checked_at":"2026-05-20T15:12:42.300Z","response_time":356,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["microsoft","oauth2","outlook","proxy","server","smtp"],"created_at":"2025-01-07T05:48:40.832Z","updated_at":"2026-05-21T00:08:09.339Z","avatar_url":"https://github.com/oldium.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Microsoft SMTP OAuth2 Proxy for Next.js\n\nThis is a [Next.js](https://nextjs.org) project providing a proxy server to\nallow connecting to Microsoft SMTP servers using basic authentication.\n\nMicrosoft has disabled basic authentication for personal Outlook accounts on\nMicrosoft Outlook SMTP servers in favor of OAuth 2.0. This change affects\nsending and forwarding emails from custom services and public ones like\nGmail\u0026apos;s \u003cq\u003eSend mail as\u003c/q\u003e feature. Authenticating with basic\nauthentication against the Outlook server like `smtp-mail.outlook.com` or\n`smtp.office365.com` results in the following error:\n\n\u003e ```\n\u003e 535 5.7.139 Authentication unsuccessful, basic authentication is disabled.\n\u003e ```\n\nThis project provides an SMTP proxy server that authenticates the client with\nusername and password, but authenticates with OAuth 2.0 on Microsoft Outlook\nserver. The proxy server supports the following features:\n\n* 🛡️ OAuth 2.0 authentication with Microsoft via web UI.\n* 🧑‍💼 User login access rules.\n* 🕸️ Single Node.js listening web server exposing just one port for web UI and\n  API.\n* 🐋 Docker-ready.\n* ✉️ Sending emails using the SMTP protocol.\n* 📬 Optional unprotected port for local trusted clients.\n* 💡 Mixed-mode operation with independent SSL/TLS and STARTTLS connections for\n  client and downstream server.\n* 🎉 Allows to use single port for SSL/TLS (a.k.a. implicit TLS) and STARTTLS\n  connections (see `SMTP_AUTOTLS_PORT` variable in\n  [`.env.example`][env-example]).\n* 🔁 Automatic retry on transient network errors when refreshing OAuth2\n  authentication tokens from Microsoft.\n* 🛡️ Safer proxy handling by ignoring untrusted `X-Forwarded-*` headers.\n* 🧹 Graceful shutdown that terminates idle connections.\n\nSupported SMTP features:\n\n* [RFC 5321][rfc5321] SMTP protocol.\n* [RFC 4616][rfc4616] Authentication using username and password with\n  `AUTH PLAIN`.\n* [LOGIN SASL Mechanism draft][draft-murchison-sasl-login-00] Authentication\n  using username and password with `AUTH LOGIN`.\n* [RFC 8314][rfc8314] SSL/TLS encryption.\n* [RFC 3207][rfc3207] SMTP `STARTTLS` extension.\n* [RFC 2920][rfc2920] SMTP Pipelining extension.\n* [RFC 3030][rfc3030] SMTP Chunking extension.\n* All other extensions using standard SMTP messages supported by the target SMTP\n  server, like 8bit encoding, UTF8 and the like (the messages and replies are\n  simply forwarded).\n\n[env-example]: https://github.com/oldium/microsoft-smtp-oauth2-proxy/blob/master/.env.example\n\n[draft-murchison-sasl-login-00]: https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00\n\n[rfc2920]: https://datatracker.ietf.org/doc/html/rfc2920\n\n[rfc3030]: https://datatracker.ietf.org/doc/html/rfc3030\n\n[rfc3207]: https://datatracker.ietf.org/doc/html/rfc3207\n\n[rfc4616]: https://datatracker.ietf.org/doc/html/rfc4616\n\n[rfc5321]: https://datatracker.ietf.org/doc/html/rfc5321\n\n[rfc8314]: https://datatracker.ietf.org/doc/html/rfc8314\n\n## Look and Feel\n\n\u003cp\u003e\n\u003cimg src=\"https://github.com/oldium/microsoft-smtp-oauth2-proxy/raw/master/doc/images/main_page.jpeg\" alt=\"Main Page\" width=\"250\"\u003e\n\u003cimg src=\"https://github.com/oldium/microsoft-smtp-oauth2-proxy/raw/master/doc/images/config_page.jpeg\" alt=\"Configuration Page\" width=\"250\"\u003e\n\u003c/p\u003e\n\nDark mode is supported 🎉.\n\n## Quick Start\n\nThis project is developed with Node.js version 24, Next.js 15.5 and React 19.1.\nIt uses custom entrypoint to start both the SMTP server and the web server.\n\nFirst, install the dependencies:\n\n```bash\nnpm install\n```\n\nThen configure the server. Look at the [`.env.example`][env-example] in the\nproject root and copy it to `.env`. The minimal configuration looks like this:\n\n`.env`:\n\n```dotenv\nAPP_SECRETS=will-configure:later\nSESSION_SECRET=my-session-secret\nSMTP_PUBLIC_HOST=smtp.example.com\nSMTP_PORT=25\n```\n\nTo generate the session secret, you can use\n[1password online service][onepass-online]. The session secret is used to\nencrypt the session cookie. Minimum of 32 characters gives you enough entropy\nfor the encryption (at least for now). You can use special characters, but read\nthe example documentation carefully on how to write it in the `.env` file.\n\nThen, run the development server:\n\n```bash\nnpm run dev\n```\n\nYou should be able to open the web interface on\n[http://localhost:3000][localhost-3000].\n\nThe Microsoft login will not work, this needs a\n[special setup](#microsoft-oauth2-setup).\n\n[onepass-online]: https://1password.com/password-generator\n\n[localhost-3000]: http://localhost:3000\n\n## Microsoft OAuth2 Setup\n\nYou need to have [Azure Microsoft account][create-azure], the free one is\nenough.\n\n\u003e [!CAUTION]\n\u003e The Azure portal is available also without subscription, it shows the\n\u003e Azure AD B2C directory, but this is not suitable for the OAuth2\n\u003e authentication. You will not be able to configure the application registration\n\u003e to use Personal Microsoft accounts.\n\n\u003e [!TIP]\n\u003e If you decide to create Azure Microsoft account, it requires a credit card.\n\u003e But do not worry, using OAuth2 authentication is free, so you will not be\n\u003e charged (unless you use some non-free Azure services of course). Before the\n\u003e free-trial month ends, you will receive an email asking to upgrade and account\n\u003e to continue using it, so if you select \u003cq\u003ePay as you go\u003c/q\u003e option, you will\n\u003e continue using OAuth2 authentication for free.\n\nIn order to register the application, do the following:\n\n1. Login to the [Azure portal][azure-portal].\n2. Open \u003cq\u003eApp registrations\u003c/q\u003e service (you can find it in the search bar).\n3. Click on \u003cq\u003eNew registration\u003c/q\u003e button.\n4. Fill-in the name, which will be shown on the User Consent screen during\n   login. Select \u003cq\u003ePersonal Microsoft accounts only\u003c/q\u003e as the personal account\n   type. Keep redirect URI empty, we will configure it later.\n5. Note the \u003cq\u003eApplication (client) ID\u003c/q\u003e on the \u003cq\u003eOverview\u003c/q\u003e page, this is\n   the `application-id` referred in the `.env.example` file.\n6. Go to \u003cq\u003eCertificates \u0026 secrets\u003c/q\u003e and create a new client secret. Note the\n   secret \u003cq\u003eValue\u003c/q\u003e (not the ID), this is the `secret` referred in the\n   `.env.example` file. You can have one secret for development and one for\n   production. Azure supports up to 2 secrets per application.\n7. Go to \u003cq\u003eAPI permissions\u003c/q\u003e and add \u003cq\u003eMicrosoft Graph\u003c/q\u003e API with\n   \u003cq\u003eSMTP.Send\u003c/q\u003e permission. This is needed to send emails using the OAuth2\n   authentication.\n8. Go to \u003cq\u003eAuthentication\u003c/q\u003e and add a new platform. Select \u003cq\u003eWeb\u003c/q\u003e and\n   fill-in the redirect URI. For development, it is\n   `http://localhost:3000/auth`. For production, it is\n   `https://smtp.example.com/auth` (use your domain).\n\n\u003e [!IMPORTANT]\n\u003e The redirect URI is used to redirect the user back to the application after\n\u003e the login. It is important to configure it correctly, otherwise the login will\n\u003e not work. The redirect URI must be HTTPS for production (more precisely for\n\u003e any non-localhost address), otherwise the login will not work. The host part\n\u003e of the URI is constructed by the application from the requests, so it is\n\u003e necessary that the `Host` HTTP header is correct or that the `X-Forwarded-*`\n\u003e proxy headers are set correctly. The proxy headers `X-Forwarded-*` have\n\u003e precedence over the `Host` header. When forwarding `X-Forwarded-*` headers\n\u003e from a reverse proxy, make sure to configure `TRUST_PROXY` in `.env` (see\n\u003e [`.env.example`][env-example]), otherwise the headers are ignored.\n\n\u003e [!TIP]\n\u003e One common gotcha is that you have http://localhost:3000/auth in the redirect\n\u003e URI, but you opened the web interface on http://127.0.0.1:3000, so the\n\u003e redirect URI created by application is http://127.0.0.1:3000/auth. The\n\u003e application URI must match exactly the configured redirect URI (including the\n\u003e protocol), so the login will not work. The easiest fix for this is to include\n\u003e both redirect URIs in the setup if possible.\n\u003e\n\u003e Another common gotcha is forwarding `X-Forwarded-*` headers from a reverse\n\u003e proxy but not configuring `TRUST_PROXY`. In that case the headers are\n\u003e ignored, so the redirect URI is constructed from the request as seen by the\n\u003e application and will not match the external URL configured in Azure.\n\n[create-azure]: https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account\n\n[azure-portal]: https://portal.azure.com\n\n## Testing the SMTP Proxy Locally\n\nThe SMTP proxy can be tested using the internal tools during development or\nanytime by [Swiss Army Knife SMTP][swaks-web] (`swaks`).\n\n### test-send-db tool\n\nThis tool uses the SQLite database and SMTP proxy configuration to send the\nemail.\n\n```bash\nnpm run test-send-db \u003cfrom\u003e \u003cto\u003e\n```\n\nThe `\u003cfrom\u003e` is either an email address, or a username and the email address in\nthe form `User Name \u003cemail@example.com\u003e`. The `\u003cto\u003e` is in the same format at\nthe `\u003cfrom\u003e`. The SQLite database will be checked for the sending user and his\ncredentials will be used for the authentication.\n\n### test-send-auth tool\n\nThis tool does not use any internal configuration, it just acts as an SMTP\nclient, which wants to send an email.\n\n```bash\nnpm run test-send-auth localhost:\u003cport\u003e \u003cfrom\u003e \u003cpassword\u003e \u003cto\u003e [\u003csubject\u003e [\u003cmessage\u003e]]\n```\n\nFor the `\u003cport\u003e` value use `SMTP_PORT` value from the configuration (or other\nconfigured port number like `SMTP_TLS_PORT`, `SMTP_STARTTLS_PORT` or\n`SMTP_AUTOTLS_PORT`). The used protocol is determined by the port number – 25 is\nunsecured, 465 is SSL/TLS and 587 is STARTTLS. The `\u003cfrom\u003e` is the email address\nof the sender and at the same time email user for authentication, the\n`\u003cpassword\u003e` is the password. The `\u003cto\u003e` is the email address of the receiver.\nThe `\u003csubject\u003e` and `\u003cmessage\u003e` are optional, the default values are used if not\nprovided.\n\n### Swiss Army Knife SMTP (`swaks`)\n\nThe `swaks` tool is a powerful SMTP client, which can be used to test the SMTP\nserver. For installation see project [web pages][swaks-web], for usage see\ngenerated [help documentation][swaks-doc].\n\nThe following command sends an email via an unprotected port:\n\n```bash\nswaks --auth PLAIN --auth-user \u003cemail\u003e --auth-password \u003cpassword\u003e \\\n  --server localhost --port \u003cport\u003e \\\n  --ehlo \"[127.0.0.1]\" \\\n  --from \u003cemail\u003e --to \u003cemail\u003e \\\n  --header \"Subject: Test email\" --body \"This is a test email.\"\n```\n\nFor the `\u003cport\u003e` value use `SMTP_PORT` value from the configuration (if\nconfigured). The `\u003cemail\u003e` is purely the email address used for authentication,\n`\u003cpassword\u003e` is the password. The `\u003cfrom\u003e` and `\u003cto\u003e` are the email addresses of\nthe sender and the receiver, respectively, without the name part. If you want to\nuse the name part, additionally supply the header `From:` and/or `To:` by using\nthe `--header \"From: User Name \u003cemail@example.com\u003e\"` format.\n\nTo test SSL/TLS, use `SMTP_TLS_PORT` or `SMTP_AUTOTLS_PORT` and the following\noptions:\n\n```bash\nswaks --auth PLAIN --auth-user \u003cemail\u003e --auth-password \u003cpassword\u003e \\\n  --server localhost --port \u003cport\u003e \\\n  --tls-on-connect --tls-sni smtp.example.com --tls-verify \\\n  --ehlo \"[127.0.0.1]\" \\\n  --from \u003cemail\u003e --to \u003cemail\u003e \\\n  --header \"Subject: Test email\" --body \"This is a test email.\"\n```\n\nThe `--tls-sni` option controls which SSL certificate is requested (there is\nonly one) and `--tls-verify` option checks the certificate. The\n`--tls-on-connect` option is used to start the SSL/TLS connection immediately.\nThe rest of the options are the same as for the unprotected connection.\n\nTo test STARTTLS, use `SMTP_STARTTLS_PORT` or `SMTP_AUTOTLS_PORT` and the\nfollowing options:\n\n```bash\nswaks --auth PLAIN --auth-user \u003cemail\u003e --auth-password \u003cpassword\u003e \\\n  --server localhost --port \u003cport\u003e \\\n  --tls --tls-sni smtp.example.com --tls-verify \\\n  --ehlo \"[127.0.0.1]\" \\\n  --from \u003cemail\u003e --to \u003cemail\u003e \\\n  --header \"Subject: Test email\" --body \"This is a test email.\"\n```\n\nThe `--tls` option is used to start the STARTTLS connection immediately, the\nrest of the options are the same as for the SSL/TLS connection.\n\n\u003e [!NOTE]\n\u003e The `--ehlo` option is used to set the SMTP `EHLO` string, which is used to\n\u003e identify the client to the server. The `swaks` tool uses the local host name,\n\u003e which is not always a valid host name. The `[127.0.0.1]` value is a correctly\n\u003e encoded and accepted IP address, although it is not reachable from the server.\n\n[swaks-web]: https://jetmore.org/john/code/swaks/\n\n[swaks-doc]: https://github.com/jetmore/swaks/blob/develop/doc/base.pod\n\n## Production Build\n\n### Manual Build\n\nTo build the production version of the application, run:\n\n```bash\nnpm run build:release\n```\n\nThis will create a production build in the `dist` directory. The application can\nthen be started with:\n\n```bash\ncd dist\nnpm run prod\n```\n\nThis expects that the correct environment variables are set for the production.\nEither modify the generated `.env` file (it contains few mandatory values to set\nup production), or ensure that the environment variables are set before the\ncommand is executed. This is suitable especially for the Docker environment.\n\n### Docker Build\n\nTo build the Docker image, run:\n\n```bash\ndocker build -t microsoft-smtp-oauth2-proxy .\n```\n\nThis will create a Docker image with the name `microsoft-smtp-oauth2-proxy`.\n\n\u003e [!NOTE]\n\u003e The author of Microsoft SMTP OAuth2 Proxy creates the release builds with the\n\u003e following command, which ensures that fresh third-party images are used and\n\u003e adds additional metadata to the Docker image itself:\n\u003e\n\u003e ```bash\n\u003e docker buildx build --progress=plain --attest type=provenance,mode=max \\\n\u003e   --attest type=sbom,generator=docker/scout-sbom-indexer:latest \\\n\u003e   --no-cache -f .\\Dockerfile . -t microsoft-smtp-oauth2-proxy\n\u003e ```\n\n#### Run the Image\n\nThe image is started with `/app` as the working directory, so all relative paths\nare resolved relative to `/app`. The image can be run with the following\ncommand:\n\n```bash\ndocker run -p 80:3000 -p \u003chost port\u003e:\u003capp port\u003e ... \\\n  -v \u003cvolume name\u003e:/app/data -v \u003ccerts directory\u003e:/app/certs ... \\\n  -e SMTP_KEY_FILE=certs/smtp_key.pem -e SMTP_CERT_FILE=certs/smtp_cert.pem \\\n  -e APP_SECRETS=my-app-id:my-secret\n  -e \u003cvariable\u003e=\u003cvalue\u003e ... \\\n  microsoft-smtp-oauth2-proxy\n```\n\nFor possible environmental variables and their values `-e \u003cvariable\u003e=\u003cvalue\u003e`\nsee [`.env.example`][env-example] file in the project root.\n\n#### SSL/TLS and STARTTLS Ports Exposed\n\nThe image can be started with:\n\n```bash\ndocker run -p 80:3000 -p 465:465 -p 587:587 \\\n  -v proxy-config:/app/data -v ./certs:/app/certs \\\n  -e APP_SECRETS=my-app-id:my-secret -e SESSION_SECRET=my-session-secret \\\n  -e SMTP_KEY_FILE=certs/smtp_key.pem -e SMTP_CERT_FILE=certs/smtp_cert.pem \\\n  -e SMTP_PUBLIC_HOST=smtp.example.com \\\n  -e SMTP_TLS_PORT=465 -e SMTP_STARTTLS_PORT=587 \\\n  microsoft-smtp-oauth2-proxy\n```\n\nThis starts the application with the following features:\n\n* Web server listens on port 80.\n* The SMTP server listens on 465 (SSL/TLS) and 587 (STARTTLS) ports.\n* The certificates `smtp_key.pem` and `smtp_cert.pem` (in PEM format) are taken\n  from the local `./certs` directory.\n* The SQLite database is stored in the Docker volume named `proxy-config`.\n\n#### Auto-TLS Protocol Detection\n\nThe auto-TLS ports can listen on the same ports as standard SSL/TLS and STARTTLS\ntraffic:\n\n```bash\ndocker run -p 80:3000 -p 465:465 -p 587:587 \\\n  -v proxy-config:/app/data -v ./certs:/app/certs \\\n  -e APP_SECRETS=my-app-id:my-secret -e SESSION_SECRET=my-session-secret \\\n  -e SMTP_KEY_FILE=certs/smtp_key.pem -e SMTP_CERT_FILE=certs/smtp_cert.pem \\\n  -e SMTP_PUBLIC_HOST=smtp.example.com \\\n  -e SMTP_AUTOTLS_PORT=465,587 \\\n  microsoft-smtp-oauth2-proxy\n```\n\nThis starts the application with the following features:\n\n* Web server listens on port 80.\n* The SMTP server listens on 465 and 587 ports, which accept both SSL/TLS and\n  STARTTLS connections.\n* The certificates `smtp_key.pem` and `smtp_cert.pem` (in PEM format) are taken\n  from the local `./certs` directory.\n* The SQLite database is stored in the Docker volume named `proxy-config`.\n\n#### Run Behind Reverse Proxy\n\nIf you have reverse proxy with SSL/TLS termination, like [HAProxy][haproxy], you\ncan omit the certificates and forward the traffic to the `SMTP_PORT`:\n\n```bash\ndocker run -p 80:3000 -p 25:25 \\\n  -v proxy-config:/app/data \\\n  -e APP_SECRETS=my-app-id:my-secret -e SESSION_SECRET=my-session-secret \\\n  -e SMTP_PUBLIC_HOST=smtp.example.com \\\n  -e SMTP_PORT=25 \\\n  microsoft-smtp-oauth2-proxy\n```\n\nThis starts the application with the following features:\n\n* Web server listens on port 80.\n* The SMTP server listens on unprotected port 25.\n* The SQLite database is stored in the Docker volume named `proxy-config`.\n\nThen you can configure the proxy to forward the decrypted (non-SSL) traffic to\nthe SMTP server's port 25. In that case you would need something like\n[go-mmproxy][go-mmproxy] to restore the original IP address in logs –\n[see below][reverse-proxy].\n\n[haproxy]: https://www.haproxy.org/\n\n[go-mmproxy]: https://github.com/path-network/go-mmproxy\n\n[reverse-proxy]: #real-ip-addresses-with-haproxy-and-ssltls-termination\n\n### Docker Compose\n\nThe following `docker-compose.yml` file can be used to start the application:\n\n```yaml\nservices:\n  smtp-proxy:\n    restart: always\n    image: microsoft-smtp-oauth2-proxy\n    volumes:\n      - ./data:/app/data\n      - ./certs:/app/certs\n    environment:\n      APP_SECRETS: \"my-app-id:my-secret\"\n      SESSION_SECRET: \"my-session-secret\"\n      SMTP_KEY_FILE: \"certs/smtp_key.pem\"\n      SMTP_CERT_FILE: \"certs/smtp_cert.pem\"\n      SMTP_PUBLIC_HOST: smtp.example.com\n      SMTP_TLS_PORT: 465\n      SMTP_STARTTLS_PORT: 587\n    ports:\n      - \"80:3000\"\n      - \"465:465\"\n      - \"587:587\"\n```\n\nThis is the same as the manual Docker run command shown above, but in the Docker\nCompose. See section above for the examples. To start the application, run:\n\n```bash\ndocker-compose up --detach\n```\n\n### Auto-TLS Protocol Detection Directly on HAProxy\n\nIf you are using HAProxy as a reverse proxy, you can configure the same logic,\nwhich is used behind the `SMTP_AUTOTLS_PORT` configuration in order to\ndifferentiate SSL/TLS and STARTTLS connections. The following HAProxy\nconfiguration can be used:\n\n```text\nfrontend in-smtp\n        mode tcp\n        bind :465\n        bind :587\n        option logasap\n\n        acl ACL_ssl_hello req.ssl_hello_type 1\n\n        tcp-request inspect-delay 3s\n        tcp-request content accept if ACL_ssl_hello\n        tcp-request content accept\n\n        use_backend out-smtp-tls if ACL_ssl_hello\n        default_backend out-smtp-starttls\n\nbackend out-smtp-tls\n        mode tcp\n        server out-smtp-proxy smtp-proxy:465\n\nbackend out-smtp-starttls\n        mode tcp\n        server out-smtp-proxy smtp-proxy:587\n```\n\nThis configuration will try to detect SSL Client Hello message and if it is\nfound, the connection is forwarded to the SSL/TLS backend. Otherwise the\nconnection is forwarded to the STARTTLS backend.\n\nThe `smtp-proxy` is the hostname of the SMTP proxy service. If the Docker\nCompose network is shared between the HAProxy and the SMTP proxy and the service\nis named `smtp-proxy`, the hostname will be resolved to the correct IP address,\nbut use the _listening_ port numbers, not the exposed ones.\n\nYou can combine it with the [go-mmproxy][go-mmproxy] to restore the original IP\naddress in logs – [see below][reverse-proxy].\n\n\u003e [!NOTE]\n\u003e With a socket trick (TCP backend forwards SSL traffic via unix socket to SSL\n\u003e frontend, which forwards to unprotected backend port), you can even use\n\u003e SSL/TLS termination, but you still need the certificates for STARTTLS\n\u003e connection, so you still need to configure certificates for the SMTP proxy\n\u003e itself.\n\n### Real IP Addresses with HAProxy and SSL/TLS Termination\n\nWe will use Docker Compose for this setup. Let's have some assumptions:\n\n* HAProxy is already up and running. The configuration shown here is only for\n  the SMTP service, so the rest of the configuration is omitted.\n* The SMTP proxy shares the network with HAProxy, so the SMTP proxy is reachable\n  by the HAProxy service by name and on the _listening_ ports. In the examples\n  below we assume the service name is `smtp-proxy`.\n* The domain for SMTP and HTTP (web UI) is the same, in examples it is\n  `smtp.example.com`.\n\n#### HAProxy Configuration\n\nThe HAProxy configuration for SMTP proxy could look like:\n\n```text\nfrontend in-smtp-ssl\n        mode tcp\n        bind :465 ssl crt smtp.example.com.pem\n        option logasap\n\n        acl ACL_smtp_sni ssl_fc_sni -i smtp.example.com\n\n        # Connection is accepted only when SNI smtp.example.com is seen\n        # within the initial 2 seconds of connection\n        tcp-request inspect-delay 2s\n        tcp-request content accept if ACL_smtp_sni\n        tcp-request content reject\n        default_backend out-smtp\n\nbackend out-smtp\n        mode tcp\n        server out-smtp-proxy smtp-proxy:5025 send-proxy-v2\n```\n\nThis will forward the incoming SSL/TLS traffic to the SMTP proxy to port 5025\nafter decrypting the traffic and the communication will start with the\n[PROXY v2][proxy-proto] header (not encrypted with SSL/TLS) containing the\nactual remote IP address.\n\n\u003e [!TIP]\n\u003e If you are forwarding HTTP traffic to the SMTP proxy via HAProxy too, ensure\n\u003e that the backend section contains the `X-Forwarded-*` headers like in this\n\u003e SSL/TLS termination example. Make sure `TRUST_PROXY` is configured in `.env`\n\u003e (see [`.env.example`][env-example]), otherwise forwarded headers are ignored.\n\u003e\n\u003e ```text\n\u003e frontend in-https\n\u003e         mode http\n\u003e         bind :443 ssl crt smtp.example.com.pem\n\u003e         # You will likely have some SNI-matching logic here, omitted for\n\u003e         # clarity\n\u003e         default_backend out-http-smtp\n\u003e\n\u003e backend out-http-smtp\n\u003e         mode http\n\u003e\n\u003e         # The following line is not strictly necessary, but it tells the\n\u003e         # client to use HTTPS for the connection for next 2 years\n\u003e         http-response set-header Strict-Transport-Security max-age=63072000\n\u003e\n\u003e         # The following line deletes any unexpected X-Forwarded-For header,\n\u003e         # which might be crafted by the client\n\u003e         http-request del-header X-Forwarded-For\n\u003e\n\u003e         option forwardfor\n\u003e         http-request set-header X-Forwarded-Port %[dst_port]\n\u003e         http-request set-header X-Forwarded-Proto https\n\u003e         http-request set-header X-Forwarded-Ssl on\n\u003e         server out-smtp-http smtp-proxy:3000\n\u003e ```\n\n\u003e [!IMPORTANT]\n\u003e When forwarding `X-Forwarded-*` headers from HAProxy, configure\n\u003e `TRUST_PROXY` in `.env` (see [`.env.example`][env-example]); otherwise the\n\u003e forwarded headers are ignored.\n\n[proxy-proto]: https://www.haproxy.org/download/2.4/doc/proxy-protocol.txt\n\n#### The go-mmproxy Configuration\n\nWe need to ensure that `go-mmproxy` listens for the connections. For details\ncheck the [GitHub page][go-mmproxy] and especially Cloudflare's Blog post\nabout [mmproxy configuration][cloudflare-mmproxy]. Here is the updated Docker\nCompose configuration for the SMTP proxy:\n\n```yaml\nservices:\n  smtp-proxy:\n    restart: always\n    image: microsoft-smtp-oauth2-proxy\n    volumes:\n      - ./data:/app/data\n    environment:\n      APP_SECRETS: \"my-app-id:my-secret\"\n      SESSION_SECRET: \"my-session-secret\"\n      SMTP_PUBLIC_HOST: smtp.example.com\n      SMTP_PORT: 25\n    ports:\n      - \"80:3000\"\n    networks:\n      - haproxy\n  mmproxy:\n    build:\n      context: .\n      dockerfile: ./mmproxy-Dockerfile\n    restart: always\n    cap_add:\n      - NET_ADMIN\n    command: [ \"-l\", \"0.0.0.0:5025\", \"-4\", \"127.0.0.1:25\", \"-6\", \"[::1]:25\", \"-v\", \"1\" ]\n    sysctls:\n      - net.ipv4.ip_nonlocal_bind=1\n      - net.ipv4.conf.all.route_localnet=1\n      - net.ipv4.conf.default.route_localnet=1\n      - net.ipv4.conf.eth0.route_localnet=1\n      - net.ipv4.tcp_rfc1337=1\n      - net.ipv4.tcp_sack=0\n      - net.ipv4.tcp_dsack=0\n      - net.ipv4.tcp_fack=0\n      - net.ipv4.tcp_slow_start_after_idle=0\n    network_mode: \"service:smtp-proxy\"\n    depends_on:\n      smtp-proxy:\n        condition: service_started\nnetworks:\n  haproxy:\n    external: true\n    name: haproxy_services\n```\n\nThis expects that the HAProxy's Docker Compose project name is `haproxy` (that\nis the name of the parent folder of the corresponding\n`docker-compose.yaml` file or a `COMPOSE_PROJECT_NAME` environment variable\nvalue) and the network inside is named `services`, thus the name\n`haproxy_services`. If that is not the case, update the configuration.\n\nYou also need the following `mmproxy-Dockerfile` file:\n\n```dockerfile\nFROM debian:trixie-slim\n\nRUN apt-get update \\\n \u0026\u0026 DEBCONF_NOWARNINGS=yes DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends apt-utils \\\n \u0026\u0026 DEBIAN_FRONTEND=noninteractive \\\n        apt-get install -y --no-install-recommends \\\n            go-mmproxy \\\n            iptables \\\n            iproute2 \\\n \u0026\u0026 rm -rf /var/lib/apt/lists/*\n\nCOPY --chmod=775 ./mmproxy-entrypoint.sh /entrypoint.sh\nENTRYPOINT [ \"/entrypoint.sh\" ]\n```\n\nand the `mmproxy-entrypoint.sh` entrypoint:\n\n```bash\n#!/bin/sh\niptables -t mangle -I PREROUTING -m mark --mark 123 -m comment --comment mmproxy -j CONNMARK --save-mark\nip6tables -t mangle -I PREROUTING -m mark --mark 123 -m comment --comment mmproxy -j CONNMARK --save-mark\niptables -t mangle -I OUTPUT -m connmark --mark 123 -m comment --comment mmproxy -j CONNMARK --restore-mark\nip6tables -t mangle -I OUTPUT -m connmark --mark 123 -m comment --comment mmproxy -j CONNMARK --restore-mark\nip rule add fwmark 123 lookup 100\nip -6 rule add fwmark 123 lookup 100\nip route add local 0.0.0.0/0 dev lo table 100\nip -6 route add local ::/0 dev lo table 100\n\nexec /usr/bin/go-mmproxy -mark 123 \"$@\"\n```\n\nAfter that you can spin-up the service with `docker compose up --detach` and\nenjoy.\n\n[cloudflare-mmproxy]: https://blog.cloudflare.com/mmproxy-creative-way-of-preserving-client-ips-in-spectrum/\n\n## About the Project\n\n### Why?\n\nMicrosoft SMTP servers require modern OAuth2 authentication for personal\naccounts since [1.\u0026thinsp;1.\u0026thinsp;2023][ms-basic-disabled], but the\nauthentication model might not be supported by all SMTP clients, especially the\ncommand-line ones. Even Gmail\u0026apos;s \u003cq\u003eSend mail as\u003c/q\u003e feature does not\nfeature a built-in support for OAuth2 authentication, so a proxy server is\nneeded to bypass the unsupported authentication model.\n\n[ms-basic-disabled]: https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online\n\n### Why SMTP?\n\nThis project started as a fork of [Gmail to Outlook proxy][gtop] for personal\nuse. But the original project suffers from few issues because of the email\nsending API it uses – it does not keep the original sender\u0026apos;s name and does\nnot support Blind Carbon Copy (BCC) receivers. This project aims to fix these\nissues by going back to the basics and using the SMTP protocol directly.\n\n[gtop]: https://github.com/jasperchan/gmail-to-outlook-proxy\n\n### Why Next.js?\n\nI decided to keep the same frameworks as the original project to learn something\nabout Node.js and Next.js (thus the similar look-and-feel). I needed to learn\nthe following:\n\n* SMTP Protocol and its extensions, like STARTTLS, AUTH, chunking (BDAT),\n  pipelining etc. I got a lot of information from Nodemailer implementation\n  of [client][nodemailer-client] and [server][nodemailer-server].\n* TypeScript, Node.js and handling of asynchronous network operations. Spoiler:\n  the biggest discovery was that all network callbacks are made inside a\n  `process.nextTick()` callback. After that the implementation went like a\n  breeze.\n* React – I had some basics from Udemy courses, but never used it, actually.\n* Next.js – nice and fast framework (with Turbopack) doing a lot of things for\n  you.\n\n[nodemailer-client]: https://github.com/nodemailer/nodemailer\n\n[nodemailer-server]: https://github.com/nodemailer/smtp-server\n\n### How?\n\nThe SMTP server implementation started from Gemini 2.0 code, which I completely\nrewrote. But it was superb to have some starting point, it felt like a code from\na medior software engineer with some junior habits 😅.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foldium%2Fmicrosoft-smtp-oauth2-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foldium%2Fmicrosoft-smtp-oauth2-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foldium%2Fmicrosoft-smtp-oauth2-proxy/lists"}