master 81da93e8a419 cached
169 files
1.9 MB
733.5k tokens
395 symbols
1 requests
Download .txt
Showing preview only (2,057K chars total). Download the full file or copy to clipboard to get everything.
Repository: reacherhq/check-if-email-exists
Branch: master
Commit: 81da93e8a419
Files: 169
Total size: 1.9 MB

Directory structure:
gitextract_puvhqcc5/

├── .dockerignore
├── .editorconfig
├── .envrc
├── .gitbook.yaml
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── config.yml
│   │   └── issue_form.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── deploy_backend.yml
│       ├── deploy_cli.yml
│       ├── pr.yml
│       └── pr_cli.yml
├── .gitignore
├── .sqlx/
│   ├── query-068521cf9e77f563b3791cce500d95660c56e852770a4eac47576089e704322a.json
│   ├── query-0968b040845ecce236576f65df3d3648f7ce03bc5ae0ebe9f36f878c309061c8.json
│   ├── query-1039a6d3d732a86b9b3b2e19e6c8e3c857125d1c4cf916ac32789bfd0176b6b5.json
│   ├── query-13862fe23ea729215fb1cfee3aadc14dfa9373dc8137c4f1da199e3ae66efd50.json
│   ├── query-1a964da4784832e5f631f2c2e727382532c86c7e66e46254d72ef0af03021975.json
│   ├── query-1e5a713008953f95d626e578ed0f2bc2cfc2b1599d8c5d09d11287a661fccd0e.json
│   ├── query-323970c6cb4f5d4b7c0cb3293d1e6fd146c7d682e28e6cb72de6bbe14a3e04b7.json
│   ├── query-47af0157fa867e147e49d80b121b1881df93a6619434a1fd1fc9a58315b4044b.json
│   ├── query-594dfc319a34dfdcf316758798a9ad099c298480b4f1dabc69b1dc6e2f4687a0.json
│   ├── query-96a32768eb750971bad4df10560b0331bf52b44dbd59020369adbbc6bc8dc830.json
│   ├── query-981f650b6c663feeae8a93e7ecf86326e7a5e6d5c8fd03c03565d86982d0381a.json
│   ├── query-ac5e197ca20a1393e4ea45248d5e702c0edbbf57624f2bb416f0fd0401a44dcf.json
│   ├── query-d682d43a07f3969bcb3113ae3600766e9197d48bdc7ccb73a53c938cb45f910f.json
│   ├── query-de8a3af8119e17c38b2f60cafac3b712359553bc295db5689f254f623d172326.json
│   └── query-f58d4d05a6ab4c1ffda39396df4c403f7588266ae8d954985fc1eda9751febcc.json
├── .well-known/
│   └── funding-manifest-urls
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE.AGPL
├── LICENSE.md
├── Makefile
├── README.md
├── backend/
│   ├── Cargo.toml
│   ├── Dockerfile
│   ├── README.md
│   ├── backend_config.toml
│   ├── docker.sh
│   ├── migrations/
│   │   ├── 20210316025847_setup.down.sql
│   │   ├── 20210316025847_setup.up.sql
│   │   ├── 20210921115907_clear.down.sql
│   │   ├── 20210921115907_clear.up.sql
│   │   ├── 20211013151757_fix_mq_latest_message.down.sql
│   │   ├── 20211013151757_fix_mq_latest_message.up.sql
│   │   ├── 20220117025847_email_data.down.sql
│   │   ├── 20220117025847_email_data.up.sql
│   │   ├── 20220208120856_fix_concurrent_poll.down.sql
│   │   ├── 20220208120856_fix_concurrent_poll.up.sql
│   │   ├── 20220713122907_fix-clear_all-keep-nil-message.down.sql
│   │   ├── 20220713122907_fix-clear_all-keep-nil-message.up.sql
│   │   ├── 20220810141100_result_created_at.down.sql
│   │   ├── 20220810141100_result_created_at.up.sql
│   │   ├── 20240929230957_v1_worker_results.down.sql
│   │   ├── 20240929230957_v1_worker_results.up.sql
│   │   └── README.md
│   ├── openapi.json
│   ├── scripts/
│   │   └── debian11.sh
│   ├── src/
│   │   ├── bin/
│   │   │   └── prune_db.rs
│   │   ├── config.rs
│   │   ├── http/
│   │   │   ├── error.rs
│   │   │   ├── mod.rs
│   │   │   ├── v0/
│   │   │   │   ├── bulk/
│   │   │   │   │   ├── db.rs
│   │   │   │   │   ├── error.rs
│   │   │   │   │   ├── get.rs
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   ├── post.rs
│   │   │   │   │   ├── results/
│   │   │   │   │   │   ├── csv_helper.rs
│   │   │   │   │   │   └── mod.rs
│   │   │   │   │   └── task.rs
│   │   │   │   ├── check_email/
│   │   │   │   │   ├── backwardcompat.rs
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── post.rs
│   │   │   │   └── mod.rs
│   │   │   ├── v1/
│   │   │   │   ├── bulk/
│   │   │   │   │   ├── get_progress.rs
│   │   │   │   │   ├── get_results/
│   │   │   │   │   │   ├── csv_helper.rs
│   │   │   │   │   │   └── mod.rs
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── post.rs
│   │   │   │   ├── check_email/
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── post.rs
│   │   │   │   └── mod.rs
│   │   │   └── version/
│   │   │       ├── get.rs
│   │   │       └── mod.rs
│   │   ├── lib.rs
│   │   ├── main.rs
│   │   ├── storage/
│   │   │   ├── commercial_license_trial.rs
│   │   │   ├── error.rs
│   │   │   ├── mod.rs
│   │   │   └── postgres.rs
│   │   ├── throttle.rs
│   │   └── worker/
│   │       ├── consume.rs
│   │       ├── do_work.rs
│   │       ├── mod.rs
│   │       └── single_shot.rs
│   └── tests/
│       ├── README.md
│       └── check_email.rs
├── ci/
│   ├── build.bash
│   ├── build.ps1
│   ├── common.bash
│   ├── set_rust_version.bash
│   └── test.bash
├── cli/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       └── main.rs
├── core/
│   ├── Cargo.toml
│   └── src/
│       ├── haveibeenpwned.rs
│       ├── lib.rs
│       ├── misc/
│       │   ├── b2c.txt
│       │   ├── gravatar.rs
│       │   ├── mod.rs
│       │   └── roles.txt
│       ├── mx/
│       │   └── mod.rs
│       ├── rules.json
│       ├── rules.rs
│       ├── smtp/
│       │   ├── connect.rs
│       │   ├── error.rs
│       │   ├── gmail.rs
│       │   ├── headless.rs
│       │   ├── http_api.rs
│       │   ├── mod.rs
│       │   ├── outlook/
│       │   │   ├── headless.rs
│       │   │   ├── microsoft365.rs
│       │   │   └── mod.rs
│       │   ├── parser.rs
│       │   ├── verif_method.rs
│       │   └── yahoo/
│       │       ├── api.rs
│       │       ├── headless.rs
│       │       └── mod.rs
│       ├── syntax/
│       │   ├── mod.rs
│       │   └── normalize.rs
│       └── util/
│           ├── input_output.rs
│           ├── mod.rs
│           ├── sentry.rs
│           └── ser_with_display.rs
├── docs/
│   ├── README.md
│   ├── SUMMARY.md
│   ├── advanced/
│   │   ├── migrations/
│   │   │   ├── README.md
│   │   │   ├── bulk.md
│   │   │   ├── docker-environment-variables.md
│   │   │   ├── migrating-from-0.7-to-0.10-beta.md
│   │   │   └── reacher-configuration-v0.10.md
│   │   ├── openapi/
│   │   │   ├── README.md
│   │   │   ├── v0-check_email.md
│   │   │   ├── v1-bulk.md
│   │   │   └── v1-check_email.md
│   │   └── run-your-own-proxy.md
│   ├── getting-started/
│   │   ├── is-reachable.md
│   │   └── quickstart.md
│   └── self-hosting/
│       ├── debugging-reacher.md
│       ├── install.md
│       ├── licensing/
│       │   ├── README.md
│       │   └── commercial-license-trial.md
│       ├── proxies/
│       │   ├── README.md
│       │   └── multiple-proxies.md
│       ├── reacher-configuration-v0.10.md
│       ├── saas-vs-self-host.md
│       └── scaling-for-production/
│           ├── README.md
│           ├── option-1-manage-scaling-yourself.md
│           └── option-2-rabbitmq-based-queue-architecture.md
├── rabbitmq/
│   └── docker-compose.yaml
├── rustfmt.toml
└── sqs/
    ├── .gitignore
    ├── Cargo.toml
    ├── Dockerfile
    ├── Makefile
    ├── README.md
    ├── main.tf
    └── src/
        └── main.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.git
.github
.vscode
ci
target/
Dockerfile.*
docs/
.env


================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true

[*]
indent_style=tab
indent_size=tab
tab_width=4
end_of_line=lf
charset=utf-8
trim_trailing_whitespace=true
max_line_length=80
insert_final_newline=true

[*.{yml,sh}]
indent_style=space
indent_size=2
tab_width=8
end_of_line=lf


================================================
FILE: .envrc
================================================
# Config for direnv
# https://direnv.net/
dotenv


================================================
FILE: .gitbook.yaml
================================================
root: ./docs/


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true


================================================
FILE: .github/ISSUE_TEMPLATE/issue_form.yml
================================================
name: Bug Report
labels: bug
description: Create a bug report to help us improve.
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: input
    id: email
    attributes:
      label: Email to check
      description: To preserve privacy, don't use a full email. But if relevant, please tell us which provider is used (e.g. gmail, hotmail...)
      placeholder: ex. example.com
    validations:
      required: false
  - type: input
    id: server
    attributes:
      label: From where did you run check-if-email-exists?
      description: Was it from Reacher dashboard? Or give the provider where you installed check-if-email-exists
      placeholder: ex. Reacher Dashboard, or OVH, Heroku, Digital Ocean...
    validations:
      required: false
  - type: input
    id: version
    attributes:
      label: Version of check-if-email-exists (if running it yourself)
      description: When you're running check-if-email-exists yourself, which version are you using? You can also put the Docker tag you're using.
    validations:
      required: false
  - type: textarea
    id: what-happened
    attributes:
      label: What happened?
      description: Also tell us, what did you expect to happen?
      placeholder: Tell us what you see!
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Relevant log output
      description: Please copy and paste any relevant log output when running with `RUST_LOG=debug`. This will be automatically formatted into code, so no need for backticks.
      render: shell


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: cargo
  directory: "/"
  schedule:
    interval: weekly
    time: "02:00"
    timezone: Europe/Berlin
  open-pull-requests-limit: 10


================================================
FILE: .github/workflows/deploy_backend.yml
================================================
name: deploy_backend

on:
  push:
    tags:
      - "v*.*.*"

jobs:
  get-tag:
    runs-on: ubuntu-latest
    outputs:
      GITHUB_TAG: ${{ steps.vars.outputs.GITHUB_TAG }}
    steps:
      - uses: actions/checkout@master
      - name: Set GITHUB_TAG arg
        id: vars
        run: echo ::set-output name=GITHUB_TAG::${GITHUB_REF:10} # Remove /refs/head/

  docker-publish:
    runs-on: ubuntu-latest
    needs: get-tag
    steps:
      - uses: actions/checkout@master
      - name: Print version
        run: echo "Publishing reacherhq/backend:${{ needs.get-tag.outputs.GITHUB_TAG }}"
      - name: Publish to Docker Hub
        uses: elgohr/Publish-Docker-Github-Action@v5
        with:
          name: reacherhq/backend
          dockerfile: backend/Dockerfile
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
          tags: "${{ needs.get-tag.outputs.GITHUB_TAG }},beta"


================================================
FILE: .github/workflows/deploy_cli.yml
================================================
name: deploy_cli

on:
  push:
    tags:
      - "v*.*.*"

env:
  # TODO: Rename to your binary
  BIN: check_if_email_exists

jobs:
  # This job downloads and stores `cross` as an artifact, so that it can be
  # redownloaded across all of the jobs. Currently this copied pasted between
  # `mean_bean_ci.yml` and `mean_bean_deploy.yml`. Make sure to update both places when making
  # changes.
  install-cross:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
        with:
          depth: 50
      - uses: XAMPPRocky/get-github-release@v1
        id: cross
        with:
          owner: rust-embedded
          repo: cross
          matches: ${{ matrix.platform }}
          token: ${{ secrets.GITHUB_TOKEN }}
      - uses: actions/upload-artifact@v4
        with:
          name: cross-${{ matrix.platform }}
          path: ${{ steps.cross.outputs.install_path }}
    strategy:
      matrix:
        platform: [linux-musl, apple-darwin]

  windows:
    runs-on: windows-latest
    if: false # Disable Windows tests for now, because Reacher Worker is not supported on Windows
    needs: install-cross
    steps:
      - uses: actions/checkout@v2
      - uses: shogo82148/actions-setup-perl@v1
        with:
          perl-version: "5.32"
          distribution: strawberry
      - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }}
        shell: bash
      - run: ci/build.ps1 cargo ${{ matrix.target }} RELEASE
      - run: tar -czvf ${{ env.BIN }}.tar.gz --directory=target/${{ matrix.target }}/release ${{ env.BIN }}.exe
      - uses: XAMPPRocky/create-release@v1.0.2
        id: create_release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: false
          prerelease: false
      - uses: actions/upload-release-asset@v1
        id: upload-release-asset
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ${{ env.BIN }}.tar.gz
          asset_name: ${{ env.BIN }}-${{ matrix.target }}.tar.gz
          asset_content_type: application/gzip
    strategy:
      fail-fast: true
      matrix:
        channel: [stable]
        target:
          - x86_64-pc-windows-msvc
          - i686-pc-windows-msvc
  macos:
    runs-on: macos-latest
    needs: install-cross
    strategy:
      matrix:
        target:
          # macOS
          - x86_64-apple-darwin
          # iOS
          # - aarch64-apple-ios
          # - armv7-apple-ios
          # - armv7s-apple-ios
          # - i386-apple-ios
          # - x86_64-apple-ios
    steps:
      - uses: actions/checkout@v2
      - uses: actions/download-artifact@v4
        with:
          name: cross-apple-darwin
          path: /usr/local/bin/
      - run: chmod +x /usr/local/bin/cross

      - run: ci/set_rust_version.bash stable ${{ matrix.target }}
      - run: ci/build.bash cross ${{ matrix.target }} RELEASE
      - run: tar -czvf ${{ env.BIN }}.tar.gz --directory=target/${{ matrix.target }}/release ${{ env.BIN }}
      - uses: XAMPPRocky/create-release@v1.0.2
        id: create_release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: false
          prerelease: false
      - uses: actions/upload-release-asset@v1
        id: upload-release-asset
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ${{ env.BIN }}.tar.gz
          asset_name: ${{ env.BIN }}-${{ matrix.target }}.tar.gz
          asset_content_type: application/gzip

  linux:
    runs-on: ubuntu-latest
    needs: install-cross
    strategy:
      matrix:
        target:
          # WASM, off by default as most rust projects aren't compatible yet.
          # - wasm32-unknown-emscripten
          # Linux
          - aarch64-unknown-linux-gnu
          # - arm-unknown-linux-gnueabi
          # - armv7-unknown-linux-gnueabihf
          # - i686-unknown-linux-gnu
          # - i686-unknown-linux-musl
          # - mips-unknown-linux-gnu
          # - mips64-unknown-linux-gnuabi64
          # - mips64el-unknown-linux-gnuabi64
          # - mipsel-unknown-linux-gnu
          # - powerpc-unknown-linux-gnu
          # - powerpc64-unknown-linux-gnu
          # - powerpc64le-unknown-linux-gnu
          # - s390x-unknown-linux-gnu
          - x86_64-unknown-linux-gnu
          - x86_64-unknown-linux-musl
          # Android
          # - aarch64-linux-android
          # - arm-linux-androideabi
          # - armv7-linux-androideabi
          # - i686-linux-android
          # - x86_64-linux-android
          # *BSD
          # The FreeBSD targets can have issues linking so they are disabled
          # by default.
          # - i686-unknown-freebsd
          # - x86_64-unknown-freebsd
          # - x86_64-unknown-netbsd
          # Solaris
          # - sparcv9-sun-solaris
          # Bare Metal
          # These are no-std embedded targets, so they will only build if your
          # crate is `no_std` compatible.
          # - thumbv6m-none-eabi
          # - thumbv7em-none-eabi
          # - thumbv7em-none-eabihf
          # - thumbv7m-none-eabi
    steps:
      - uses: actions/checkout@v2
      - uses: actions/download-artifact@v4
        with:
          name: cross-linux-musl
          path: /tmp/
      - run: chmod +x /tmp/cross

      - run: ci/set_rust_version.bash stable ${{ matrix.target }}
      - run: ci/build.bash /tmp/cross ${{ matrix.target }} RELEASE
      - run: tar -czvf ${{ env.BIN }}.tar.gz --directory=target/${{ matrix.target }}/release ${{ env.BIN }}
      - uses: XAMPPRocky/create-release@v1.0.2
        id: create_release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: false
          prerelease: false
      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ${{ env.BIN }}.tar.gz
          asset_name: ${{ env.BIN }}-${{ matrix.target }}.tar.gz
          asset_content_type: application/gzip


================================================
FILE: .github/workflows/pr.yml
================================================
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md

name: pr

on:
  pull_request:
  push:
    branches:
      - master

# For backend, run builds and tests without sqlx's compile time query checks
# https://github.com/launchbadge/sqlx/blob/master/sqlx-cli/README.md#force-building-in-offline-mode
env:
  SQLX_OFFLINE: true

jobs:
  # Cargo check.
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout sources
        uses: actions/checkout@v2

      - name: Install stable toolchain
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true

      - name: Run cargo check
        uses: actions-rs/cargo@v1
        with:
          command: check
          args: --all

  # Cargo test.
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout sources
        uses: actions/checkout@v2

      - name: Install stable toolchain
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true

      - name: Run cargo test
        uses: actions-rs/cargo@v1
        with:
          command: test
          args: --all

  # Cargo fmt and clippy
  lints:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout sources
        uses: actions/checkout@v2

      - name: Install stable toolchain
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true
          components: rustfmt, clippy

      - name: Run cargo fmt
        uses: actions-rs/cargo@v1
        with:
          command: fmt
          args: --all -- --check

      - name: Run cargo clippy
        uses: actions-rs/cargo@v1
        with:
          command: clippy
          args: --all -- -A deprecated


================================================
FILE: .github/workflows/pr_cli.yml
================================================
# CI actions for the CLI only. We test that the CLI works on various platforms.
name: cli

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  # This job downloads and stores `cross` as an artifact, so that it can be
  # redownloaded across all of the jobs. Currently this copied pasted between
  # `ci.yml` and `deploy.yml`. Make sure to update both places when making
  # changes.
  install-cross:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: XAMPPRocky/get-github-release@v1
        id: cross
        with:
          owner: rust-embedded
          repo: cross
          matches: ${{ matrix.platform }}
          token: ${{ secrets.GITHUB_TOKEN }}
      - uses: actions/upload-artifact@v4
        with:
          name: cross-${{ matrix.platform }}
          path: ${{ steps.cross.outputs.install_path }}
    strategy:
      matrix:
        platform: [linux-musl, apple-darwin]

  macos:
    runs-on: macos-latest
    needs: install-cross
    steps:
      - uses: actions/checkout@v2
      - uses: actions/download-artifact@v4
        with:
          name: cross-apple-darwin
          path: /usr/local/bin/

      - run: chmod +x /usr/local/bin/cross

      - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }}
      - run: ci/build.bash cross ${{ matrix.target }}
        # Only test on macOS platforms since we can't simulate the others.
      - run: ci/test.bash cross ${{ matrix.target }}
        if: matrix.target == 'x86_64-apple-darwin'

    strategy:
      fail-fast: true
      matrix:
        channel: [stable] # channel: [stable, beta, nightly]
        target:
          # macOS
          - x86_64-apple-darwin
          # iOS
          # - aarch64-apple-ios
          # - x86_64-apple-ios

  windows:
    if: false # Disable Windows tests for now, because Reacher Worker is not supported on Windows
    runs-on: windows-latest
    needs: install-cross
    steps:
      - uses: actions/checkout@v2
      - uses: shogo82148/actions-setup-perl@v1
        with:
          perl-version: "5.32"
          distribution: strawberry

      - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }}
        shell: bash
      - run: ci/build.ps1 cargo ${{ matrix.target }}
      - run: ci/test.bash cargo ${{ matrix.target }}
        shell: bash
    strategy:
      fail-fast: true
      matrix:
        channel: [stable]
        target:
          - x86_64-pc-windows-msvc
          - i686-pc-windows-msvc

  linux:
    runs-on: ubuntu-latest
    needs: install-cross
    steps:
      - uses: actions/checkout@v2

      - name: Download Cross
        uses: actions/download-artifact@v4
        with:
          name: cross-linux-musl
          path: /tmp/
      - run: chmod +x /tmp/cross
      - run: ci/set_rust_version.bash ${{ matrix.channel }} ${{ matrix.target }}
      - run: ci/build.bash /tmp/cross ${{ matrix.target }}
        # These targets have issues with being tested so they are disabled
        # by default. You can try disabling to see if they work for
        # your project.
      - run: ci/test.bash /tmp/cross ${{ matrix.target }}
        if: |
          !contains(matrix.target, 'android') &&
          !contains(matrix.target, 'bsd') &&
          !contains(matrix.target, 'solaris') &&
          matrix.target != 'armv5te-unknown-linux-musleabi' &&
          matrix.target != 'sparc64-unknown-linux-gnu'

    strategy:
      fail-fast: true
      matrix:
        channel: [stable] # channel: [stable, beta, nightly]
        target:
          # WASM, off by default as most rust projects aren't compatible yet.
          # - wasm32-unknown-emscripten
          # Linux
          - aarch64-unknown-linux-gnu
          - aarch64-unknown-linux-musl
          # - arm-unknown-linux-gnueabi
          # - arm-unknown-linux-gnueabihf
          # - arm-unknown-linux-musleabi
          # - arm-unknown-linux-musleabihf
          # - armv5te-unknown-linux-musleabi
          # - armv7-unknown-linux-gnueabihf
          # - armv7-unknown-linux-musleabihf
          # - i586-unknown-linux-gnu
          # - i586-unknown-linux-musl
          # - i686-unknown-linux-gnu
          # - i686-unknown-linux-musl
          # - mips-unknown-linux-gnu
          # - mips-unknown-linux-musl
          # - mips64-unknown-linux-gnuabi64
          # - mips64el-unknown-linux-gnuabi64
          # - mipsel-unknown-linux-gnu
          # - mipsel-unknown-linux-musl
          # - powerpc-unknown-linux-gnu
          # - powerpc64-unknown-linux-gnu
          # - powerpc64le-unknown-linux-gnu
          # - s390x-unknown-linux-gnu
          - x86_64-unknown-linux-gnu
          - x86_64-unknown-linux-musl
          # - sparc64-unknown-linux-gnu
          # Android
          # - aarch64-linux-android
          # - arm-linux-androideabi
          # - armv7-linux-androideabi
          # - i686-linux-android
          # - x86_64-linux-android
          # *BSD
          # The FreeBSD targets can have issues linking so they are disabled
          # by default.
          # - i686-unknown-freebsd
          # - x86_64-unknown-freebsd
          # - x86_64-unknown-netbsd
          # DragonFly (Doesn't currently work)
          # - x86_64-unknown-dragonfly
          # Solaris
          # - sparcv9-sun-solaris
          # - x86_64-sun-solaris
          # Bare Metal
          # These are no-std embedded targets, so they will only build if your
          # crate is `no_std` compatible.
          # - thumbv6m-none-eabi
          # - thumbv7em-none-eabi
          # - thumbv7em-none-eabihf
          # - thumbv7m-none-eabi


================================================
FILE: .gitignore
================================================
target
**/*.rs.bk
.DS_Store
private

# docker-compose
backend/postgres_data

# Output when debugging Hotmail password recovery using a headless browser.
hotmail.jpeg

# sensitive data
/test_suite/src/sensitive_fixtures/*.json
run.sh
.env


================================================
FILE: .sqlx/query-068521cf9e77f563b3791cce500d95660c56e852770a4eac47576089e704322a.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\t\t\t\tINSERT INTO v1_task_result (payload, job_id, extra, error)\n\t\t\t\t\tVALUES ($1, $2, $3, $4)\n\t\t\t\t\tRETURNING id\n\t\t\t\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "id",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Jsonb",
        "Int4",
        "Jsonb",
        "Text"
      ]
    },
    "nullable": [
      false
    ]
  },
  "hash": "068521cf9e77f563b3791cce500d95660c56e852770a4eac47576089e704322a"
}


================================================
FILE: .sqlx/query-0968b040845ecce236576f65df3d3648f7ce03bc5ae0ebe9f36f878c309061c8.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tSELECT id, created_at, total_records FROM v1_bulk_job\n\t\tWHERE id = $1\n\t\tLIMIT 1\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "id",
        "type_info": "Int4"
      },
      {
        "ordinal": 1,
        "name": "created_at",
        "type_info": "Timestamptz"
      },
      {
        "ordinal": 2,
        "name": "total_records",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      false,
      false,
      false
    ]
  },
  "hash": "0968b040845ecce236576f65df3d3648f7ce03bc5ae0ebe9f36f878c309061c8"
}


================================================
FILE: .sqlx/query-1039a6d3d732a86b9b3b2e19e6c8e3c857125d1c4cf916ac32789bfd0176b6b5.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tSELECT result FROM email_results\n\t\tWHERE job_id = $1\n\t\tORDER BY id\n\t\tLIMIT $2 OFFSET $3\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "result",
        "type_info": "Jsonb"
      }
    ],
    "parameters": {
      "Left": [
        "Int4",
        "Int8",
        "Int8"
      ]
    },
    "nullable": [
      true
    ]
  },
  "hash": "1039a6d3d732a86b9b3b2e19e6c8e3c857125d1c4cf916ac32789bfd0176b6b5"
}


================================================
FILE: .sqlx/query-13862fe23ea729215fb1cfee3aadc14dfa9373dc8137c4f1da199e3ae66efd50.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tSELECT\n\t\t\tCOUNT(*) as total_processed,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'safe' THEN 1 END) as safe_count,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'risky' THEN 1 END) as risky_count,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'invalid' THEN 1 END) as invalid_count,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'unknown' THEN 1 END) as unknown_count,\n\t\t\t(SELECT created_at FROM email_results WHERE job_id = $1 ORDER BY created_at DESC LIMIT 1) as finished_at\n\t\tFROM email_results\n\t\tWHERE job_id = $1\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "total_processed",
        "type_info": "Int8"
      },
      {
        "ordinal": 1,
        "name": "safe_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 2,
        "name": "risky_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 3,
        "name": "invalid_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 4,
        "name": "unknown_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 5,
        "name": "finished_at",
        "type_info": "Timestamptz"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      null,
      null,
      null,
      null,
      null,
      null
    ]
  },
  "hash": "13862fe23ea729215fb1cfee3aadc14dfa9373dc8137c4f1da199e3ae66efd50"
}


================================================
FILE: .sqlx/query-1a964da4784832e5f631f2c2e727382532c86c7e66e46254d72ef0af03021975.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\t\tINSERT INTO email_results (job_id, result)\n\t\t\tVALUES ($1, $2)\n\t\t\t",
  "describe": {
    "columns": [],
    "parameters": {
      "Left": [
        "Int4",
        "Jsonb"
      ]
    },
    "nullable": []
  },
  "hash": "1a964da4784832e5f631f2c2e727382532c86c7e66e46254d72ef0af03021975"
}


================================================
FILE: .sqlx/query-1e5a713008953f95d626e578ed0f2bc2cfc2b1599d8c5d09d11287a661fccd0e.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "SELECT total_records FROM v1_bulk_job WHERE id = $1;",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "total_records",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      false
    ]
  },
  "hash": "1e5a713008953f95d626e578ed0f2bc2cfc2b1599d8c5d09d11287a661fccd0e"
}


================================================
FILE: .sqlx/query-323970c6cb4f5d4b7c0cb3293d1e6fd146c7d682e28e6cb72de6bbe14a3e04b7.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tSELECT result FROM v1_task_result\n\t\tWHERE job_id = $1\n\t\tORDER BY id\n\t\tLIMIT $2 OFFSET $3\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "result",
        "type_info": "Jsonb"
      }
    ],
    "parameters": {
      "Left": [
        "Int4",
        "Int8",
        "Int8"
      ]
    },
    "nullable": [
      true
    ]
  },
  "hash": "323970c6cb4f5d4b7c0cb3293d1e6fd146c7d682e28e6cb72de6bbe14a3e04b7"
}


================================================
FILE: .sqlx/query-47af0157fa867e147e49d80b121b1881df93a6619434a1fd1fc9a58315b4044b.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tSELECT id, created_at, total_records FROM bulk_jobs\n\t\tWHERE id = $1\n\t\tLIMIT 1\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "id",
        "type_info": "Int4"
      },
      {
        "ordinal": 1,
        "name": "created_at",
        "type_info": "Timestamptz"
      },
      {
        "ordinal": 2,
        "name": "total_records",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      false,
      false,
      false
    ]
  },
  "hash": "47af0157fa867e147e49d80b121b1881df93a6619434a1fd1fc9a58315b4044b"
}


================================================
FILE: .sqlx/query-594dfc319a34dfdcf316758798a9ad099c298480b4f1dabc69b1dc6e2f4687a0.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "SELECT COUNT(*) FROM v1_task_result WHERE job_id = $1;",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "count",
        "type_info": "Int8"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      null
    ]
  },
  "hash": "594dfc319a34dfdcf316758798a9ad099c298480b4f1dabc69b1dc6e2f4687a0"
}


================================================
FILE: .sqlx/query-96a32768eb750971bad4df10560b0331bf52b44dbd59020369adbbc6bc8dc830.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tINSERT INTO v1_bulk_job (total_records)\n\t\tVALUES ($1)\n\t\tRETURNING id\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "id",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      false
    ]
  },
  "hash": "96a32768eb750971bad4df10560b0331bf52b44dbd59020369adbbc6bc8dc830"
}


================================================
FILE: .sqlx/query-981f650b6c663feeae8a93e7ecf86326e7a5e6d5c8fd03c03565d86982d0381a.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tINSERT INTO bulk_jobs (total_records)\n\t\tVALUES ($1)\n\t\tRETURNING id\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "id",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      false
    ]
  },
  "hash": "981f650b6c663feeae8a93e7ecf86326e7a5e6d5c8fd03c03565d86982d0381a"
}


================================================
FILE: .sqlx/query-ac5e197ca20a1393e4ea45248d5e702c0edbbf57624f2bb416f0fd0401a44dcf.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "SELECT total_records FROM bulk_jobs WHERE id = $1;",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "total_records",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      false
    ]
  },
  "hash": "ac5e197ca20a1393e4ea45248d5e702c0edbbf57624f2bb416f0fd0401a44dcf"
}


================================================
FILE: .sqlx/query-d682d43a07f3969bcb3113ae3600766e9197d48bdc7ccb73a53c938cb45f910f.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\tSELECT\n\t\t\tCOUNT(*) as total_processed,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'safe' THEN 1 END) as safe_count,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'risky' THEN 1 END) as risky_count,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'invalid' THEN 1 END) as invalid_count,\n\t\t\tCOUNT(CASE WHEN result ->> 'is_reachable' LIKE 'unknown' THEN 1 END) as unknown_count,\n\t\t\t(SELECT created_at FROM v1_task_result WHERE job_id = $1 ORDER BY created_at DESC LIMIT 1) as finished_at\n\t\tFROM v1_task_result\n\t\tWHERE job_id = $1\n\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "total_processed",
        "type_info": "Int8"
      },
      {
        "ordinal": 1,
        "name": "safe_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 2,
        "name": "risky_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 3,
        "name": "invalid_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 4,
        "name": "unknown_count",
        "type_info": "Int8"
      },
      {
        "ordinal": 5,
        "name": "finished_at",
        "type_info": "Timestamptz"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      null,
      null,
      null,
      null,
      null,
      null
    ]
  },
  "hash": "d682d43a07f3969bcb3113ae3600766e9197d48bdc7ccb73a53c938cb45f910f"
}


================================================
FILE: .sqlx/query-de8a3af8119e17c38b2f60cafac3b712359553bc295db5689f254f623d172326.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "\n\t\t\t\t\tINSERT INTO v1_task_result (payload, job_id, extra, result)\n\t\t\t\t\tVALUES ($1, $2, $3, $4)\n\t\t\t\t\tRETURNING id\n\t\t\t\t\t",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "id",
        "type_info": "Int4"
      }
    ],
    "parameters": {
      "Left": [
        "Jsonb",
        "Int4",
        "Jsonb",
        "Jsonb"
      ]
    },
    "nullable": [
      false
    ]
  },
  "hash": "de8a3af8119e17c38b2f60cafac3b712359553bc295db5689f254f623d172326"
}


================================================
FILE: .sqlx/query-f58d4d05a6ab4c1ffda39396df4c403f7588266ae8d954985fc1eda9751febcc.json
================================================
{
  "db_name": "PostgreSQL",
  "query": "SELECT COUNT(*) FROM email_results WHERE job_id = $1;",
  "describe": {
    "columns": [
      {
        "ordinal": 0,
        "name": "count",
        "type_info": "Int8"
      }
    ],
    "parameters": {
      "Left": [
        "Int4"
      ]
    },
    "nullable": [
      null
    ]
  },
  "hash": "f58d4d05a6ab4c1ffda39396df4c403f7588266ae8d954985fc1eda9751febcc"
}


================================================
FILE: .well-known/funding-manifest-urls
================================================
https://reacher.email/funding.json


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file. The changes in this project follow [Convention Commits](https://www.conventionalcommits.org/en/v1.0.0/).

# [](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.7...v) (2026-01-15)



## [0.11.7](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.6...v0.11.7) (2026-01-15)


### Bug Fixes

* **smtp:** treat permanent 5.1.1 and 5.7.1 responses as invalid recipients ([#1631](https://github.com/reacherhq/check-if-email-exists/issues/1631)) ([b3d6b07](https://github.com/reacherhq/check-if-email-exists/commit/b3d6b0751ae95c874014d99fb437e4cca4899d14))



## [0.11.6](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.5...v0.11.6) (2025-07-06)


### Bug Fixes

* Bring back `{yahoo,hotmailb2c}_verif_method` ([#1606](https://github.com/reacherhq/check-if-email-exists/issues/1606)) ([3fbe520](https://github.com/reacherhq/check-if-email-exists/commit/3fbe5200a3d8608fbd72c0f2a5917326c1f8ec91))
* Fix rabbitmq docker compose ([7c3856e](https://github.com/reacherhq/check-if-email-exists/commit/7c3856ebec6089b37b3dd30e3c4f13df9fb4e73a))



## [0.11.5](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.4...v0.11.5) (2025-04-29)


### Features

* Add optional timeout on proxy (env var: `RCH__PROXY__TIMEOUT_MS`) ([#1595](https://github.com/reacherhq/check-if-email-exists/issues/1595)) ([0e51eb6](https://github.com/reacherhq/check-if-email-exists/commit/0e51eb686dad6bd2ec827e785bf9c30ccc88cde1))



## [0.11.4](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.3...v0.11.4) (2025-04-28)


### Bug Fixes

* Add "utilisateur inconnu" in invalid parser ([#1594](https://github.com/reacherhq/check-if-email-exists/issues/1594)) ([fb91653](https://github.com/reacherhq/check-if-email-exists/commit/fb9165303e2d7be59ed2fa4f0682e8592bc0c5e7))



## [0.11.3](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.2...v0.11.3) (2025-03-29)


### Bug Fixes

* Fix version in logs ([fa6be78](https://github.com/reacherhq/check-if-email-exists/commit/fa6be7867abae981b0d82fde24e0310b9759ab1f))



## [0.11.2](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.1...v0.11.2) (2025-03-28)


### Bug Fixes

* Remove max requests per minute/day ([07a6d96](https://github.com/reacherhq/check-if-email-exists/commit/07a6d96416f52ac0824e7e7ac665fd2169ddc7ec))
* Show thread ID in logs ([#1579](https://github.com/reacherhq/check-if-email-exists/issues/1579)) ([3388163](https://github.com/reacherhq/check-if-email-exists/commit/3388163d03b66ba92455be8404441e8555a9d53c))


### Reverts

* "Show thread ID in logs ([#1579](https://github.com/reacherhq/check-if-email-exists/issues/1579))" ([56e7838](https://github.com/reacherhq/check-if-email-exists/commit/56e7838f28067b05b58f1fcd166368a915aafbbc))



## [0.11.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.11.0...v0.11.1) (2025-03-24)


### Bug Fixes

* Revert back to using lowest-priority MX record ([#1578](https://github.com/reacherhq/check-if-email-exists/issues/1578)) ([60468b3](https://github.com/reacherhq/check-if-email-exists/commit/60468b3f533491a0dff6a42e7096f34ece19896c))



# [0.11.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.10.1...v0.11.0) (2025-02-19)


* feat!: Allow multiple proxies (#1562) ([eed5a15](https://github.com/reacherhq/check-if-email-exists/commit/eed5a1536af37877f12eebab6481acaa6efa55c5)), closes [#1562](https://github.com/reacherhq/check-if-email-exists/issues/1562)


### Bug Fixes

* **docker:** Fix dockerfile entrypoint ([d1d3326](https://github.com/reacherhq/check-if-email-exists/commit/d1d3326af88a85b2192796d8d2c92ff854b5644d))
* Don't show proxy full info in logs ([2668ce1](https://github.com/reacherhq/check-if-email-exists/commit/2668ce14418076b00f36f18a370070ac1f3754bf))
* Fix AWS login in Action ([6dd6fb0](https://github.com/reacherhq/check-if-email-exists/commit/6dd6fb02b77049d2a9fc2510ed438b1ac8ab60aa))
* Fix correct Dockerfile in Action ([4d4d91e](https://github.com/reacherhq/check-if-email-exists/commit/4d4d91ea7b43678d472e4f7f6ae6952625b2f478))
* Fixed inverted hello-name and from-email in CLI ([#1565](https://github.com/reacherhq/check-if-email-exists/issues/1565)) ([a53561e](https://github.com/reacherhq/check-if-email-exists/commit/a53561e087593ccc887b45943f54855b9cc6ae85))
* Improve logging, add retries for Yahoo headless, switch to rustls ([#1549](https://github.com/reacherhq/check-if-email-exists/issues/1549)) ([b1377db](https://github.com/reacherhq/check-if-email-exists/commit/b1377db2b32155d766a09a76864fc9b0990833e6))
* Make new config backwards-compatible ([#1567](https://github.com/reacherhq/check-if-email-exists/issues/1567)) ([b824e2c](https://github.com/reacherhq/check-if-email-exists/commit/b824e2c988ee4eef021b97fc65ebcfa36a166d7f))
* Reinstate proxy in JSON request ([#1569](https://github.com/reacherhq/check-if-email-exists/issues/1569)) ([c36e6e0](https://github.com/reacherhq/check-if-email-exists/commit/c36e6e09c9079de210d288b84d79b984e2ea77f0))


### Features

* Add `misc.is_b2c` field ([#1553](https://github.com/reacherhq/check-if-email-exists/issues/1553)) ([14a6759](https://github.com/reacherhq/check-if-email-exists/commit/14a6759d805d2051a4a1e1d81588279cb9c85336))
* Add AWS SQS support ([#1554](https://github.com/reacherhq/check-if-email-exists/issues/1554)) ([92be54e](https://github.com/reacherhq/check-if-email-exists/commit/92be54ebfe4a2d19101141f55e94fc8e9588ff95))


### BREAKING CHANGES

* - The `hello_name`, `from_email`, `smtp_timeout`, `retries` and `proxy` settings have been moved to inside the new `verif_method` field, which is now the centralized place to configure how each email is verified (categorized by email provider).



# [0.10.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.9.1...v0.10.0) (2024-12-15)


* feat(core)!: Update async-smtp to 0.9 (#1520) ([297ce4f](https://github.com/reacherhq/check-if-email-exists/commit/297ce4f11994b483faa015bebe4abf550eb77e11)), closes [#1520](https://github.com/reacherhq/check-if-email-exists/issues/1520)
* feat!: Add `/v1/{check_email,bulk}` endpoints with throttle&concurrency (#1537) ([08522e4](https://github.com/reacherhq/check-if-email-exists/commit/08522e4326bbcbc980cf501d5d994d0c17222561)), closes [#1537](https://github.com/reacherhq/check-if-email-exists/issues/1537)
* fix(core)!: Clean up CheckEmailInput (#1531) ([b97b9ff](https://github.com/reacherhq/check-if-email-exists/commit/b97b9ff9b91bdfbf18e5c0892559e87e7cd5e16c)), closes [#1531](https://github.com/reacherhq/check-if-email-exists/issues/1531)
* refactor!: Use config-rs instead of env vars (#1530) ([bcd2dc8](https://github.com/reacherhq/check-if-email-exists/commit/bcd2dc867b7dc2bdaeb70097fd14109c2a40da17)), closes [#1530](https://github.com/reacherhq/check-if-email-exists/issues/1530)
* feat(backend)!: Remove /v0/bulk endpoints (#1421) ([522f324](https://github.com/reacherhq/check-if-email-exists/commit/522f32448416cd75a70ddb51038e50d06c3130b4)), closes [#1421](https://github.com/reacherhq/check-if-email-exists/issues/1421)
* fix!(core): Bump timeout to 45s, set retries to 1 (#1406) ([22e8e3e](https://github.com/reacherhq/check-if-email-exists/commit/22e8e3e86ce922e76262f33ceeec2388334a5264)), closes [#1406](https://github.com/reacherhq/check-if-email-exists/issues/1406)
* refactor!: Use verify method for known providers (#1366) ([5ca4dfa](https://github.com/reacherhq/check-if-email-exists/commit/5ca4dfa5ec38fba0ec7cfb052106da8d6af4df44)), closes [#1366](https://github.com/reacherhq/check-if-email-exists/issues/1366)


### Bug Fixes

* Add backend_name in /v0/check_email ([a738fae](https://github.com/reacherhq/check-if-email-exists/commit/a738faec99942d20b817298f7850e84ab3e74835))
* Add HoneyPot rule ([fb428ef](https://github.com/reacherhq/check-if-email-exists/commit/fb428ef42586641711dfd10190514ff5aa24583d))
* **backend:** CSV download retrieves all results ([#1362](https://github.com/reacherhq/check-if-email-exists/issues/1362)) ([b3670fc](https://github.com/reacherhq/check-if-email-exists/commit/b3670fcaebce05a0aab09bcc3253134cb3c643c1))
* **backend:** Fix docker CTRL+C ([3a7245f](https://github.com/reacherhq/check-if-email-exists/commit/3a7245f9a47e8332d682d437d9492559e5adf66f))
* **backend:** Fix env var for multiple queues ([ed19166](https://github.com/reacherhq/check-if-email-exists/commit/ed191662b18c62f397b4fed6b95249b5aa76c423))
* **backend:** Update sqlx to 0.7 ([#1390](https://github.com/reacherhq/check-if-email-exists/issues/1390)) ([7198f87](https://github.com/reacherhq/check-if-email-exists/commit/7198f87de92ab403cdc1e7c68667cdef9db96085))
* **ci:** actions/download-artifact@v4 ([ec48fec](https://github.com/reacherhq/check-if-email-exists/commit/ec48fec4bd675cbc33198e27e51b2d5c1f9090b5))
* **ci:** Fix Windows build ([#1397](https://github.com/reacherhq/check-if-email-exists/issues/1397)) ([ab2bb41](https://github.com/reacherhq/check-if-email-exists/commit/ab2bb4184adbd77628a38de6dccf01a1fde029cb))
* **ci:** Use v4 of upload-artifacts ([fa7f438](https://github.com/reacherhq/check-if-email-exists/commit/fa7f438d3afa7b132765107d16f41d6ce7d3b4d9))
* **ci:** Use v4 of upload-artifacts ([b97d181](https://github.com/reacherhq/check-if-email-exists/commit/b97d181b42c72f7a94dfb86acada09d423af1c0e))
* **core:** Fix gmail test ([ea80690](https://github.com/reacherhq/check-if-email-exists/commit/ea80690b4168485ed7e03f4e228a12e276d605b0))
* **core:** Fix hotmail/outlook checks ([5e4bf16](https://github.com/reacherhq/check-if-email-exists/commit/5e4bf16e75e01ba17dd9022934359c9d03f3b0c8))
* **core:** Headless check for Microsoft365 too ([#1346](https://github.com/reacherhq/check-if-email-exists/issues/1346)) ([682cc2d](https://github.com/reacherhq/check-if-email-exists/commit/682cc2d96b93d73f3fca3ba11f03800477c8fb9e))
* **core:** More robust Hotmail invalid check ([ee741f4](https://github.com/reacherhq/check-if-email-exists/commit/ee741f4570050f559395e687da64c64ff9046afb))
* **core:** Prefer empty MX lookup when Err NoRecordsFound ([#1409](https://github.com/reacherhq/check-if-email-exists/issues/1409)) ([d4b5ef9](https://github.com/reacherhq/check-if-email-exists/commit/d4b5ef9696a8c3ff0eaad2d3b5321437bd2a4df3))
* **core:** Use semver in sentry ([03e6c97](https://github.com/reacherhq/check-if-email-exists/commit/03e6c97a7f842b115b367ca942119496d8400024))
* **core:** Use Smtp for Gmail by default ([8e79884](https://github.com/reacherhq/check-if-email-exists/commit/8e79884314f0c1eec5a7964fa686e2c60e7d2209))
* **core:** Use tagged enum representation ([ffde851](https://github.com/reacherhq/check-if-email-exists/commit/ffde851068798adc3372d843a916a121b5caeccb))
* Fix dockerfile ([ce5067e](https://github.com/reacherhq/check-if-email-exists/commit/ce5067e4050e0cf3fa6c022bc7e25e5f15261c2a))
* Fix dockerfile ([83d70d8](https://github.com/reacherhq/check-if-email-exists/commit/83d70d8886730795ff69320e6ebd8e40fdf18d5e))
* Fix dockerfile build ([95aeecb](https://github.com/reacherhq/check-if-email-exists/commit/95aeecbdc9d712a5e7f9e0d547f68da4fa602d61))
* Fix Dockerfiles ([e9fb1e3](https://github.com/reacherhq/check-if-email-exists/commit/e9fb1e33435f89d627d89b98b358f257325dc13b))
* Fix duplicate `yahoo_verif_method` field in default() inputs ([#1428](https://github.com/reacherhq/check-if-email-exists/issues/1428)) ([b7c51d5](https://github.com/reacherhq/check-if-email-exists/commit/b7c51d5caaf21140c174cb419aedaf8fe752f817))
* Only do headless for non-365 hotmail emails ([1c52bdc](https://github.com/reacherhq/check-if-email-exists/commit/1c52bdc75fb201f2e54c62d5f67f50a56c57cb83))
* Put Smtp debug details in Debug struct ([5b71ca5](https://github.com/reacherhq/check-if-email-exists/commit/5b71ca59b6fab18263348aeafc7a895b7f4b8076))
* Remove local_ip retrieval ([ff8e599](https://github.com/reacherhq/check-if-email-exists/commit/ff8e5998f8b88954b4104f9251d1331542dbb182))
* Revert Cargo files ([#1389](https://github.com/reacherhq/check-if-email-exists/issues/1389)) ([96a2278](https://github.com/reacherhq/check-if-email-exists/commit/96a2278823ce717f9b1e79feccd13c059a598906))
* rm .rustfmt.toml ([#1524](https://github.com/reacherhq/check-if-email-exists/issues/1524)) ([1691d2d](https://github.com/reacherhq/check-if-email-exists/commit/1691d2db73b5dbd7384a0a99c60b4878be2aae1b))
* Support queues in env var ([39655d5](https://github.com/reacherhq/check-if-email-exists/commit/39655d51afe5f65d62cd5dc3485586e16bcdec31))
* Typo in expect of RCH_VERIF_METHOD ([#1405](https://github.com/reacherhq/check-if-email-exists/issues/1405)) ([c50d8eb](https://github.com/reacherhq/check-if-email-exists/commit/c50d8ebdfc470fe1ec6290e07668c70095298799))


### Features

* Add back RabbitMQ-based worker ([#1513](https://github.com/reacherhq/check-if-email-exists/issues/1513)) ([de75ece](https://github.com/reacherhq/check-if-email-exists/commit/de75eceef32c6ea512e0a301ec62d393bb59ff0f))
* Add debug information about each email verification ([#1391](https://github.com/reacherhq/check-if-email-exists/issues/1391)) ([3ea6e66](https://github.com/reacherhq/check-if-email-exists/commit/3ea6e6607735682dfca6ecfa27460650ac6e42d3))
* Add proxy field in SmtpDebug ([2f60a03](https://github.com/reacherhq/check-if-email-exists/commit/2f60a03f25d56397eb54302b134730ef923d9105))
* Add RabbitMQ worker ([#1395](https://github.com/reacherhq/check-if-email-exists/issues/1395)) ([ecef8c9](https://github.com/reacherhq/check-if-email-exists/commit/ecef8c98deb744390c7017a4e98d4f3c7e737fcb))
* Add sentry logging to worker ([5aa6026](https://github.com/reacherhq/check-if-email-exists/commit/5aa6026e6147fd68f9b93c4feb2752b51c337aae))
* Allow /v1/check_email without worker mode ([9ca9f39](https://github.com/reacherhq/check-if-email-exists/commit/9ca9f39ee487dc1b7d9b4cdc9a0b2c0669b10bc0))
* **backend:** Add one simple retry on Unknown ([fcffc1a](https://github.com/reacherhq/check-if-email-exists/commit/fcffc1a28bab990b0596ad8b66163e47a494191b))
* **backend:** Add POST /v1/bulk ([#1413](https://github.com/reacherhq/check-if-email-exists/issues/1413)) ([d9302d4](https://github.com/reacherhq/check-if-email-exists/commit/d9302d4c1cec6a5a1788afe2a3718df8986f118f))
* **backend:** Add reply-to queue ([aaea59f](https://github.com/reacherhq/check-if-email-exists/commit/aaea59f251634db7c35f029b09ef6e5f8c77cfbc))
* **backend:** Add worker webhook ([db90cfa](https://github.com/reacherhq/check-if-email-exists/commit/db90cfa27b85916685268a3599bdfdb2c46de07a))
* **backend:** Customize SMTP defaults ([8f152b8](https://github.com/reacherhq/check-if-email-exists/commit/8f152b83c70b94618b71308552a6999f4b27aa2f))
* **backend:** Prune bulk email verification database ([#1377](https://github.com/reacherhq/check-if-email-exists/issues/1377)) ([f905735](https://github.com/reacherhq/check-if-email-exists/commit/f90573566abf40133ebfb28ebc8f18ad8278a9b3))
* **backend:** Reject a request with to_email field empty or missing ([#1353](https://github.com/reacherhq/check-if-email-exists/issues/1353)) ([1d9c29f](https://github.com/reacherhq/check-if-email-exists/commit/1d9c29f5a48655a11f985b7df91c8bcbdf102487))
* **backend:** Support RCH_SMTP_TIMEOUT ([#1407](https://github.com/reacherhq/check-if-email-exists/issues/1407)) ([b9bda40](https://github.com/reacherhq/check-if-email-exists/commit/b9bda4049540372811a86d8dd7ba873c9875e54d))
* **core:** Add domain-specific rules as JSON file ([#1347](https://github.com/reacherhq/check-if-email-exists/issues/1347)) ([cab143c](https://github.com/reacherhq/check-if-email-exists/commit/cab143c72889c585adbf041e9c248e57d0c4c4ca))
* **core:** Bump to 45s timeout for some domains ([#1348](https://github.com/reacherhq/check-if-email-exists/issues/1348)) ([fda33a2](https://github.com/reacherhq/check-if-email-exists/commit/fda33a27441e2ccb1c4e97c0fc582abf25b1561f))
* **core:** Default Gmail checks to use API ([4304743](https://github.com/reacherhq/check-if-email-exists/commit/4304743fa93b6511857827afcdaa1fb9124bd62b))
* Increase content length limit for bulk validation endpoint ([#1525](https://github.com/reacherhq/check-if-email-exists/issues/1525)) ([bbdab31](https://github.com/reacherhq/check-if-email-exists/commit/bbdab31e0dde54d21f4eeb5880ae28e60de7dced))
* Update parser.rs ([#1345](https://github.com/reacherhq/check-if-email-exists/issues/1345)) ([8269f22](https://github.com/reacherhq/check-if-email-exists/commit/8269f22f73214412f154927a908a7769d3f8b00c))
* Use 2 queues instead of 1 ([#1396](https://github.com/reacherhq/check-if-email-exists/issues/1396)) ([af44f6c](https://github.com/reacherhq/check-if-email-exists/commit/af44f6c6629267571e7754a8c40c1036dbf4fc7d))
* Yahoo account recovery via headless ([#1364](https://github.com/reacherhq/check-if-email-exists/issues/1364)) ([6f0f12b](https://github.com/reacherhq/check-if-email-exists/commit/6f0f12b8cf528e819f8743f7e3c5f5e141c51559))


### Reverts

* **backend:** Bring back the sqlxmq-based bulk verification ([#1477](https://github.com/reacherhq/check-if-email-exists/issues/1477)) ([322ad4e](https://github.com/reacherhq/check-if-email-exists/commit/322ad4e4b53d534a8ae6461f3d3383d67b219b5d)), closes [#1421](https://github.com/reacherhq/check-if-email-exists/issues/1421)


### BREAKING CHANGES

* The `smtp_security` field has been removed from the /check_email request.
* - In `/v0/check_email` endpoint, the `hotmail_verif_method` field has been replaced two fields: `hotmailb2b_verif_method` and `hotmailb2c_verif_method`
- All serializations of `"Smtp","Api","Headless"` have been converted to lowercase `"smtp","api","headless"`
* We switched to a more commonly-used builder pattern to create an input:
```diff
- let mut input = CheckEmailInput::new("someone@gmail.com");
- input.set_from_email("me@mycompany.com");

+ let input = CheckEmailInputBuilder::default()
+     .to_email("someone@gmail.com")
+     .from_email("me@mycompany.com")
+     .build()
+     .unwrap();

let res = check_email(input, &config).await;
```
* The main function, `check_email()`, now takes a second argument called `ReacherConfig`. This struct contains configuration such as the webdriver address to listen to for headless email verifications, or an optional Sentry configuration to send error reports to. Previously, these configurations were passed through poorly-documented environment variables; now we make them explicit. When migration, you can pass `ReacherConfig::default()` which returns sensible default values.
* Remove /v0/bulk endpoints in favor of the /v1/bulk endpoints. New docs are here: https://help.reacher.email/bulk-email-verification.
* Vercel functions (used by https://app.reacher.email) usually timeout with a 504 error within less than 60s. So we should absolutely make a verification in less time than that.

After some testing, Reacher performs better with this setting:
- each SMTP connection times out after 45s, but we don't retry
over this previous setting
- each SMTP connection times out after ~20s, but we do retry once (to avoid greylisting in some rare cases)

Changing the default behaviour in this PR.
* For Hotmail, Gmail and Yahoo addresses, the `*_use_api` and `*_use_headless` parameters have been removed and replaced with a `*VerifyMethod`, an enum which can take value Api, Headless or Smtp. If using headless, pass a webdriver address to env variable RCH_WEBDRIVER_ADDR. 
* `input.hotmail_use_headless` is now a bool instead of a string. Pass the webdriver address as an environment variable `RCH_WEBDRIVER_ADDR` now.
* **core:** `SmtpError::TimeoutError` has been removed in favor of the one async-smtp uses, namely `std::io::Error` with `ErrorKind::TimeoutError`



## [0.9.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.32...v0.9.1) (2023-10-08)


* refactor!: Change RUST_LOG target to `reacher` (#1152) ([7e87be2](https://github.com/reacherhq/check-if-email-exists/commit/7e87be26f1e35a6936bfc967c872cd42b93fd256)), closes [#1152](https://github.com/reacherhq/check-if-email-exists/issues/1152)


### Bug Fixes

* **backend:** Fix CI priting ([748940c](https://github.com/reacherhq/check-if-email-exists/commit/748940ca2fa7fb59aac8e07a408a22d1ab688527))
* **backend:** Fix deploy to docker ([20fcfa6](https://github.com/reacherhq/check-if-email-exists/commit/20fcfa6032e4614dc459a34183958fde63199acf))
* **backend:** Fix dockerfile ([f0ed49f](https://github.com/reacherhq/check-if-email-exists/commit/f0ed49f50238c1c71a130f3db19ec047af00b8df))
* **backend:** Improve sentry error messages ([#1155](https://github.com/reacherhq/check-if-email-exists/issues/1155)) ([d90d998](https://github.com/reacherhq/check-if-email-exists/commit/d90d998d1cb189fed3f888659aa08fd4fabf6e93))
* **backend:** Redact email in sentry bug tracking ([2c2d1d8](https://github.com/reacherhq/check-if-email-exists/commit/2c2d1d88c0086196bc09359e32c96638124d9539))
* **cli:** Update flags default values ([a4fe57e](https://github.com/reacherhq/check-if-email-exists/commit/a4fe57e9ab89659e12182719ccb12fb2cdcb5f2e))
* **core:** Add more invalid parsing and improve logging ([#1156](https://github.com/reacherhq/check-if-email-exists/issues/1156)) ([b5ae9f8](https://github.com/reacherhq/check-if-email-exists/commit/b5ae9f8ad910b77ad6a179ecb5d4b633011ed2f4))
* **core:** Default SMTP timeout to 15 ([0d4fa4d](https://github.com/reacherhq/check-if-email-exists/commit/0d4fa4d8f662ecfd3fa2e0359322f324a8ef86db))
* **core:** Don't use headless on Microsoft 465 addresses ([#1196](https://github.com/reacherhq/check-if-email-exists/issues/1196)) ([0c3c21d](https://github.com/reacherhq/check-if-email-exists/commit/0c3c21daf6ea79875835121fb86ab7c0c86d55eb))
* **core:** Fix default CheckEmailInput ([09215a1](https://github.com/reacherhq/check-if-email-exists/commit/09215a13ac3525861e6cd1dea3fc71c13dfffe52))
* **core:** Fix hotmail headless option parsing ([6ddc3b9](https://github.com/reacherhq/check-if-email-exists/commit/6ddc3b96da0d01b02711d62873ad0d0df6bf1b33))
* **core:** Fix hotmail headless with authenticator ([51cdb2e](https://github.com/reacherhq/check-if-email-exists/commit/51cdb2e3c13a433fff92f1d3dcf1bfcb90f6ce7b))
* **core:** Fix MX random record selection ([#1263](https://github.com/reacherhq/check-if-email-exists/issues/1263)) ([9fae593](https://github.com/reacherhq/check-if-email-exists/commit/9fae593b8590ad5efb3e7d16bbd25cc05c228cb9))
* **core:** Improve invalid parser ([#1166](https://github.com/reacherhq/check-if-email-exists/issues/1166)) ([bb46004](https://github.com/reacherhq/check-if-email-exists/commit/bb460046bf1cb031fee706d836c8a737157f803c))
* **core:** Improve parser and headless hotmail runner ([#1167](https://github.com/reacherhq/check-if-email-exists/issues/1167)) ([0de33a5](https://github.com/reacherhq/check-if-email-exists/commit/0de33a5f265105a769c7ca6125df0fd4f88b89e2))
* **core:** Improve parser from Sentry errors ([fbaf588](https://github.com/reacherhq/check-if-email-exists/commit/fbaf58824a339e546d50c2125a459161769dda6e))
* **core:** Improve parser's `is_invalid` ([#1159](https://github.com/reacherhq/check-if-email-exists/issues/1159)) ([ec1c4d5](https://github.com/reacherhq/check-if-email-exists/commit/ec1c4d5e5d4c94d75d255a0699402f75eb29f7ab))
* **core:** No sandbox in headless Hotmail check ([0590438](https://github.com/reacherhq/check-if-email-exists/commit/0590438310f3c052b2748a8c408e0d8dbfb777b7))
* **core:** Remove antispam check ([#1337](https://github.com/reacherhq/check-if-email-exists/issues/1337)) ([06f18ed](https://github.com/reacherhq/check-if-email-exists/commit/06f18edf7aee5640b3725feedfa7b7f213da83a8))
* **core:** Yahoo add back IDENTIFIER_EXISTS ([2b63556](https://github.com/reacherhq/check-if-email-exists/commit/2b635564efb37b0aa891bbba77244e6cf2d611bb))
* **core:** yahoo api changes: yid is userId now, sessionIndex is required and fo… ([#1314](https://github.com/reacherhq/check-if-email-exists/issues/1314)) ([0209111](https://github.com/reacherhq/check-if-email-exists/commit/02091115026520596fc5b4b2a6757169e91cba15))
* Don't auto-fetch Chrome, install in Docker ([84fcc0d](https://github.com/reacherhq/check-if-email-exists/commit/84fcc0de40567126ce3a385934086450c3a89ccf))
* split Microsoft 365/Hotmail functionality ([#1204](https://github.com/reacherhq/check-if-email-exists/issues/1204)) ([e987b13](https://github.com/reacherhq/check-if-email-exists/commit/e987b13a5ccd98d28fb756f1bf41427c337750c4))
* Switch back to upstream fast-socks ([#1164](https://github.com/reacherhq/check-if-email-exists/issues/1164)) ([db356f1](https://github.com/reacherhq/check-if-email-exists/commit/db356f19374843ca135de8ebd8a6c34bfeb017a8))
* TLS accept unsafe ([778692b](https://github.com/reacherhq/check-if-email-exists/commit/778692bce760c0a1e1201dd3e11b41e7ccb7e2e8))
* Use chromedriver instead of gecko for parallel requests ([e282e28](https://github.com/reacherhq/check-if-email-exists/commit/e282e28aeb7259d800f7faad97173c3a216095a4))


### Features

* **#289:** add haveibeenpwned check ([#1253](https://github.com/reacherhq/check-if-email-exists/issues/1253)) ([166dbd2](https://github.com/reacherhq/check-if-email-exists/commit/166dbd2cc878e30c51538b919abc1aaea4465c45)), closes [#289](https://github.com/reacherhq/check-if-email-exists/issues/289)
* add email address normalisation ([#1206](https://github.com/reacherhq/check-if-email-exists/issues/1206)) ([f8ec348](https://github.com/reacherhq/check-if-email-exists/commit/f8ec348883cd4f4a20a8acbb38d54b69e798222b)), closes [#952](https://github.com/reacherhq/check-if-email-exists/issues/952)
* add Microsoft 365 HTTP API validation ([#1194](https://github.com/reacherhq/check-if-email-exists/issues/1194)) ([5d3c49f](https://github.com/reacherhq/check-if-email-exists/commit/5d3c49f41ef1369efe2a9e63b24543e281ae0776)), closes [#937](https://github.com/reacherhq/check-if-email-exists/issues/937)
* Add skipped domains ([#1293](https://github.com/reacherhq/check-if-email-exists/issues/1293)) ([29119fa](https://github.com/reacherhq/check-if-email-exists/commit/29119fa72027c9830396bbdf3e90f08c0c89d7a7))
* Add suggestions for syntax errors ([#1192](https://github.com/reacherhq/check-if-email-exists/issues/1192)) ([2d385f3](https://github.com/reacherhq/check-if-email-exists/commit/2d385f30f7a62ab2706599fbb89fb50275cffb5f))
* additional Gmail validation ([#1193](https://github.com/reacherhq/check-if-email-exists/issues/1193)) ([49c8f5c](https://github.com/reacherhq/check-if-email-exists/commit/49c8f5c3b4a3db04533d06d7267b0f15ebda3285)), closes [#937](https://github.com/reacherhq/check-if-email-exists/issues/937)
* **backend:** Add header secret to protect against public requests ([#1158](https://github.com/reacherhq/check-if-email-exists/issues/1158)) ([fa6a56b](https://github.com/reacherhq/check-if-email-exists/commit/fa6a56b62f4b3aeeec704cfe4882755998d40833))
* **core:** Add check for antispam MX records ([#1257](https://github.com/reacherhq/check-if-email-exists/issues/1257)) ([c9771da](https://github.com/reacherhq/check-if-email-exists/commit/c9771da66c7869a4d0a255e2e2536f2863e8958c))
* **core:** Add check gravatar image ([#1188](https://github.com/reacherhq/check-if-email-exists/issues/1188)) ([6a26035](https://github.com/reacherhq/check-if-email-exists/commit/6a26035327ab681a65a4f4ba284e155f00680e89))
* **core:** Add Hotmail checks via headless password recovery ([#1165](https://github.com/reacherhq/check-if-email-exists/issues/1165)) ([7517ed9](https://github.com/reacherhq/check-if-email-exists/commit/7517ed98ba966158deebba6a1a4745c931bfed18))
* **core:** Fix disabled accts on hanmail.net ([#1339](https://github.com/reacherhq/check-if-email-exists/issues/1339)) ([90393c8](https://github.com/reacherhq/check-if-email-exists/commit/90393c8dda39267da7eb5efe6f112c8f25a593f4))
* **core:** Skip catch-all for known domains ([#1336](https://github.com/reacherhq/check-if-email-exists/issues/1336)) ([c40a46c](https://github.com/reacherhq/check-if-email-exists/commit/c40a46c4555129346bd9efa444a483bf25b679fe))
* **core:** Update default MAIL-FROM and HELO ([743a811](https://github.com/reacherhq/check-if-email-exists/commit/743a8111b4831ee19e7ac887c39a8da2775acd4c))
* Move `backend` code to this repo ([#1138](https://github.com/reacherhq/check-if-email-exists/issues/1138)) ([0dc6053](https://github.com/reacherhq/check-if-email-exists/commit/0dc60531d26efb217137347ef2b6aaf678d94238))
* Revert back to `check_email` input with single email ([#1150](https://github.com/reacherhq/check-if-email-exists/issues/1150)) ([ce1ba53](https://github.com/reacherhq/check-if-email-exists/commit/ce1ba5346849b578a0ed30b1d72096f15cfbc09d))
* Set default timeout to 10s ([#1251](https://github.com/reacherhq/check-if-email-exists/issues/1251)) ([d04f84c](https://github.com/reacherhq/check-if-email-exists/commit/d04f84cc1e7b30e02d3717ab1af9f680cdb2c27f))


### BREAKING CHANGES

* The `RUST_LOG` target has been changed from `check-if-email-exists` to `reacher`.

```diff
- RUST_LOG=check-if-email-exists=debug cargo run
- RUST_LOG=reacher=debug cargo run
```
* The library's main function `check_email`'s argument `CheckEmailInput` nows takes a single `to_email` field, instead of a `to_emails: Vec<String>`

```diff
pub struct CheckEmailInput {
- pub to_emails: Vec<String>,
+ pub to_email: String,
  // --snip--
}
```

This effectively makes the public API more similar to the v0.7.* series. I'm still thinking about how to best verify multiple emails in one SMTP connection, but it most likely will be a new function with a different API. Follow [issue #65](https://github.com/reacherhq/check-if-email-exists/issues/65) for more info.



## [0.8.32](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.31...v0.8.32) (2022-08-13)


### Bug Fixes

* Fix parsing some invalid emails ([cb65c0f](https://github.com/reacherhq/check-if-email-exists/commit/cb65c0f4767b2f163f48054652f7652b6d0b6043))
* Syntax also check using using `mailchecker` ([8385bec](https://github.com/reacherhq/check-if-email-exists/commit/8385bec6fedc0912881800442bffda5b33c2f394))


### Features

* Use opportunistic STARTTLS by default ([#1079](https://github.com/reacherhq/check-if-email-exists/issues/1079)) ([54911f0](https://github.com/reacherhq/check-if-email-exists/commit/54911f0a8ec51e753f757878021e933609cff868))



## [0.8.31](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.30...v0.8.31) (2022-08-10)



## [0.8.30](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.29...v0.8.30) (2022-06-02)


### Bug Fixes

* Fix `has_full_inbox` check too lenient ([93de444](https://github.com/reacherhq/check-if-email-exists/commit/93de444dfa7c6d66061570115be8f53f0647c431))


### Features

* Add `smtp.error.description` field for human-readable description of error ([#1111](https://github.com/reacherhq/check-if-email-exists/issues/1111)) ([43b47ea](https://github.com/reacherhq/check-if-email-exists/commit/43b47ea2b9250f2c6d58c8a0ec4340066169c169))



## [0.8.29](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.28...v0.8.29) (2022-03-02)


### Features

* Loop through all MX servers ([#1070](https://github.com/reacherhq/check-if-email-exists/issues/1070)) ([11e6a06](https://github.com/reacherhq/check-if-email-exists/commit/11e6a06a67f5893b729c76d1a33667f83d63c836))



## [0.8.28](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.27...v0.8.28) (2022-02-11)


### Features

* Add proxy username/password ([#1057](https://github.com/reacherhq/check-if-email-exists/issues/1057)) ([d9583c6](https://github.com/reacherhq/check-if-email-exists/commit/d9583c6ae0d3353a5135dd157999cf579b308d6d))



## [0.8.27](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.26...v0.8.27) (2022-02-07)


### Features

* Allow user to define SMTP client security for TLS ([#1043](https://github.com/reacherhq/check-if-email-exists/issues/1043)) ([bc722ff](https://github.com/reacherhq/check-if-email-exists/commit/bc722ff1a9b30747308a3b3b5959d73e5e853292))
* Break SmtpError into `{Helo,Connect,ConnectWithStream,MailFrom,RcptTo,Close}Error` ([#1055](https://github.com/reacherhq/check-if-email-exists/issues/1055)) ([64e5193](https://github.com/reacherhq/check-if-email-exists/commit/64e5193c48a6bf4c080e79daeefd1c98dadffd5d))



## [0.8.26](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.25...v0.8.26) (2022-01-26)


### Bug Fixes

* Use std::default for deriving ([#1015](https://github.com/reacherhq/check-if-email-exists/issues/1015)) ([03720f0](https://github.com/reacherhq/check-if-email-exists/commit/03720f027fd68d5ea5ae538aa567a621f4a65fe3))


### Features

* Add SMTP retries to avoid greylisting ([#1041](https://github.com/reacherhq/check-if-email-exists/issues/1041)) ([b451a1e](https://github.com/reacherhq/check-if-email-exists/commit/b451a1e93a6ccf025c78d56dee7439ad607c8507))



## [0.8.25](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.24...v0.8.25) (2021-10-05)


### Bug Fixes

* Use async_std_resolver::resolver_from_system_conf ([#982](https://github.com/reacherhq/check-if-email-exists/issues/982)) ([376c3b0](https://github.com/reacherhq/check-if-email-exists/commit/376c3b0d4743ccc60a1df2a9fa3e9f2f5cd68178))
* Use TLS when available ([#964](https://github.com/reacherhq/check-if-email-exists/issues/964)) ([aed11d2](https://github.com/reacherhq/check-if-email-exists/commit/aed11d2e15b6b7688ecaf856824ca6effbb5d21b))


### Features

* Add possibility to set SMTP port ([#985](https://github.com/reacherhq/check-if-email-exists/issues/985)) ([cdabdf8](https://github.com/reacherhq/check-if-email-exists/commit/cdabdf80e858908d6c33e1273dfdc1fef0f78d35))



## [0.8.24](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.23...v0.8.24) (2021-07-03)


### Features

* Add `CheckEmailInput` setter `set_` prefix to differentiate with accessing fields ([#933](https://github.com/reacherhq/check-if-email-exists/issues/933)) ([276f656](https://github.com/reacherhq/check-if-email-exists/commit/276f6561e7a98af6415dbd4645d84cbe697b738e))
* Add deprecated warning when running HTTP server ([#943](https://github.com/reacherhq/check-if-email-exists/issues/943)) ([e4b1570](https://github.com/reacherhq/check-if-email-exists/commit/e4b1570a8be5573f7394a3139f34ab021452cc3a))



## [0.8.23](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.22...v0.8.23) (2021-06-20)


### Bug Fixes

* Add serde (De)Serialize to pub structs ([#931](https://github.com/reacherhq/check-if-email-exists/issues/931)) ([949475d](https://github.com/reacherhq/check-if-email-exists/commit/949475dee4a1ed96e873688e7432c702eb30af62))



## [0.8.22](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.21...v0.8.22) (2021-03-31)



## [0.8.21](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.20...v0.8.21) (2021-03-31)



## [0.8.20](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.19...v0.8.20) (2021-03-30)



## [0.8.19](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.18...v0.8.19) (2021-01-10)


### Bug Fixes

* Reconnect auto-closed SMTP connections by foreign server ([#825](https://github.com/reacherhq/check-if-email-exists/issues/825)) ([01ccf0d](https://github.com/reacherhq/check-if-email-exists/commit/01ccf0d2363475d486bb9827e3e3b9d6954bc032))


### Features

* Consider CLI config parameters in HTTP request checks ([#827](https://github.com/reacherhq/check-if-email-exists/issues/827)) ([88b751a](https://github.com/reacherhq/check-if-email-exists/commit/88b751a17f4367c990e8a54661e3872898afd10f))



## [0.8.18](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.17...v0.8.18) (2021-01-07)


### Bug Fixes

* Check deliverability using successful response code instead of message parsing ([#822](https://github.com/reacherhq/check-if-email-exists/issues/822)) ([39d0ecd](https://github.com/reacherhq/check-if-email-exists/commit/39d0ecdeaf078dce5cdb59cba95ab9e02bce11ee))



## [0.8.17](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.16...v0.8.17) (2021-01-05)


### Bug Fixes

* Add better checks for existing mailboxes ([#819](https://github.com/reacherhq/check-if-email-exists/issues/819)) ([9f88d01](https://github.com/reacherhq/check-if-email-exists/commit/9f88d01fad2c8de898aa35645bab95a14a147393))



## [0.8.16](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.15...v0.8.16) (2020-12-07)


### Features

* Add proxy_host and proxy_port info to HTTP ([#770](https://github.com/reacherhq/check-if-email-exists/issues/770)) ([123f431](https://github.com/reacherhq/check-if-email-exists/commit/123f431e10e90339e030783582d6e4c4919c1a33))



## [0.8.15](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.14...v0.8.15) (2020-11-11)


### Bug Fixes

* Don't check inputted email if catch-all ([#714](https://github.com/reacherhq/check-if-email-exists/issues/714)) ([5129dd1](https://github.com/reacherhq/check-if-email-exists/commit/5129dd1374d3ef93db632f6d7e140e3ce69369b2))
* Fix 'reached the type-length limit while instantiating' ([#665](https://github.com/reacherhq/check-if-email-exists/issues/665)) ([fa040fd](https://github.com/reacherhq/check-if-email-exists/commit/fa040fda8b16ca4d540829ee72f9d7b07ef77fdd))



## [0.8.14](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.13...v0.8.14) (2020-09-24)


### Bug Fixes

* Add more known errors for invalid email ([#543](https://github.com/reacherhq/check-if-email-exists/issues/543)) ([ad209c7](https://github.com/reacherhq/check-if-email-exists/commit/ad209c7ecb3f5aa466f31e293a05734b5edf5f6a))


### Features

* Add optional timeout on smtp verification ([#611](https://github.com/reacherhq/check-if-email-exists/issues/611)) ([c70de7d](https://github.com/reacherhq/check-if-email-exists/commit/c70de7dcac1811596c78e14888a4258e9db408ed))



## [0.8.13](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.12...v0.8.13) (2020-08-04)


### Bug Fixes

* **ci:** Put lto flag in cargo.toml ([#531](https://github.com/reacherhq/check-if-email-exists/issues/531)) ([00cbc1f](https://github.com/reacherhq/check-if-email-exists/commit/00cbc1fd46743c7579809a09b3897259213af496))



## [0.8.12](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.11...v0.8.12) (2020-08-04)


### Bug Fixes

* Add "recipient address accepted" check ([#489](https://github.com/reacherhq/check-if-email-exists/issues/489)) ([5d1e72a](https://github.com/reacherhq/check-if-email-exists/commit/5d1e72ae165f335ab97a96c3806e3293289187a2))
* http request body to use `to_emails` ([#502](https://github.com/reacherhq/check-if-email-exists/issues/502)) ([36aed56](https://github.com/reacherhq/check-if-email-exists/commit/36aed567cf705ef8d20489b2275e3d8ba58b75bb))



## [0.8.11](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.10...v0.8.11) (2020-07-11)


### Bug Fixes

* Add "Invalid email address" check ([#471](https://github.com/reacherhq/check-if-email-exists/issues/471)) ([3b03617](https://github.com/reacherhq/check-if-email-exists/commit/3b03617b81a1f9f6bc1bc6edc8c5d6d9f87eabbb))
* Add possibility to use proxy in Yahoo API request ([#472](https://github.com/reacherhq/check-if-email-exists/issues/472)) ([aafcedf](https://github.com/reacherhq/check-if-email-exists/commit/aafcedf9b9135a6550e7aa2da5d7ca5898da9b53))



## [0.8.10](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.9...v0.8.10) (2020-07-04)


### Bug Fixes

* Correct message parsing for "receiving at a rate" error ([#462](https://github.com/reacherhq/check-if-email-exists/issues/462)) ([4b31706](https://github.com/reacherhq/check-if-email-exists/commit/4b31706228a6e81852505ec21a0f70d5472b1385))



## [0.8.9](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.8...v0.8.9) (2020-07-04)


### Features

* Make using Yahoo API optional ([#460](https://github.com/reacherhq/check-if-email-exists/issues/460)) ([1e42f0a](https://github.com/reacherhq/check-if-email-exists/commit/1e42f0abef27dcea9a467f677ef9a080a3cc0f18))



## [0.8.8](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.7...v0.8.8) (2020-06-28)


### Bug Fixes

* Add debug logs for Yahoo ([e534670](https://github.com/reacherhq/check-if-email-exists/commit/e53467006f9fa435993ea58b1753ce5baa059d2a))



## [0.8.7](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.6...v0.8.7) (2020-06-28)


### Bug Fixes

* Add "recipient unknown" check ([#446](https://github.com/reacherhq/check-if-email-exists/issues/446)) ([deca071](https://github.com/reacherhq/check-if-email-exists/commit/deca071583e34bb9c5836d5238dd51975f827cdc))



## [0.8.6](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.5...v0.8.6) (2020-06-28)


### Bug Fixes

* Add additional error check for undeliverable ([#374](https://github.com/reacherhq/check-if-email-exists/issues/374)) ([e52a8f0](https://github.com/reacherhq/check-if-email-exists/commit/e52a8f0941fd53c9b087e6e59a7018d11af72dff))
* Use HTTP requests to verify Yahoo emails ([#412](https://github.com/reacherhq/check-if-email-exists/issues/412)) ([5fad57d](https://github.com/reacherhq/check-if-email-exists/commit/5fad57d88ef92d65c7d493cdcb45eff347d6a286))



## [0.8.5](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.4...v0.8.5) (2020-05-21)


### Features

* Expose misc, syntax, mx, smtp modules ([#373](https://github.com/reacherhq/check-if-email-exists/issues/373)) ([7c1d741](https://github.com/reacherhq/check-if-email-exists/commit/7c1d741f00b3a807b190140a1d91a42bce29470c))



## [0.8.4](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.3...v0.8.4) (2020-05-19)


### Bug Fixes

* Fix is_reachable with wrong syntax ([#352](https://github.com/reacherhq/check-if-email-exists/issues/352)) ([b0f0209](https://github.com/reacherhq/check-if-email-exists/commit/b0f02090edc0bb8947ab826415fa3bf8b5db55f0))



## [0.8.3](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.2...v0.8.3) (2020-05-12)


### Bug Fixes

* Lowercase Reachable enum variants ([#351](https://github.com/reacherhq/check-if-email-exists/issues/351)) ([b88c20e](https://github.com/reacherhq/check-if-email-exists/commit/b88c20ef5bc947ecd8cc646a9e6c583df0bef4d7))



## [0.8.2](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.1...v0.8.2) (2020-05-12)


### Bug Fixes

* Add "Unknown user" smtp error check ([#347](https://github.com/reacherhq/check-if-email-exists/issues/347)) ([47eb578](https://github.com/reacherhq/check-if-email-exists/commit/47eb5780f692f54aadf264b107996bb2d1a31a56))
* Add more error strings matching ([#323](https://github.com/reacherhq/check-if-email-exists/issues/323)) ([f5392d4](https://github.com/reacherhq/check-if-email-exists/commit/f5392d4befcee6e4d935e1585066eae3b57ec6fa))


### Features

* Add `is_reachable` top field ([#350](https://github.com/reacherhq/check-if-email-exists/issues/350)) ([e7abb17](https://github.com/reacherhq/check-if-email-exists/commit/e7abb17ef29610fbe9210f42830c0ba02bb130b7))
* Detect role-based accounts ([#348](https://github.com/reacherhq/check-if-email-exists/issues/348)) ([7c612fd](https://github.com/reacherhq/check-if-email-exists/commit/7c612fda110729ece094d0b022db05fa4e6b27b4))



## [0.8.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.8.0...v0.8.1) (2020-05-09)


### Bug Fixes

* Lowercase the error string before matching ([#321](https://github.com/reacherhq/check-if-email-exists/issues/321)) ([d983b2f](https://github.com/reacherhq/check-if-email-exists/commit/d983b2fe960ed46c4bd03c55b39d7ea58be5124f))



# [0.8.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.7.1...v0.8.0) (2020-05-08)


* refactor!: Rename main function to `check_email` (#319) ([bd12b7d](https://github.com/reacherhq/check-if-email-exists/commit/bd12b7dbbd6c090fcdf80e3d6bbe475cd1d82b9a)), closes [#319](https://github.com/reacherhq/check-if-email-exists/issues/319)


### Bug Fixes

* Rename valid_format to is_valid_syntax ([#288](https://github.com/reacherhq/check-if-email-exists/issues/288)) ([eae6482](https://github.com/reacherhq/check-if-email-exists/commit/eae64821c31d0193f77d9137ec4e7d6131f91ccb))


### BREAKING CHANGES

* This new version includes an overhaul of the codebase, mainly to prepare the groundwork for the upcoming work on bulk validation. These changes include:

- The main function `email_exists` has been renamed to `check_email`:

```diff
- email_exists(&input).await;
+ check_email(&input).await;
```

- The input `EmailInput` has been renamed to `CheckEmailInput`. Its `::new()` method, instead of taking a single `String`, now takes `Vec<String>`.

- The output `SingleEmail` has been renamed to `CheckEmailOutput`. The main function `check_emails` now returns a `Vec<CheckEmailOutput>`.

```rust
pub async fn check_email(inputs: &CheckEmailInput) -> Vec<CheckEmailOutput>
```

- The `syntax` field in `CheckEmailOutput` is no longer a `Result<SyntaxDetails, SyntaxError>`, but only `SyntaxDetails`. Error cases are guaranteed not to happen for syntax validation.

- The `misc`, `mx`, and `smtp` fields' signatures stay the same: `Result<{Misc,Mx,Smtp}Details, {Misc,Mx,Smtp}Error>`. However, the `Result` is an `Err` only when an internal error arrives. In case of errors due to user input (e.g. incorrect email inputted), the `Default` trait has been implemented on `{Misc,Mx,Smtp}Details` and will be returned. As such, the `Skipped` variant of error enums has been removed.

```diff
{
  "input": "foo@bar.baz",
  "mx": {
-    "error": { "cannot resolve" }
+    "accepts_mail": false, // This is Default
+    "records": [] // This is Default
  }
```

- The `misc`, `mx`, `smtp`, `syntax` modules have been made private.
* The field `syntax.valid_format` has been renamed to `syntax.is_valid_syntax`.



## [0.7.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.7.0...v0.7.1) (2020-04-14)


### Features

* Add possibility to verify emails via proxy ([#286](https://github.com/reacherhq/check-if-email-exists/issues/286)) ([a0ab93f](https://github.com/reacherhq/check-if-email-exists/commit/a0ab93fde5105d594a8280b942d337ff76fbb517))



# [0.7.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.6.7...v0.7.0) (2020-03-26)


### Features

* Use builder pattern for EmailInput ([#254](https://github.com/reacherhq/check-if-email-exists/issues/254)) ([0c85d36](https://github.com/reacherhq/check-if-email-exists/commit/0c85d36cdccb37d8da9566f7e7baf5dbbd266740))


### BREAKING CHANGES

* `email_exists` only takes one input now, an `EmailInput` which is built using the builder pattern.
```diff
- use check_if_email_exists::email_exists;
+ use check_if_email_exists::{email_exists, EmailInput};

- email_exists("someone@gmail.com", "user@example.org");
+ email_exists(
+   EmailInput::new("someone@gmail.com".to_string()).from_email("user@example.org".to_string())
+ )
```

`EmailInput` additionally takes a `hello_name()` method, which is used to set the name in the EHLO smtp command.

`--from` in CLI has been replaced with `--from-email`.



## [0.6.7](https://github.com/reacherhq/check-if-email-exists/compare/v0.6.6...v0.6.7) (2020-03-20)



## [0.6.6](https://github.com/reacherhq/check-if-email-exists/compare/v0.6.1...v0.6.6) (2020-03-01)


### Bug Fixes

* Allow http to listen to $PORT env variable ([#215](https://github.com/reacherhq/check-if-email-exists/issues/215)) ([3b0c262](https://github.com/reacherhq/check-if-email-exists/commit/3b0c262763bc9d52855ced90aa2a435a97d35d8b))



## [0.6.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.6.0...v0.6.1) (2020-02-18)


### Features

* Add --http-host flag to CLI ([#197](https://github.com/reacherhq/check-if-email-exists/issues/197)) ([55657b2](https://github.com/reacherhq/check-if-email-exists/commit/55657b251fcc22fad2ae53da4f62a017ff01d035))



# [0.6.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.5.0...v0.6.0) (2019-12-01)


### Features

* Add a HTTP server behind the `--http` flag ([#85](https://github.com/reacherhq/check-if-email-exists/issues/85)) ([d8b733e](https://github.com/reacherhq/check-if-email-exists/commit/d8b733e5a571c512644b34219b5f2dfd9dc717b3))
* Add Dockerfile & `x86_64-unknown-linux-musl` target ([#86](https://github.com/reacherhq/check-if-email-exists/issues/86)) ([cba1241](https://github.com/reacherhq/check-if-email-exists/commit/cba124110be04d7febfeab68a6b825197b3aa1fb))


### BREAKING CHANGES

* - The `is_disposable` subfield has been moved from the `mx` field to a separate `misc` field



# [0.5.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.4.0...v0.5.0) (2019-11-16)


### Code Refactoring

* Use futures ([#78](https://github.com/reacherhq/check-if-email-exists/issues/78)) ([0e1f6b0](https://github.com/reacherhq/check-if-email-exists/commit/0e1f6b014929bbdd97eeb687e8399e016168c304))


### BREAKING CHANGES

* - The main function `email_exists` now returns a Future:
```rust
pub async fn email_exists(to_email: &str, from_email: &str) -> SingleEmail {}
```
- The `SmtpError::SmtpError` has been renamed to `SmtpError::LettreError` to show the underlying error more correctly (i.e., coming from `lettre` crate).
- The `BlockedByISP` error has been removed. Instead, you'll see e.g. `"connection refused"`, or whatever is returned by the SMTP server:
```json
{
  // ...,
  "smtp": {
    "error": {
      "type": "LettreError",
      "message": "connection refused"
    }
  },
}
```



# [0.4.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.3.2...v0.4.0) (2019-09-30)


### Features

* Add disposable email check ([#64](https://github.com/reacherhq/check-if-email-exists/issues/64)) ([1b2cea3](https://github.com/reacherhq/check-if-email-exists/commit/1b2cea3a6ffec08e63c5b6e7d9b2cce9d3b3c427))


### BREAKING CHANGES

* the `smtp`'s object keys have changed. Instead of
```
{
  "deliverable": ...,
  "full_inbox": ...,
  "has_catch_all": ...
}
```
it now returns 
```
{
  "has_full_inbox": ...,
  "is_deliverable": ...,
  "is_disabled": ...,
  "is_catch_all": ...
}
```
where `is_disabled` checks if the address has been disabled/blocked by the email provider



## [0.3.2](https://github.com/reacherhq/check-if-email-exists/compare/v0.3.1...v0.3.2) (2019-09-26)


### Bug Fixes

* **core:** SyntaxError also is type & message ([#60](https://github.com/reacherhq/check-if-email-exists/issues/60)) ([996633b](https://github.com/reacherhq/check-if-email-exists/commit/996633b1ccde5dd79ec42001b6d445aa195002ad))



## [0.3.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.3.0...v0.3.1) (2019-09-26)


### Bug Fixes

* Don't use virtual workspace, be able to build ([#59](https://github.com/reacherhq/check-if-email-exists/issues/59)) ([6c93893](https://github.com/reacherhq/check-if-email-exists/commit/6c93893273483ab027af3ef769ab1246dfab7ad7))



# [0.3.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.2.3...v0.3.0) (2019-09-26)


### Features

* New error JSON format ([#56](https://github.com/reacherhq/check-if-email-exists/issues/56)) ([fec4315](https://github.com/reacherhq/check-if-email-exists/commit/fec43156b3edef30d449c14572043e312335c01b))
* Output JSON information with CLI ([#53](https://github.com/reacherhq/check-if-email-exists/issues/53)) ([1d026d5](https://github.com/reacherhq/check-if-email-exists/commit/1d026d5d5df3c1684acb30379a3640528b572485))
* Return Result<EmailDetails> instead of Result<bool>, with much more details ([#23](https://github.com/reacherhq/check-if-email-exists/issues/23)) ([39b13f5](https://github.com/reacherhq/check-if-email-exists/commit/39b13f55249cdf68e627d23cc4eee1146186d55c))



## [0.2.3](https://github.com/reacherhq/check-if-email-exists/compare/v0.2.2...v0.2.3) (2019-05-09)


### Bug Fixes

* Update version to correct version in cli ([992777c](https://github.com/reacherhq/check-if-email-exists/commit/992777ce898013f2ff998f0dc72c0308eac9d318))



## [0.2.2](https://github.com/reacherhq/check-if-email-exists/compare/v0.2.1...v0.2.2) (2019-05-09)


### Bug Fixes

* Fix travis and appveyor to build binaries ([f743e67](https://github.com/reacherhq/check-if-email-exists/commit/f743e6767a401a9d97f5f98d56a3dbbb7a38a289))



## [0.2.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.2.0...v0.2.1) (2019-05-09)


### Bug Fixes

* Refactor app to make travis build binaries ([#17](https://github.com/reacherhq/check-if-email-exists/issues/17)) ([9616ef5](https://github.com/reacherhq/check-if-email-exists/commit/9616ef5bcf6c016dd065550739b9eece5a6b8a07))



# [0.2.0](https://github.com/reacherhq/check-if-email-exists/compare/v0.1.1...v0.2.0) (2019-05-09)


### Features

* Add serverless function ([#15](https://github.com/reacherhq/check-if-email-exists/issues/15)) ([532c4eb](https://github.com/reacherhq/check-if-email-exists/commit/532c4ebcb4a9ee13c1d3ab557085971ee774a158))
* Return Option<bool> instead of bool ([#13](https://github.com/reacherhq/check-if-email-exists/issues/13)) ([2aef345](https://github.com/reacherhq/check-if-email-exists/commit/2aef3458f694bd8deeedb6860278846650096f50))



## [0.1.1](https://github.com/reacherhq/check-if-email-exists/compare/v0.1.0...v0.1.1) (2018-12-29)





================================================
FILE: Cargo.toml
================================================
[workspace]
members = ["backend", "cli", "core", "sqs"]


================================================
FILE: LICENSE.AGPL
================================================
                    GNU AFFERO GENERAL PUBLIC LICENSE
                       Version 3, 19 November 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.

  A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate.  Many developers of free software are heartened and
encouraged by the resulting cooperation.  However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.

  The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community.  It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server.  Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.

  An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals.  This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU Affero General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Remote Network Interaction; Use with the GNU General Public License.

  Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software.  This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time.  Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source.  For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code.  There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.


================================================
FILE: LICENSE.md
================================================
`check-if-email-exists`'s source code is provided under a **dual license model**.

### Commercial license

If you want to use `check-if-email-exists` to develop commercial sites, tools, and applications, the Commercial License is the appropriate license. With this option, your source code is kept proprietary. Purchase an `check-if-email-exists` Commercial License at https://reacher.email/pricing.

### Open source license

If you are creating an open source application under a license compatible with the GNU Affero GPL license v3, you may use `check-if-email-exists` under the terms of the [AGPL-3.0](./LICENSE.AGPL).

[Read more](https://docs.reacher.email/self-hosting/licensing) about Reacher's license.


================================================
FILE: Makefile
================================================
###############################################################################
# Run
###############################################################################

# Run the backend without worker mode, i.e. only enabling single-shot
# verifications via the /v1/check_email endpoint.
.PHONY: run
run:
	cd backend && cargo run --bin reacher_backend

# Run the backend with worker mode on. This enables the /v1/bulk endpoints.
# Make sure to have a Postgres DB and a RabbitMQ instance running.
.PHONY: run-with-worker
run-with-worker: export RCH__WORKER__ENABLE=true
run-with-worker: export RCH__WORKER__RABBITMQ__URL=amqp://guest:guest@localhost:5672
run-with-worker: export RCH__STORAGE__POSTGRES__DB_URL=postgresql://localhost/reacherdb
run-with-worker: run

.PHONY: run-with-commercial-license-trial
run-with-commercial-license-trial: export RCH__COMMERCIAL_LICENSE_TRIAL__URL=http://localhost:3000/api/v1/commercial_license_trial
run-with-commercial-license-trial: run

# Generate the changelog using the conventional-changelog tool.
# As a hack, we delete all tags that are not beta tags, so that the changelog
# only contains the vX.X.X tags. See:
# https://github.com/conventional-changelog/standard-version/issues/818
#
# To have those tags back locally, run `git fetch --tags`.
.PHONY: changelog
changelog:
	git tag | grep -E '(beta|backend|worker)' | xargs git tag -d
	echo "# Changelog" > CHANGELOG.md
	echo "" >> CHANGELOG.md
	echo "All notable changes to this project will be documented in this file. The changes in this project follow [Convention Commits](https://www.conventionalcommits.org/en/v1.0.0/)." >> CHANGELOG.md
	echo "" >> CHANGELOG.md
	conventional-changelog -p angular -r 0 >> CHANGELOG.md

###############################################################################
# Update lists
###############################################################################

.PHONY: update-role-accounts
update-role-accounts:
# License is MIT.
	curl https://raw.githubusercontent.com/mixmaxhq/role-based-email-addresses/refs/heads/master/index.js -o core/src/misc/roles.txt
# Remove first line, last line, and all ' and , characters
	sed -i.bak '1d' core/src/misc/roles.txt && rm core/src/misc/roles.txt.bak
	sed -i.bak '$$d' core/src/misc/roles.txt && rm core/src/misc/roles.txt.bak
	sed -i.bak 's/['\'', ]//g' core/src/misc/roles.txt && rm core/src/misc/roles.txt.bak


.PHONY: update-free-email-providers
update-free-email-providers:
# License is MIT.
	curl https://raw.githubusercontent.com/ihmpavel/free-email-domains-list/refs/heads/master/data/data.txt -o core/src/misc/b2c.txt

================================================
FILE: README.md
================================================
[![Crate](https://img.shields.io/crates/v/check-if-email-exists.svg)](https://crates.io/crates/check-if-email-exists)
[![Docs](https://docs.rs/check-if-email-exists/badge.svg)](https://docs.rs/check-if-email-exists)
[![Docker](https://img.shields.io/docker/v/reacherhq/backend?color=0db7ed&label=docker&sort=date)](https://hub.docker.com/r/reacherhq/backend)
[![Actions Status](https://github.com/reacherhq/check-if-email-exists/workflows/pr/badge.svg)](https://github.com/reacherhq/check-if-email-exists/actions)

<br /><br />

<p align="center"><img align="center" src="https://storage.googleapis.com/saasify-uploads-prod/696e287ad79f0e0352bc201b36d701849f7d55e7.svg" height="96" alt="reacher" /></p>
<h1 align="center">check-if-email-exists</h1>
<h4 align="center">Check if an email address exists without sending any email.<br/>Comes with a <a href="./backend">⚙️ HTTP backend</a>.</h4>

<br /><br /><br />

## 👉 Live Demo: https://reacher.email

<img src="https://storage.googleapis.com/saasify-uploads-prod/696e287ad79f0e0352bc201b36d701849f7d55e7.svg" height="68" align="left" />

This is open-source, but I also offer a **SaaS** solution that has `check-if-email-exists` packaged in a nice friendly web interface. If you are interested, find out more at [No2Bounce.com](https://no2bounce.com/?ref=github). If you have any questions, you can contact me at amaury@reacher.email.

<br />

## Get Started

3 non-SaaS ways to get started with `check-if-email-exists`.

### 1. ⚙️ HTTP backend using Docker (popular method 🥇) [[Full docs](./backend/README.md)]

This option allows you to run a HTTP backend using Docker 🐳, on a cloud instance or your own server. Please note that outbound port 25 must be open.

```bash
docker run -p 8080:8080 reacherhq/backend:latest
```

Then send a `POST http://localhost:8080/v0/check_email` request with the following body:

```js
{
    "to_email": "someone@gmail.com",
    "proxy": {                        // (optional) SOCK5 proxy to run the verification through, default is empty
        "host": "my-proxy.io",
        "port": 1080,
        "username": "me",             // (optional) Proxy username
        "password": "pass"            // (optional) Proxy password
    }
}
```
**Note regarding proxy servers**
It is possible to operate Reacher with your own IP addresses. But if you wish to process more than very small volumes you will need SMTP proxy servers. For SMTP proxy servers please use [proxy25.com](https://proxy25.com/?ref=github)

### 2. Download the CLI [[Full docs](./cli/README.md)]

> Note: The CLI binary doesn't connect to any backend, it checks the email directly from your computer.

Head to the [releases page](https://github.com/reacherhq/check-if-email-exists/releases) and download the binary for your platform.

```bash
> $ check_if_email_exists --help
check_if_email_exists 0.9.1
Check if an email address exists without sending an email.

USAGE:
    check_if_email_exists [FLAGS] [OPTIONS] [TO_EMAIL]
```

Check out the [dedicated README.md](./cli/README.md) for all options and flags.

### 3. Programmatic Usage [[Full docs](https://docs.rs/check-if-email-exists)]

In your own Rust project, you can add `check-if-email-exists` in your `Cargo.toml`:

```toml
[dependencies]
check-if-email-exists = "0.9"
```

And use it in your code as follows:

```rust
use check_if_email_exists::{check_email, CheckEmailInput, CheckEmailInputProxy};

async fn check() {
    // Let's say we want to test the deliverability of someone@gmail.com.
    let mut input = CheckEmailInput::new(vec!["someone@gmail.com".into()]);

    // Verify this email, using async/await syntax.
    let result = check_email(&input).await;

    // `result` is a `Vec<CheckEmailOutput>`, where the CheckEmailOutput
    // struct contains all information about our email.
    println!("{:?}", result);
}
```

The reference docs are hosted on [docs.rs](https://docs.rs/check-if-email-exists).

## ✈️ JSON Output

The output will be a JSON with the below format, the fields should be self-explanatory. For `someone@gmail.com` (note that it is disabled by Gmail), here's the exact output:

```json
{
	"input": "someone@gmail.com",
	"is_reachable": "invalid",
	"misc": {
		"is_disposable": false,
		"is_role_account": false,
		"is_b2c": true
	},
	"mx": {
		"accepts_mail": true,
		"records": [
			"alt3.gmail-smtp-in.l.google.com.",
			"gmail-smtp-in.l.google.com.",
			"alt1.gmail-smtp-in.l.google.com.",
			"alt4.gmail-smtp-in.l.google.com.",
			"alt2.gmail-smtp-in.l.google.com."
		]
	},
	"smtp": {
		"can_connect_smtp": true,
		"has_full_inbox": false,
		"is_catch_all": false,
		"is_deliverable": false,
		"is_disabled": true
	},
	"syntax": {
		"domain": "gmail.com",
		"is_valid_syntax": true,
		"username": "someone",
		"suggestion": null
	}
}
```

## What Does This Tool Check?

| Included? | Feature                                       | Description                                                                                                                     | JSON field                                                                |
| --------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| ✅        | **Email reachability**                        | How confident are we in sending an email to this address? Can be one of `safe`, `risky`, `invalid` or `unknown`.                | `is_reachable`                                                            |
| ✅        | **Syntax validation**                         | Is the address syntactically valid?                                                                                             | `syntax.is_valid_syntax`                                                  |
| ✅        | **DNS records validation**                    | Does the domain of the email address have valid MX DNS records?                                                                 | `mx.accepts_mail`                                                         |
| ✅        | **Disposable email address (DEA) validation** | Is the address provided by a known [disposable email address](https://en.wikipedia.org/wiki/Disposable_email_address) provider? | `misc.is_disposable`                                                      |
| ✅        | **SMTP server validation**                    | Can the mail exchanger of the email address domain be contacted successfully?                                                   | `smtp.can_connect_smtp`                                                   |
| ✅        | **Email deliverability**                      | Is an email sent to this address deliverable?                                                                                   | `smtp.is_deliverable`                                                     |
| ✅        | **Mailbox disabled**                          | Has this email address been disabled by the email provider?                                                                     | `smtp.is_disabled`                                                        |
| ✅        | **Full inbox**                                | Is the inbox of this mailbox full?                                                                                              | `smtp.has_full_inbox`                                                     |
| ✅        | **Catch-all address**                         | Is this email address a [catch-all](https://debounce.io/blog/help/what-is-a-catch-all-or-accept-all/) address?                  | `smtp.is_catch_all`                                                       |
| ✅        | **Role account validation**                   | Is the email address a well-known role account?                                                                                 | `misc.is_role_account`                                                    |
| ✅        | **Gravatar Url**                              | The url of the [Gravatar](https://gravatar.com/) email address profile picture                                                  | `misc.gravatar_url`                                                       |
| ✅        | **Have I Been Pwned?**                        | Has this email been compromised in a [data breach](https://haveibeenpwned.com/)?                                                | `misc.haveibeenpwned`                                                     |
| 🔜        | **Free email provider check**                 | Is the email address bound to a known free email provider?                                                                      | [Issue #89](https://github.com/reacherhq/check-if-email-exists/issues/89) |
| 🔜        | **Syntax validation, provider-specific**      | According to the syntactic rules of the target mail provider, is the address syntactically valid?                               | [Issue #90](https://github.com/reacherhq/check-if-email-exists/issues/90) |
| 🔜        | **Honeypot detection**                        | Does email address under test hide a [honeypot](https://en.wikipedia.org/wiki/Spamtrap)?                                        | [Issue #91](https://github.com/reacherhq/check-if-email-exists/issues/91) |

## 🤔 Why?

Many online services (https://hunter.io, https://verify-email.org, https://email-checker.net) offer this service for a paid fee. Here is an open-source alternative to those tools.

## License

`check-if-email-exists`'s source code is provided under a **dual license model**.

### Commercial license

If you want to use `check-if-email-exists` to develop commercial sites, tools, and applications, the Commercial License is the appropriate license. With this option, your source code is kept proprietary. Purchase a `check-if-email-exists` Commercial License at https://reacher.email/pricing.

### Open source license

If you are creating an open-source application under a license compatible with the GNU Affero GPL License v3, you may use `check-if-email-exists` under the terms of the [AGPL-3.0](./LICENSE.AGPL).

[➡️ Read more](https://docs.reacher.email/self-hosting/licensing) about Reacher's license.

## 🔨 Build From Source

Build the [CLI from source](./cli/README.md#build-from-source) or the [HTTP backend from source](./backend/README.md#build-from-source).


================================================
FILE: backend/Cargo.toml
================================================
[package]
name = "reacher_backend"
version = "0.11.7"
edition = "2018"
license = "AGPL-3.0"
publish = false

[dependencies]
anyhow = "1.0"
async-smtp = { version = "0.9.1", features = ["runtime-tokio"] }
check-if-email-exists = { path = "../core", features = ["sentry"] }
config = "0.14"
csv = "1.3.0"
dotenv = "0.15.0"
futures = { version = "0.3.30" }
http = "1.2.0"
lapin = { version = "2.3.1" }
tokio-executor-trait = { version = "2.1.1" }
tokio-reactor-trait = { version = "1.1.0" }
openssl = { version = "0.10.69", features = ["vendored"] }
reqwest = { version = "0.12.15", default-features = false, features = [
    "json",
    "rustls-tls",
] }
sentry = { version = "0.36", default-features = false, features = [
    "reqwest",
    "rustls",
] }
sentry-anyhow = "0.32"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", default-features = false, features = [
    "runtime-tokio-rustls",
    "postgres",
    "uuid",
    "chrono",
    "json",
    "migrate",
] }
sqlxmq = "0.5"
thiserror = "2.0"
tokio = { version = "1.40", features = ["macros"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
uuid = "1.10"
warp = "0.3"

[dev-dependencies]
serial_test = "3.2"
toml = "0.8"


================================================
FILE: backend/Dockerfile
================================================
# From https://shaneutt.com/blog/rust-fast-small-docker-image-builds/

# ------------------------------------------------------------------------------
# Cargo Build Stage
# ------------------------------------------------------------------------------

FROM messense/rust-musl-cross:x86_64-musl as cargo-build

WORKDIR /usr/src/reacher

RUN rm -f target/x86_64-unknown-linux-musl/release/deps/reacher*

COPY . .

ENV SQLX_OFFLINE=true

RUN cargo build --bin reacher_backend --release --target=x86_64-unknown-linux-musl

# ------------------------------------------------------------------------------
# Final Stage
# ------------------------------------------------------------------------------

FROM zenika/alpine-chrome:123

WORKDIR /home/reacher/

USER root

# Install chromedriver
# https://github.com/Zenika/alpine-chrome/blob/master/with-chromedriver/Dockerfile
RUN apk add --no-cache chromium-chromedriver

COPY --from=cargo-build /usr/src/reacher/target/x86_64-unknown-linux-musl/release/reacher_backend .
COPY --from=cargo-build /usr/src/reacher/backend/docker.sh .
COPY --from=cargo-build /usr/src/reacher/backend/backend_config.toml .

RUN chown chrome:chrome reacher_backend
RUN chown chrome:chrome docker.sh

# User chrome was created in zenika/alpine-chrome
USER chrome

ENV RUST_LOG=reacher=info
ENV RCH__HTTP_HOST=0.0.0.0
# Currently this Dockerfile is mainly used for single-shot verifications, so we
# disable the worker by default.
ENV RCH__WORKER__ENABLE=false

EXPOSE 8080

# Remove entrypoint from parent Docker file
# https://stackoverflow.com/questions/40122152/how-to-remove-entrypoint-from-parent-image-on-dockerfile
ENTRYPOINT []

CMD ["./docker.sh"]


================================================
FILE: backend/README.md
================================================
[![Docker](https://img.shields.io/docker/v/reacherhq/backend?color=0db7ed&label=docker&sort=date)](https://hub.docker.com/r/reacherhq/backend)
[![Actions Status](https://github.com/reacherhq/check-if-email-exists/workflows/pr/badge.svg)](https://github.com/reacherhq/check-if-email-exists/actions)

<br /><br />

<p align="center"><img align="center" src="https://storage.googleapis.com/saasify-uploads-prod/696e287ad79f0e0352bc201b36d701849f7d55e7.svg" height="96" alt="reacher" /></p>
<h1 align="center">⚙️ Reacher Backend</h1>
<h4 align="center">REST Server for Reacher Email Verification API: https://reacher.email.</h4>

<br /><br />

This crate holds the backend for [Reacher](https://reacher.email). The backend is both a HTTP server and a email verification worker. It has with the following components:

-   [`check-if-email-exists`](https://github.com/reacherhq/check-if-email-exists), which performs the core email verification logic,
-   [`warp`](https://github.com/seanmonstar/warp) web framework,
-   [`RabbitMQ`](https://www.rabbitmq.com/) worker for consuming a queue of incoming verification requests.

## Get Started

The [Docker image](./Dockerfile) is hosted on Docker Hub: https://hub.docker.com/r/reacherhq/backend.

To run it, run the following command:

```bash
docker run -p 8080:8080 reacherhq/backend:latest
```

Then send a `POST http://localhost:8080/v0/check_email` request with the following body:

```js
{
    "to_email": "someone@gmail.com",
    "proxy": {                        // (optional) SOCK5 proxy to run the verification through, default is empty
        "host": "my-proxy.io",
        "port": 1080,
        "username": "me",             // (optional) Proxy username
        "password": "pass"            // (optional) Proxy password
    },
}
```

## Configuration

The backend is configured via its [`backend_config.toml`](./backend_config.toml) file.

## API Documentation

See the full [OpenAPI documentation](https://docs.reacher.email/advanced/openapi).

## Build From Source

You can build the backend from source to generate a binary, and run the server locally on your machine. First, [install Rust](https://www.rust-lang.org/tools/install); you'll need Rust 1.37.0 or later. Make sure `openssl` is installed too. Then, run the following commands:

```bash
# Download the code
$ git clone https://github.com/reacherhq/check-if-email-exists
$ cd check-if-email-exists/backend

# Run the backend binary in release mode (slower build, but more performant).
$ cargo run --release --bin reacher_backend
```

The server will then be listening on `http://127.0.0.1:8080`.


================================================
FILE: backend/backend_config.toml
================================================
# Backend configuration.

# Name to identify the backend.
#
# Env variable: RCH__BACKEND_NAME
backend_name = "backend-dev"

# Host to bind the backend to.
#
# Env variable: RCH__HTTP_HOST
http_host = "127.0.0.1"

# Port for the backend.
#
# Env variable: RCH__HTTP_PORT
http_port = 8080

# Name to use during the EHLO/HELO command in the SMTP conversation.
# Ideally, this should match the reverse DNS of the server's IP address.
#
# Env variable: RCH__HELLO_NAME
hello_name = "localhost"

# Email to use during the MAIL FROM command in the SMTP conversation.
# Ideally, the domain of this email should match the "hello_name" above.
#
# Env variable: RCH__FROM_EMAIL
from_email = "hello@localhost"

# Timeout for each SMTP connection, in seconds. Leaving it commented out will
# not set a timeout, i.e. the connection will wait indefinitely. If using a
# proxy, this timeout includes both the time to connect to the proxy and the
# time to connect to perform the whole SMTP verification. Also see:
# `proxy.timeout_ms`.
#
# Env variable: RCH__SMTP_TIMEOUT
# smtp_timeout = 45

# Shared secret between a trusted client and the backend, required in the
# `x-reacher-secret` header of all incoming requests.
#
# Env variable: RCH__HEADER_SECRET
# header_secret = "my-secret"

# Optional Sentry DSN. If set, all errors will be sent to Sentry.
#
# Env variable: RCH__SENTRY_DSN
# sentry_dsn = "<PASTE_YOUR_DSN_HERE>"

# Address of the Chrome WebDriver server for headless email verifications.
#
# Env variable: RCH__WEBDRIVER_ADDR
webdriver_addr = "http://localhost:9515"

# Uncomment the line `[proxy]` below to route all SMTP verification requests
# through a specified proxy.
# [proxy]

# The proxy host and port. The proxy must be a SOCKS5 proxy to work with the
# SMTP protocol. This proxy will not be used for headless verifications.
#
# Env variables:
# - RCH__PROXY__HOST
# - RCH__PROXY__PORT
#
# Uncomment the two lines below if the `[proxy]` section is uncommented.
# host = "my.proxy.com"
# port = 1080

# Username and password for the proxy. These are optional and only needed if
# the proxy requires authentication.
#
# Env variables:
# - RCH__PROXY__USERNAME
# - RCH__PROXY__PASSWORD
#
# Uncomment the two lines below if needed.
# username = "my-username"
# password = "my-password"

# This is the timeout for the proxy connection, in milliseconds. Please note
# that this is not the timeout for the SMTP connection itself, but rather the
# timeout for the connection to the proxy server only. As such, it can be kept
# quite low, for example 5000-10000ms. For a full timeout of the SMTP
# connection, please use the `smtp_timeout` field above.
#
# Env variable: RCH__PROXY__TIMEOUT_MS
#
# Uncomment the line below if needed.
# timeout_ms = 10000

[webdriver]
# Path to the Chrome binary. If not set, the default system Chrome will be used.
#
# Env variable: RCH__WEBDRIVER__BINARY
# binary = "/usr/bin/google-chrome"

# Override verification method to use for each email provider. Each email provider can
# be verified using one of the following methods:
# - Gmail: smtp
# - Hotmail B2B: smtp
# - Hotmail B2C: headless or smtp
# - Yahoo: headless or smtp
# - Mimecast: smtp
# - Proofpoint: smtp
#
# For the email providers you choose to verify using the "smtp" method, you
# may add additional configuration, such as hello_name, from_email, and
# whether to use a proxy or not.
#
# If using proxies, the list of proxies must be defined in the "proxies"
# section below, with a unique name for each proxy such as "proxy1", "proxy2",
# etc. Then, in the email provider's SMTP configuration, set the value to the
# name of the proxy to use. To use the proxy defined the the top-level "proxy"
# section, set the value to "default".
[overrides]
# Use the "proxies" configuration below to route SMTP verification requests
# through a specified proxy.
#
# Reacher allows you to configure multiple proxies, each with a unique name.
# We recommend simply using "proxy1", "proxy2", etc. as the proxy names.
#
# In the `overrides` section below, you can specify which proxy to use for
# each email provider. For example, to use "proxy1" for Gmail and "proxy2" for
# Yahoo, set the `gmail` field to "proxy1" and the `yahoo` field to "proxy2".
[overrides.proxies]
# Uncomment the lines below to configure a proxy. The username and password are
# optional and only needed if the proxy requires authentication.
#
# Env variables:
# - RCH__OVERRIDES__PROXIES__PROXY1__HOST
# - RCH__OVERRIDES__PROXIES__PROXY1__PORT
# - RCH__OVERRIDES__PROXIES__PROXY1__USERNAME
# - RCH__OVERRIDES__PROXIES__PROXY1__PASSWORD
# proxy1 = { host = "my.proxy1.com", port = 1080, username = "my-username1", password = "my-password1" }
# proxy2 = { host = "my.proxy2.com", port = 1081 }

# Set overrides for Gmail. If uncommented, make sure to uncomment all fields.
# [overrides.gmail]
# type = "smtp"
# proxy = "proxy1"
# hello_name = "my-domain.com"
# from_email = "hello@my-domain.com"

# For each email provider, you can override the verification method and set
# additional configuration. The available fields are the same as for the
# "gmail" section above.

# [overrides.hotmailb2b]

# [overrides.hotmailb2c]

# [overrides.mimecast]

# [overrides.proofpoint]

# [overrides.yahoo]

# Throttle the maximum number of requests per second, per minute, per hour, and
# per day for this worker.
# All fields are optional; comment them out to disable the limit.
#
# We however recommend setting the throttle for at least the per-minute and
# per-day limits to prevent the IPs from being blocked by the email providers.
# The default values are set to 60 requests per minute and 10,000 requests per
# day.
#
# Important: these throttle configurations only apply to /v1/* endpoints, and
# not to the previous /v0/check_email endpoint. The latter endpoint always
# executes the verification immediately, regardless of the throttle settings.
#
# Env variables:
# - RCH__THROTTLE__MAX_REQUESTS_PER_SECOND
# - RCH__THROTTLE__MAX_REQUESTS_PER_MINUTE
# - RCH__THROTTLE__MAX_REQUESTS_PER_HOUR
# - RCH__THROTTLE__MAX_REQUESTS_PER_DAY
[throttle]
# max_requests_per_second = 20
# max_requests_per_minute = 60
# max_requests_per_hour = 1000
# max_requests_per_day = 10000

# Configuration for a queue-based architecture for Reacher. This feature is
# currently in **beta**. The queue-based architecture allows Reacher to scale
# horizontally by running multiple workers that consume emails from a RabbitMQ
# queue.
#
# To enable the queue-based architecture, set the "enable" field to "true" and
# configure the RabbitMQ connection below. The "concurrency" field specifies
# the number of concurrent emails to verify for this worker.
#
# For more information, see the documentation at:
# https://docs.reacher.email/self-hosting/scaling-for-production
[worker]
# Enable the worker to consume emails from the RabbitMQ queues. If set, the
# RabbitMQ configuration below must be set as well.
#
# Env variable: RCH__WORKER__ENABLE
enable = false

# RabbitMQ configuration.
[worker.rabbitmq]
# Env variable: RCH__WORKER__RABBITMQ__URL
url = "amqp://guest:guest@localhost:5672"

# Number of concurrent emails to verify for this worker.
#
# Env variable: RCH__WORKER__RABBITMQ__CONCURRENCY
concurrency = 5

# Below are the configurations for the storage of the email verification
# results. We currently support the following storage backends:
# - Postgres
#
# Uncomment the following line to configure the storage to use Postgres.
# [storage.postgres]

# # URL to connect to the Postgres database.
#
# Env variable: RCH__STORAGE__POSTGRES__DB_URL
# db_url = "postgresql://localhost/reacherdb"
#
# If you wish to store additional data along with the verification results,
# you can add a JSON object to the "extra" field. This object will be stored
# as a JSONB column in the database. This is for example useful to track who
# initiated the verification request in a multi-tenant system.
# 
# Env variable: RCH__STORAGE__POSTGRES__EXTRA
# extra = { "my_custom_key" = "my_custom_value" }


================================================
FILE: backend/docker.sh
================================================
#!/bin/ash

# This is the Dockerfile's entrypoint script.
# https://docs.docker.com/config/containers/multi-service_container/

chromedriver &
./reacher_backend


================================================
FILE: backend/migrations/20210316025847_setup.down.sql
================================================
DROP FUNCTION mq_checkpoint;
DROP FUNCTION mq_keep_alive;
DROP FUNCTION mq_delete;
DROP FUNCTION mq_commit;
DROP FUNCTION mq_insert;
DROP FUNCTION mq_poll;
DROP FUNCTION mq_active_channels;
DROP FUNCTION mq_latest_message;
DROP TABLE mq_payloads;
DROP TABLE mq_msgs;
DROP FUNCTION mq_uuid_exists;
DROP TYPE mq_new_t;


================================================
FILE: backend/migrations/20210316025847_setup.up.sql
================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- The UDT for creating messages
CREATE TYPE mq_new_t AS (
    -- Unique message ID
    id UUID,
    -- Delay before message is processed
    delay INTERVAL,
    -- Number of retries if initial processing fails
    retries INT,
    -- Initial backoff between retries
    retry_backoff INTERVAL,
    -- Name of channel
    channel_name TEXT,
    -- Arguments to channel
    channel_args TEXT,
    -- Interval for two-phase commit (or NULL to disable two-phase commit)
    commit_interval INTERVAL,
    -- Whether this message should be processed in order with respect to other
    -- ordered messages.
    ordered BOOLEAN,
    -- Name of message
    name TEXT,
    -- JSON payload
    payload_json TEXT,
    -- Binary payload
    payload_bytes BYTEA
);

-- Small, frequently updated table of messages
CREATE TABLE mq_msgs (
    id UUID PRIMARY KEY,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    attempt_at TIMESTAMPTZ DEFAULT NOW(),
    attempts INT NOT NULL DEFAULT 5,
    retry_backoff INTERVAL NOT NULL DEFAULT INTERVAL '1 second',
    channel_name TEXT NOT NULL,
    channel_args TEXT NOT NULL,
    commit_interval INTERVAL,
    after_message_id UUID DEFAULT uuid_nil() REFERENCES mq_msgs(id) ON DELETE SET DEFAULT
);

-- Insert dummy message so that the 'nil' UUID can be referenced
INSERT INTO mq_msgs (id, channel_name, channel_args, after_message_id) VALUES (uuid_nil(), '', '', NULL);

-- Internal helper function to check that a UUID is neither NULL nor NIL
CREATE FUNCTION mq_uuid_exists(
    id UUID
) RETURNS BOOLEAN AS $$
	SELECT id IS NOT NULL AND id != uuid_nil()
$$ LANGUAGE SQL IMMUTABLE;

-- Index for polling
CREATE INDEX ON mq_msgs(channel_name, channel_args, attempt_at) WHERE id != uuid_nil() AND NOT mq_uuid_exists(after_message_id);
-- Index for adding messages
CREATE INDEX ON mq_msgs(channel_name, channel_args, created_at, id) WHERE id != uuid_nil() AND after_message_id IS NOT NULL;

-- Index for ensuring strict message order
CREATE UNIQUE INDEX mq_msgs_channel_name_channel_args_after_message_id_idx ON mq_msgs(channel_name, channel_args, after_message_id);


-- Large, less frequently updated table of message payloads
CREATE TABLE mq_payloads(
    id UUID PRIMARY KEY,
    name TEXT NOT NULL,
    payload_json JSONB,
    payload_bytes BYTEA
);

-- Internal helper function to return the most recently added message in a queue.
CREATE FUNCTION mq_latest_message(from_channel_name TEXT, from_channel_args TEXT)
RETURNS UUID AS $$
    SELECT COALESCE(
        (
            SELECT id FROM mq_msgs
            WHERE channel_name = from_channel_name
            AND channel_args = from_channel_args
            AND after_message_id IS NOT NULL
            AND id != uuid_nil()
            ORDER BY created_at DESC, id DESC
            LIMIT 1
        ),
        uuid_nil()
    )
$$ LANGUAGE SQL STABLE;

-- Internal helper function to randomly select a set of channels with "ready" messages.
CREATE FUNCTION mq_active_channels(channel_names TEXT[], batch_size INT)
RETURNS TABLE(name TEXT, args TEXT) AS $$
    SELECT channel_name, channel_args
    FROM mq_msgs
    WHERE id != uuid_nil()
    AND attempt_at <= NOW()
    AND (channel_names IS NULL OR channel_name = ANY(channel_names))
    AND NOT mq_uuid_exists(after_message_id)
    GROUP BY channel_name, channel_args
    ORDER BY RANDOM()
    LIMIT batch_size
$$ LANGUAGE SQL STABLE;

-- Main entry-point for job runner: pulls a batch of messages from the queue.
CREATE FUNCTION mq_poll(channel_names TEXT[], batch_size INT DEFAULT 1)
RETURNS TABLE(
    id UUID,
    is_committed BOOLEAN,
    name TEXT,
    payload_json TEXT,
    payload_bytes BYTEA,
    retry_backoff INTERVAL,
    wait_time INTERVAL
) AS $$
BEGIN
    RETURN QUERY UPDATE mq_msgs
    SET
        attempt_at = CASE WHEN mq_msgs.attempts = 1 THEN NULL ELSE NOW() + mq_msgs.retry_backoff END,
        attempts = mq_msgs.attempts - 1,
        retry_backoff = mq_msgs.retry_backoff * 2
    FROM (
        SELECT
            msgs.id
        FROM mq_active_channels(channel_names, batch_size) AS active_channels
        INNER JOIN LATERAL (
            SELECT * FROM mq_msgs
            WHERE mq_msgs.id != uuid_nil()
            AND mq_msgs.attempt_at <= NOW()
            AND mq_msgs.channel_name = active_channels.name
            AND mq_msgs.channel_args = active_channels.args
            AND NOT mq_uuid_exists(mq_msgs.after_message_id)
            ORDER BY mq_msgs.attempt_at ASC
            LIMIT batch_size
        ) AS msgs ON TRUE
        LIMIT batch_size
    ) AS messages_to_update
    LEFT JOIN mq_payloads ON mq_payloads.id = messages_to_update.id
    WHERE mq_msgs.id = messages_to_update.id
    RETURNING
        mq_msgs.id,
        mq_msgs.commit_interval IS NULL,
        mq_payloads.name,
        mq_payloads.payload_json::TEXT,
        mq_payloads.payload_bytes,
        mq_msgs.retry_backoff / 2,
        interval '0' AS wait_time;

    IF NOT FOUND THEN
        RETURN QUERY SELECT
            NULL::UUID,
            NULL::BOOLEAN,
            NULL::TEXT,
            NULL::TEXT,
            NULL::BYTEA,
            NULL::INTERVAL,
            MIN(mq_msgs.attempt_at) - NOW()
        FROM mq_msgs
        WHERE mq_msgs.id != uuid_nil()
        AND NOT mq_uuid_exists(mq_msgs.after_message_id)
        AND (channel_names IS NULL OR mq_msgs.channel_name = ANY(channel_names));
    END IF;
END;
$$ LANGUAGE plpgsql;

-- Creates new messages
CREATE FUNCTION mq_insert(new_messages mq_new_t[])
RETURNS VOID AS $$
BEGIN
    PERFORM pg_notify(CONCAT('mq_', channel_name), '')
    FROM unnest(new_messages) AS new_msgs
    GROUP BY channel_name;

    IF FOUND THEN
        PERFORM pg_notify('mq', '');
    END IF;

    INSERT INTO mq_payloads (
        id,
        name,
        payload_json,
        payload_bytes
    ) SELECT
        id,
        name,
        payload_json::JSONB,
        payload_bytes
    FROM UNNEST(new_messages);

    INSERT INTO mq_msgs (
        id,
        attempt_at,
        attempts,
        retry_backoff,
        channel_name,
        channel_args,
        commit_interval,
        after_message_id
    )
    SELECT
        id,
        NOW() + delay + COALESCE(commit_interval, INTERVAL '0'),
        retries + 1,
        retry_backoff,
        channel_name,
        channel_args,
        commit_interval,
        CASE WHEN ordered
            THEN
                LAG(id, 1, mq_latest_message(channel_name, channel_args))
                OVER (PARTITION BY channel_name, channel_args, ordered ORDER BY id)
            ELSE
                NULL
            END
    FROM UNNEST(new_messages);
END;
$$ LANGUAGE plpgsql;

-- Commits messages previously created with a non-NULL commit interval.
CREATE FUNCTION mq_commit(msg_ids UUID[])
RETURNS VOID AS $$
BEGIN
    UPDATE mq_msgs
    SET
        attempt_at = attempt_at - commit_interval,
        commit_interval = NULL
    WHERE id = ANY(msg_ids)
    AND commit_interval IS NOT NULL;
END;
$$ LANGUAGE plpgsql;


-- Deletes messages from the queue. This occurs when a message has been
-- processed, or when it expires without being processed.
CREATE FUNCTION mq_delete(msg_ids UUID[])
RETURNS VOID AS $$
BEGIN
    PERFORM pg_notify(CONCAT('mq_', channel_name), '')
    FROM mq_msgs
    WHERE id = ANY(msg_ids)
    AND after_message_id = uuid_nil()
    GROUP BY channel_name;

    IF FOUND THEN
        PERFORM pg_notify('mq', '');
    END IF;

    DELETE FROM mq_msgs WHERE id = ANY(msg_ids);
    DELETE FROM mq_payloads WHERE id = ANY(msg_ids);
END;
$$ LANGUAGE plpgsql;


-- Can be called during the initial commit interval, or when processing
-- a message. Indicates that the caller is still active and will prevent either
-- the commit interval elapsing or the message being retried for the specified
-- interval.
CREATE FUNCTION mq_keep_alive(msg_ids UUID[], duration INTERVAL)
RETURNS VOID AS $$
    UPDATE mq_msgs
    SET
        attempt_at = NOW() + duration,
        commit_interval = commit_interval + ((NOW() + duration) - attempt_at)
    WHERE id = ANY(msg_ids)
    AND attempt_at < NOW() + duration;
$$ LANGUAGE SQL;


-- Called during lengthy processing of a message to checkpoint the progress.
-- As well as behaving like `mq_keep_alive`, the message payload can be
-- updated.
CREATE FUNCTION mq_checkpoint(
    msg_id UUID,
    duration INTERVAL,
    new_payload_json TEXT,
    new_payload_bytes BYTEA,
    extra_retries INT
)
RETURNS VOID AS $$
    UPDATE mq_msgs
    SET
        attempt_at = GREATEST(attempt_at, NOW() + duration),
        attempts = attempts + COALESCE(extra_retries, 0)
    WHERE id = msg_id;

    UPDATE mq_payloads
    SET
        payload_json = COALESCE(new_payload_json::JSONB, payload_json),
        payload_bytes = COALESCE(new_payload_bytes, payload_bytes)
    WHERE
        id = msg_id;
$$ LANGUAGE SQL;



================================================
FILE: backend/migrations/20210921115907_clear.down.sql
================================================
DROP FUNCTION mq_clear;
DROP FUNCTION mq_clear_all;


================================================
FILE: backend/migrations/20210921115907_clear.up.sql
================================================
-- Deletes all messages from a list of channel names.
CREATE FUNCTION mq_clear(channel_names TEXT[])
RETURNS VOID AS $$
BEGIN
    WITH deleted_ids AS (
        DELETE FROM mq_msgs WHERE channel_name = ANY(channel_names) RETURNING id
    )
    DELETE FROM mq_payloads WHERE id IN (SELECT id FROM deleted_ids);
END;
$$ LANGUAGE plpgsql;

-- Deletes all messages.
CREATE FUNCTION mq_clear_all()
RETURNS VOID AS $$
BEGIN
    WITH deleted_ids AS (
        DELETE FROM mq_msgs RETURNING id
    )
    DELETE FROM mq_payloads WHERE id IN (SELECT id FROM deleted_ids);
END;
$$ LANGUAGE plpgsql;


================================================
FILE: backend/migrations/20211013151757_fix_mq_latest_message.down.sql
================================================
CREATE OR REPLACE FUNCTION mq_latest_message(from_channel_name TEXT, from_channel_args TEXT)
RETURNS UUID AS $$
    SELECT COALESCE(
        (
            SELECT id FROM mq_msgs
            WHERE channel_name = from_channel_name
            AND channel_args = from_channel_args
            AND after_message_id IS NOT NULL
            AND id != uuid_nil()
            ORDER BY created_at DESC, id DESC
            LIMIT 1
        ),
        uuid_nil()
    )
$$ LANGUAGE SQL STABLE;


================================================
FILE: backend/migrations/20211013151757_fix_mq_latest_message.up.sql
================================================
CREATE OR REPLACE FUNCTION mq_latest_message(from_channel_name TEXT, from_channel_args TEXT)
RETURNS UUID AS $$
    SELECT COALESCE(
        (
            SELECT id FROM mq_msgs
            WHERE channel_name = from_channel_name
            AND channel_args = from_channel_args
            AND after_message_id IS NOT NULL
            AND id != uuid_nil()
            AND NOT EXISTS(
                SELECT * FROM mq_msgs AS mq_msgs2
                WHERE mq_msgs2.after_message_id = mq_msgs.id
            )
            ORDER BY created_at DESC
            LIMIT 1
        ),
        uuid_nil()
    )
$$ LANGUAGE SQL STABLE;

================================================
FILE: backend/migrations/20220117025847_email_data.down.sql
================================================
DROP INDEX job_emails;
DROP TABLE email_results;
DROP TABLE bulk_jobs;
DROP TYPE valid_status;

================================================
FILE: backend/migrations/20220117025847_email_data.up.sql
================================================
CREATE TABLE bulk_jobs (
    id SERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    total_records INTEGER NOT NULL
);
CREATE TABLE email_results (
    id SERIAL PRIMARY KEY,
    job_id INTEGER,
    result JSONB,
    FOREIGN KEY (job_id) REFERENCES bulk_jobs(id)
);
CREATE INDEX job_emails ON email_results USING HASH (job_id);

================================================
FILE: backend/migrations/20220208120856_fix_concurrent_poll.down.sql
================================================
-- Main entry-point for job runner: pulls a batch of messages from the queue.
CREATE OR REPLACE FUNCTION mq_poll(channel_names TEXT[], batch_size INT DEFAULT 1)
RETURNS TABLE(
    id UUID,
    is_committed BOOLEAN,
    name TEXT,
    payload_json TEXT,
    payload_bytes BYTEA,
    retry_backoff INTERVAL,
    wait_time INTERVAL
) AS $$
BEGIN
    RETURN QUERY UPDATE mq_msgs
    SET
        attempt_at = CASE WHEN mq_msgs.attempts = 1 THEN NULL ELSE NOW() + mq_msgs.retry_backoff END,
        attempts = mq_msgs.attempts - 1,
        retry_backoff = mq_msgs.retry_backoff * 2
    FROM (
        SELECT
            msgs.id
        FROM mq_active_channels(channel_names, batch_size) AS active_channels
        INNER JOIN LATERAL (
            SELECT * FROM mq_msgs
            WHERE mq_msgs.id != uuid_nil()
            AND mq_msgs.attempt_at <= NOW()
            AND mq_msgs.channel_name = active_channels.name
            AND mq_msgs.channel_args = active_channels.args
            AND NOT mq_uuid_exists(mq_msgs.after_message_id)
            ORDER BY mq_msgs.attempt_at ASC
            LIMIT batch_size
        ) AS msgs ON TRUE
        LIMIT batch_size
    ) AS messages_to_update
    LEFT JOIN mq_payloads ON mq_payloads.id = messages_to_update.id
    WHERE mq_msgs.id = messages_to_update.id
    RETURNING
        mq_msgs.id,
        mq_msgs.commit_interval IS NULL,
        mq_payloads.name,
        mq_payloads.payload_json::TEXT,
        mq_payloads.payload_bytes,
        mq_msgs.retry_backoff / 2,
        interval '0' AS wait_time;

    IF NOT FOUND THEN
        RETURN QUERY SELECT
            NULL::UUID,
            NULL::BOOLEAN,
            NULL::TEXT,
            NULL::TEXT,
            NULL::BYTEA,
            NULL::INTERVAL,
            MIN(mq_msgs.attempt_at) - NOW()
        FROM mq_msgs
        WHERE mq_msgs.id != uuid_nil()
        AND NOT mq_uuid_exists(mq_msgs.after_message_id)
        AND (channel_names IS NULL OR mq_msgs.channel_name = ANY(channel_names));
    END IF;
END;
$$ LANGUAGE plpgsql;

================================================
FILE: backend/migrations/20220208120856_fix_concurrent_poll.up.sql
================================================

-- Main entry-point for job runner: pulls a batch of messages from the queue.
CREATE OR REPLACE FUNCTION mq_poll(channel_names TEXT[], batch_size INT DEFAULT 1)
RETURNS TABLE(
    id UUID,
    is_committed BOOLEAN,
    name TEXT,
    payload_json TEXT,
    payload_bytes BYTEA,
    retry_backoff INTERVAL,
    wait_time INTERVAL
) AS $$
BEGIN
    RETURN QUERY UPDATE mq_msgs
    SET
        attempt_at = CASE WHEN mq_msgs.attempts = 1 THEN NULL ELSE NOW() + mq_msgs.retry_backoff END,
        attempts = mq_msgs.attempts - 1,
        retry_backoff = mq_msgs.retry_backoff * 2
    FROM (
        SELECT
            msgs.id
        FROM mq_active_channels(channel_names, batch_size) AS active_channels
        INNER JOIN LATERAL (
            SELECT mq_msgs.id FROM mq_msgs
            WHERE mq_msgs.id != uuid_nil()
            AND mq_msgs.attempt_at <= NOW()
            AND mq_msgs.channel_name = active_channels.name
            AND mq_msgs.channel_args = active_channels.args
            AND NOT mq_uuid_exists(mq_msgs.after_message_id)
            ORDER BY mq_msgs.attempt_at ASC
            LIMIT batch_size
        ) AS msgs ON TRUE
        LIMIT batch_size
    ) AS messages_to_update
    LEFT JOIN mq_payloads ON mq_payloads.id = messages_to_update.id
    WHERE mq_msgs.id = messages_to_update.id
    AND mq_msgs.attempt_at <= NOW()
    RETURNING
        mq_msgs.id,
        mq_msgs.commit_interval IS NULL,
        mq_payloads.name,
        mq_payloads.payload_json::TEXT,
        mq_payloads.payload_bytes,
        mq_msgs.retry_backoff / 2,
        interval '0' AS wait_time;

    IF NOT FOUND THEN
        RETURN QUERY SELECT
            NULL::UUID,
            NULL::BOOLEAN,
            NULL::TEXT,
            NULL::TEXT,
            NULL::BYTEA,
            NULL::INTERVAL,
            MIN(mq_msgs.attempt_at) - NOW()
        FROM mq_msgs
        WHERE mq_msgs.id != uuid_nil()
        AND NOT mq_uuid_exists(mq_msgs.after_message_id)
        AND (channel_names IS NULL OR mq_msgs.channel_name = ANY(channel_names));
    END IF;
END;
$$ LANGUAGE plpgsql;


================================================
FILE: backend/migrations/20220713122907_fix-clear_all-keep-nil-message.down.sql
================================================
-- Add down migration script here


================================================
FILE: backend/migrations/20220713122907_fix-clear_all-keep-nil-message.up.sql
================================================
CREATE OR REPLACE FUNCTION mq_clear(channel_names TEXT[])
RETURNS VOID AS $$
BEGIN
    WITH deleted_ids AS (
        DELETE FROM mq_msgs
        WHERE channel_name = ANY(channel_names)
          AND id != uuid_nil()
        RETURNING id
    )
    DELETE FROM mq_payloads WHERE id IN (SELECT id FROM deleted_ids);
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION mq_clear IS
    'Deletes all messages with corresponding payloads from a list of channel names';


CREATE OR REPLACE FUNCTION mq_clear_all()
RETURNS VOID AS $$
BEGIN
    WITH deleted_ids AS (
        DELETE FROM mq_msgs
        WHERE id != uuid_nil()
        RETURNING id
    )
    DELETE FROM mq_payloads WHERE id IN (SELECT id FROM deleted_ids);
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION mq_clear_all IS
    'Deletes all messages with corresponding payloads';


================================================
FILE: backend/migrations/20220810141100_result_created_at.down.sql
================================================
ALTER TABLE email_results
DROP COLUMN created_at;


================================================
FILE: backend/migrations/20220810141100_result_created_at.up.sql
================================================
ALTER TABLE email_results
ADD created_at TIMESTAMPTZ NOT NULL DEFAULT NOW();

================================================
FILE: backend/migrations/20240929230957_v1_worker_results.down.sql
================================================
-- Add down migration script here
DROP INDEX IF EXISTS v1_worker_results_job_id;
DROP TABLE IF EXISTS v1_task_result;
DROP TABLE IF EXISTS v1_bulk_job;


================================================
FILE: backend/migrations/20240929230957_v1_worker_results.up.sql
================================================
CREATE TABLE v1_bulk_job (
    id SERIAL PRIMARY KEY,
    total_records INTEGER NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);

CREATE TABLE v1_task_result (
    id SERIAL PRIMARY KEY,
    job_id INTEGER REFERENCES v1_bulk_job(id) ON DELETE CASCADE,
    payload JSONB NOT NULL,
    extra JSONB, -- any extra data that needs to be stored
    result JSONB,
    error TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);

CREATE INDEX idx_v1_task_result_job_id ON v1_task_result (job_id);


================================================
FILE: backend/migrations/README.md
================================================
# Database and Migrations

## Migrations

All migrations in this folder are embedded directly in the `reacher_backend` binary, so you don't need to run the migrations manually.

The migrations come from 2 sources:

-   `sqlxmq` migrations
-   Reacher's own migrations

## `sqlxmq` migrations

The following migration files have been copied from the [sqlxmq repo](https://github.com/Diggsey/sqlxmq) as per the [given instructions](https://github.com/Diggsey/sqlxmq/blob/6d3ed6fb99e7592e370a7f3ec074ce0bebae62fd/README.md?plain=1#L111):

-   `20210316025847_setup.{up,down}.sql`
-   `20210921115907_clear.{up,down}.sql`
-   `20211013151757_fix_mq_latest_message.{up,down}.sql`
-   `20220208120856_fix_concurrent_poll.{up,down}.sql`
-   `20220713122907_fix-clear_all-keep-nil-message.{up,down}.sql`

## Reacher migrations

The following migrations are specific to Reacher:

-   `20220117025847_email_data.{up,down}.sql`: set up the `bulk_jobs` and `email_results` tables
-   `20220810141100_result_created_at.{up,down}.sql`: add a `created_at` column on `email_result`

## Advanced Usage

For more advanced usage (such as reverting to an old state), please use the `sqlx` CLI command.

See https://github.com/launchbadge/sqlx/blob/main/sqlx-cli/README.md


================================================
FILE: backend/openapi.json
================================================
{
	"openapi": "3.0.0",
	"info": {
		"title": "Reacher",
		"version": "0.11.0",
		"description": "### What is Reacher?\n\nReacher is a robust, open-source email verification API service available as both a SaaS and self-hosted solution.",
		"license": {
			"name": "AGPL-3.0 OR Commercial",
			"url": "https://github.com/reacherhq/check-if-email-exists/blob/master/LICENSE.md"
		},
		"contact": {
			"name": "Reacher",
			"url": "https://reacher.email",
			"email": "amaury@reacher.email"
		},
		"termsOfService": "https://github.com/reacherhq/policies/blob/master/terms/index.fr.md"
	},
	"servers": [
		{
			"url": "https://api.reacher.email",
			"description": "Reacher Production"
		}
	],
	"paths": {
		"/v1/check_email": {
			"post": {
				"summary": "/v1/check_email",
				"responses": {
					"200": {
						"description": "OK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"$ref": "#/components/schemas/CheckEmailOutput"
								}
							}
						}
					}
				},
				"operationId": "post-v1-check-email",
				"x-stoplight": {
					"id": "yho6jrv7p04qv"
				},
				"description": "Perform a comprehensive verification of an email address. This endpoint supersedes the previous `/v0/check_email` endpoint, maintaining the same input and output format. Unlike the `/v0/check_email` endpoint, the new `/v1/check_email` endpoint queues the email for verification, and the Reacher server processes the queue based on its configuration settings such as throttle and concurrency.",
				"requestBody": {
					"description": "Request object containing all parameters necessary for an email verification.",
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"$ref": "#/components/schemas/CheckEmailRequest"
							}
						}
					}
				}
			},
			"parameters": [],
			"servers": []
		},
		"/v1/bulk": {
			"post": {
				"summary": "/v1/bulk",
				"responses": {
					"200": {
						"description": "OK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": ["job_id"],
									"properties": {
										"job_id": {
											"type": "integer",
											"x-stoplight": {
												"id": "614agdy9mxybk"
											},
											"description": "The unique ID generated for this bulk verification job. Use this `job_id` to query the progress or results of the bulk verification."
										}
									}
								}
							}
						}
					}
				},
				"operationId": "post-v1-bulk",
				"x-stoplight": {
					"id": "jpoyy08arq31b"
				},
				"description": "Initiate a bulk email verification.",
				"requestBody": {
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": ["input"],
								"properties": {
									"input": {
										"type": "array",
										"x-stoplight": {
											"id": "fsitj4yhkzk8e"
										},
										"description": "A list of emails to verify.",
										"items": {
											"x-stoplight": {
												"id": "kgcb3i4u5sfgr"
											},
											"type": "string"
										}
									},
									"webhook": {
										"$ref": "#/components/schemas/TaskWebhook"
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/bulk/{job_id}": {
			"parameters": [
				{
					"schema": {
						"type": "integer"
					},
					"name": "job_id",
					"in": "path",
					"required": true,
					"description": "The unique bulk verification job ID"
				}
			],
			"get": {
				"summary": "/v1/bulk/{job_id}",
				"responses": {
					"200": {
						"description": "OK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"job_id",
										"created_at",
										"total_records",
										"total_processed",
										"summary",
										"job_status"
									],
									"properties": {
										"job_id": {
											"type": "integer",
											"x-stoplight": {
												"id": "4f6vqudy7zank"
											}
										},
										"created_at": {
											"type": "string",
											"x-stoplight": {
												"id": "7baymbmoaj5vo"
											},
											"format": "date-time",
											"description": "The date and time when the bulk verification job was created."
										},
										"finished_at": {
											"type": "string",
											"x-stoplight": {
												"id": "7lhzxsb1ixnvz"
											},
											"format": "date-time",
											"description": "If the bulk verification job is completed, the date and time when it was finished."
										},
										"total_records": {
											"type": "integer",
											"x-stoplight": {
												"id": "y8q2zhagq8zd6"
											},
											"description": "The number of emails to verify in the bulk verification job."
										},
										"total_processed": {
											"type": "integer",
											"x-stoplight": {
												"id": "oqxvsorhd06ch"
											},
											"description": "The number of emails that have been verified at the time of the query."
										},
										"summary": {
											"type": "object",
											"x-stoplight": {
												"id": "n5q382zces1lq"
											},
											"description": "A summary of the processed emails.",
											"required": [
												"total_safe",
												"total_invalid",
												"total_risky",
												"total_unknown"
											],
											"properties": {
												"total_safe": {
													"type": "integer",
													"x-stoplight": {
														"id": "4nss0vedcrjc4"
													},
													"description": "The number of emails where `is_reachable` is \"safe\"."
												},
												"total_invalid": {
													"type": "integer",
													"x-stoplight": {
														"id": "uzzk7ija2l5sv"
													},
													"description": "The number of emails where `is_reachable` is \"invalid\"."
												},
												"total_risky": {
													"type": "integer",
													"x-stoplight": {
														"id": "sqjtoggge6us3"
													},
													"description": "The number of emails where `is_reachable` is \"risky\"."
												},
												"total_unknown": {
													"type": "integer",
													"x-stoplight": {
														"id": "8lsodukakqreu"
													},
													"description": "The number of emails where `is_reachable` is \"unknown\"."
												}
											}
										},
										"job_status": {
											"x-stoplight": {
												"id": "tsw5sp5cxe7ad"
											},
											"enum": ["Running", "Completed"],
											"description": "The status of the job, either \"Running\" or \"Completed\"."
										}
									}
								}
							}
						}
					}
				},
				"operationId": "get-v1-bulk",
				"x-stoplight": {
					"id": "phrhxrak01ja6"
				},
				"description": "Retrieve the progress of a bulk verification job."
			}
		},
		"/v1/bulk/{job_id}/results": {
			"parameters": [
				{
					"schema": {
						"type": "string"
					},
					"name": "job_id",
					"in": "path",
					"required": true,
					"description": "The unique bulk verification job ID"
				}
			],
			"get": {
				"summary": "Retrieve bulk verification results",
				"responses": {
					"200": {
						"description": "OK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": ["results"],
									"properties": {
										"results": {
											"$ref": "#/components/schemas/CheckEmailOutput"
										}
									}
								}
							}
						}
					}
				},
				"operationId": "get-v1-bulk-results",
				"x-stoplight": {
					"id": "skx9nlfib3but"
				},
				"description": "Retrieve the results of a bulk verification job. This endpoint will return an error if the job is still running. Please query `GET /v1/bulk/{job_id}` first to check the job's progress.",
				"parameters": [
					{
						"schema": {
							"type": "integer",
							"default": 50
						},
						"in": "query",
						"name": "limit",
						"description": "The number of results to return."
					},
					{
						"schema": {
							"type": "integer"
						},
						"in": "query",
						"name": "offset",
						"description": "The offset from which to return the results, equivalent to the number of elements in the array to skip."
					}
				]
			}
		},
		"/v0/check_email": {
			"post": {
				"summary": "/v0/check_email",
				"responses": {
					"200": {
						"description": "OK",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/CheckEmailOutput"
								},
								"examples": {}
							}
						}
					}
				},
				"operationId": "post-v0-check-email",
				"description": "**Sunset notice: please use `/v1/check_email` instead.** Both endpoints accept the same input arguments and return the same output; only their internal implementation differs. Perform a comprehensive verification of an email address. Unlike the `/v1/check_email` endpoint, this endpoint performs an email verification immediately, without considering the Reacher server's throttling, concurrency, and other configurations. As such, this endpoint is slightly riskier than `/v1/check_email`, as the Reacher server's IP reputation can be impacted if this endpoint is called too frequently.",
				"requestBody": {
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/CheckEmailRequest"
							}
						}
					},
					"description": "Request object containing all parameters necessary for an email verification."
				},
				"parameters": [
					{
						"schema": {
							"type": "string"
						},
						"in": "header",
						"name": "Authorization",
						"description": "Your personal Reacher API key",
						"required": true
					}
				],
				"x-stoplight": {
					"id": "9cw3vilj3q88h"
				},
				"deprecated": true
			},
			"parameters": []
		}
	},
	"components": {
		"schemas": {
			"CheckEmailRequest": {
				"title": "CheckEmailRequest",
				"x-stoplight": {
					"id": "834398a8ce480"
				},
				"type": "object",
				"description": "A request object to perform an email verification. The `to_email` field is required, all other fields are optional.",
				"required": ["to_email"],
				"properties": {
					"from_email": {
						"type": "string",
						"description": "In the SMTP connection, the FROM email address."
					},
					"to_email": {
						"type": "string",
						"description": "The email address to check."
					},
					"hello_name": {
						"type": "string",
						"description": "In the SMTP connection, the EHLO hostname."
					},
					"proxy": {
						"$ref": "#/components/schemas/CheckEmailInputProxy"
					},
					"smtp_port": {
						"type": "number",
						"description": "SMTP port to use for email validation. Defaults to 25, but 465, 587, and 2525 are sometimes also used."
					},
					"gmail_verif_method": {
						"$ref": "#/components/schemas/GmailVerifMethod"
					},
					"hotmailb2b_verif_method": {
						"$ref": "#/components/schemas/HotmailB2BVerifMethod"
					},
					"hotmailb2c_verif_method": {
						"$ref": "#/components/schemas/HotmailB2CVerifMethod"
					},
					"yahoo_verif_method": {
						"$ref": "#/components/schemas/YahooVerifMethod"
					},
					"check_gravatar": {
						"type": "boolean",
						"description": "Whether to check if a Gravatar image exists for the given email."
					}
				}
			},
			"CheckEmailOutput": {
				"title": "CheckEmailOutput",
				"type": "object",
				"x-examples": {
					"Example with test@gmail.com": {
						"input": "test@gmail.com",
						"is_reachable": "invalid",
						"misc": {
							"is_disposable": false,
							"is_role_account": true
						},
						"mx": {
							"accepts_mail": true,
							"records": [
								"alt4.gmail-smtp-in.l.google.com.",
								"alt2.gmail-smtp-in.l.google.com.",
								"alt3.gmail-smtp-in.l.google.com.",
								"gmail-smtp-in.l.google.com.",
								"alt1.gmail-smtp-in.l.google.com."
							]
						},
						"smtp": {
							"can_connect_smtp": true,
							"has_full_inbox": false,
							"is_catch_all": false,
							"is_deliverable": false,
							"is_disabled": false
						},
						"syntax": {
							"domain": "gmail.com",
							"is_valid_syntax": true,
							"username": "test"
						}
					}
				},
				"description": "The result of the email verification process.",
				"required": [
					"input",
					"is_reachable",
					"misc",
					"mx",
					"smtp",
					"syntax"
				],
				"properties": {
					"input": {
						"type": "string",
						"format": "email",
						"description": "The email address that was verified."
					},
					"is_reachable": {
						"$ref": "#/components/schemas/Reachable"
					},
					"misc": {
						"oneOf": [
							{
								"$ref": "#/components/schemas/MiscDetails"
							},
							{
								"$ref": "#/components/schemas/CoreError"
							}
						],
						"description": "Additional information about the email account."
					},
					"mx": {
						"oneOf": [
							{
								"$ref": "#/components/schemas/MxDetails"
							},
							{
								"$ref": "#/components/schemas/CoreError"
							}
						],
						"description": "Details obtained from querying the mail server's MX records."
					},
					"smtp": {
						"oneOf": [
							{
								"$ref": "#/components/schemas/SmtpDetails"
							},
							{
								"$ref": "#/components/schemas/CoreError"
							}
						],
						"description": "Results from connecting to the mail server via SMTP."
					},
					"syntax": {
						"$ref": "#/components/schemas/SyntaxDetails"
					},
					"debug": {
						"$ref": "#/components/schemas/DebugDetails"
					}
				}
			},
			"Reachable": {
				"type": "string",
				"title": "Reachable",
				"enum": ["invalid", "unknown", "safe", "risky"],
				"description": "An enumeration describing the confidence level that the recipient address is valid: `safe`, `risky`, `invalid`, or `unknown`. Refer to our FAQ for detailed definitions: https://help.reacher.email/email-attributes-inside-json."
			},
			"MiscDetails": {
				"title": "MiscDetails",
				"type": "object",
				"description": "Additional information about the email account.",
				"required": ["is_disposable", "is_role_account", "is_b2c"],
				"properties": {
					"is_disposable": {
						"type": "boolean",
						"description": "Indicates if the email address is from a known disposable email provider."
					},
					"is_role_account": {
						"type": "boolean",
						"description": "Indicates if the email address is a role-based account."
					},
					"gravatar_url": {
						"type": "string",
						"description": "URL to the Gravatar profile picture associated with the email, if available and requested."
					},
					"is_b2c": {
						"type": "boolean",
						"x-stoplight": {
							"id": "0cxn26qlxy8r4"
						},
						"description": "Is this a B2C email address?"
					}
				}
			},
			"MxDetails": {
				"title": "MxDetails",
				"type": "object",
				"properties": {
					"accepts_mail": {
						"type": "boolean",
						"description": "Indicates if the mail server accepts emails."
					},
					"records": {
						"type": "array",
						"description": "List of Fully Qualified Domain Names (FQDN) of the mail server.",
						"items": {
							"type": "string"
						}
					}
				},
				"required": ["accepts_mail", "records"],
				"description": "Details about the mail server's MX records."
			},
			"SmtpDetails": {
				"title": "SmtpDetails",
				"type": "object",
				"description": "Results from SMTP connection attempts to the mail server.",
				"properties": {
					"can_connect_smtp": {
						"type": "boolean",
						"description": "Indicates if the mail exchanger can be contacted successfully."
					},
					"has_full_inbox": {
						"type": "boolean",
						"description": "Indicates if the mailbox is full."
					},
					"is_catch_all": {
						"type": "boolean",
						"description": "Indicates if the email address is a catch-all address."
					},
					"is_deliverable": {
						"type": "boolean",
						"description": "Indicates if an email sent to this address is deliverable."
					},
					"is_disabled": {
						"type": "boolean",
						"description": "Indicates if the email address has been disabled by the provider."
					}
				},
				"required": [
					"can_connect_smtp",
					"has_full_inbox",
					"is_catch_all",
					"is_deliverable",
					"is_disabled"
				]
			},
			"SyntaxDetails": {
				"title": "SyntaxDetails",
				"type": "object",
				"description": "Validation of the email address syntax.",
				"properties": {
					"domain": {
						"type": "string",
						"description": "The domain part of the email address."
					},
					"is_valid_syntax": {
						"type": "boolean",
						"description": "Indicates if the email address syntax is valid."
					},
					"username": {
						"type": "string",
						"description": "The username part of the email address."
					}
				},
				"required": ["domain", "is_valid_syntax", "username"]
			},
			"CoreError": {
				"title": "CoreError",
				"x-stoplight": {
					"id": "a872ead6474cd"
				},
				"type": "object",
				"description": "Details of an error encountered during the verification process.",
				"properties": {
					"type": {
						"type": "string",
						"description": "The type of error."
					},
					"message": {
						"type": "string",
						"description": "A human-readable description of the error."
					}
				},
				"required": ["type", "message"]
			},
			"YahooVerifMethod": {
				"type": "string",
				"title": "YahooVerifMethod",
				"enum": ["Api", "Headless", "Smtp"],
				"description": "Enumeration describing the method used to verify Yahoo emails."
			},
			"HotmailB2BVerifMethod": {
				"type": "string",
				"x-stoplight": {
					"id": "ntdugsleyotut"
				},
				"title": "HotmailB2BVerifMethod",
				"enum": ["Smtp"],
				"description": "Enumeration describing the method used to verify Hotmail B2B emails."
			},
			"HotmailB2CVerifMethod": {
				"type": "string",
				"x-stoplight": {
					"id": "cuc5bj6ra2t0i"
				},
				"title": "HotmailB2CVerifMethod",
				"enum": ["Smtp", "Headless"],
				"description": "Enumeration describing the method used to verify Hotmail B2C emails."
			},
			"GmailVerifMethod": {
				"type": "string",
				"x-stoplight": {
					"id": "xo5r48yhtxiwr"
				},
				"title": "GmailVerifMethod",
				"enum": ["Smtp"],
				"description": "Enumeration describing the method used to verify Gmail emails.",
				"x-internal": false
			},
			"CheckEmailInputProxy": {
				"title": "CheckEmailInputProxy",
				"type": "object",
				"x-examples": {
					"example-1": {
						"value": {
							"host": "my-proxy.io",
							"port": 1080
						}
					}
				},
				"properties": {
					"host": {
						"type": "string",
						"description": "The proxy host address."
					},
					"port": {
						"type": "integer",
						"description": "The proxy port number."
					},
					"username": {
						"type": "string",
						"description": "Username for proxy authentication."
					},
					"password": {
						"type": "string",
						"description": "Password for proxy authentication."
					}
				},
				"required": ["host", "port"],
				"description": "Proxy configuration for email verification."
			},
			"DebugDetails": {
				"title": "DebugDetails",
				"x-stoplight": {
					"id": "4wxlk39h8v9kz"
				},
				"type": "object",
				"properties": {
					"start_time": {
						"type": "string",
						"x-stoplight": {
							"id": "60i65k60m8e8d"
						},
						"description": "The timestamp when the email verification started."
					},
					"end_time": {
						"type": "string",
						"x-stoplight": {
							"id": "zw4ccbvwsoh6q"
						},
						"description": "The timestamp when the email verification ended."
					},
					"duration": {
						"$ref": "#/components/schemas/Duration"
					},
					"server_name": {
						"type": "string",
						"x-stoplight": {
							"id": "2jrbdecvqh4t5"
						},
						"description": "The name of the server that performed the verification."
					},
					"smtp": {
						"$ref": "#/components/schemas/DebugDetailsSmtp"
					}
				},
				"required": [
					"start_time",
					"end_time",
					"duration",
					"server_name",
					"smtp"
				]
			},
			"Duration": {
				"title": "Duration",
				"x-stoplight": {
					"id": "bg9y0iez4zati"
				},
				"type": "object",
				"description": "An object representing a duration in seconds and nanoseconds.",
				"properties": {
					"secs": {
						"type": "number",
						"description": "Duration in seconds."
					},
					"nanos": {
						"type": "number",
						"description": "Duration in nanoseconds."
					}
				},
				"required": ["secs", "nanos"]
			},
			"DebugDetailsSmtp": {
				"title": "DebugDetailsSmtp",
				"x-stoplight": {
					"id": "2a90bzapppo0j"
				},
				"type": "object",
				"properties": {
					"verif_method": {
						"$ref": "#/components/schemas/VerifMethod"
					}
				},
				"description": "SMTP details used for debugging, including the verification method."
			},
			"VerifMethod": {
				"title": "VerifMethod",
				"x-stoplight": {
					"id": "9xw9e1jwti230"
				},
				"type": "object",
				"description": "The method used for email verification.",
				"required": ["type"],
				"properties": {
					"type": {
						"x-stoplight": {
							"id": "4ogsz639tcdb6"
						},
						"enum": ["Smtp", "Headless", "Api", "Skipped"],
						"description": "The method used for the email verification."
					}
				}
			},
			"TaskWebhook": {
				"title": "TaskWebhook",
				"x-stoplight": {
					"id": "6053d3ngu2hfn"
				},
				"type": "object",
				"description": "Optional webhook configuration for sending email verification results during bulk verification.",
				"properties": {
					"on_each_email": {
						"$ref": "#/components/schemas/Webhook"
					}
				}
			},
			"Webhook": {
				"title": "Webhook",
				"x-stoplight": {
					"id": "p9emghvcb92fj"
				},
				"type": "object",
				"description": "Configuration for a webhook to receive email verification results. The method will be POST, and the body will contain the email verification response.",
				"required": ["url"],
				"properties": {
					"url": {
						"type": "string",
						"x-stoplight": {
							"id": "iraaa1ism4bzi"
						},
						"description": "The URL to send the email verification results to."
					},
					"extra": {
						"type": "object",
						"x-stoplight": {
							"id": "f20boz81d2fei"
						}
					}
				}
			}
		},
		"securitySchemes": {
			"Authorization": {
				"name": "Authorization",
				"type": "apiKey",
				"in": "header",
				"description": "A Reacher API key is required for all requests. Sign up on https://reacher.email to get your personal API key."
			}
		},
		"requestBodies": {}
	},
	"security": [
		{
			"Authorization": []
		}
	]
}


================================================
FILE: backend/scripts/debian11.sh
================================================
#!/usr/bin/env bash

# Install script of Reacher Backend on an OVH debian 11 server.
# As a postinstall, this script is meant to be run once, but for convenience,
# it's actually idempotent.

# Fail early.
set -e

# You can change the default values of these variables inline here, or by
# setting them in the environment before running this script, e.g.:
# RCH__BACKEND_NAME="my-own-name" ./debian11.sh

# An unique identifier for the backend.
RCH__BACKEND_NAME=${RCH__BACKEND_NAME:-"backend1.mycompany.com"}
# Docker Hub tag for reacherhq/backend.
RCH_VERSION=${RCH_VERSION:-"v0.10.0-beta.1"}
# Optional: Send bug reports to a Sentry.io dashboard.
RCH__SENTRY_DSN=${RCH__SENTRY_DSN:-}
# Protect the backend from the public via a `x-reacher-secret` header.
RCH__HEADER_SECRET=${RCH__HEADER_SECRET:-}
# For the "FROM" field in emails.
RCH__FROM_EMAIL=${RCH__FROM_EMAIL:-"hello@mycompany.com"}
# For the "EHLO" field in emails. This should ideally match the server's
# reverse DNS entry for optimal results.
RCH__HELLO_NAME=${RCH__HELLO_NAME:-"backend1.mycompany.com"}
# Timeout for SMTP connections in seconds.
RCH__SMTP_TIMEOUT=${RCH__SMTP_TIMEOUT:-"90"}
# Proxy settings.
RCH__PROXY__HOST=${RCH__PROXY__HOST:-}
RCH__PROXY__PORT=${RCH__PROXY__PORT:-}
RCH__PROXY__USERNAME=${RCH__PROXY__USERNAME:-}
RCH__PROXY__PASSWORD=${RCH__PROXY__PASSWORD:-}
# Logging. Setup to "debug" to show all logs.
RUST_LOG=${RUST_LOG:-"info"}

echo "Installing Reacher backend $RCH_VERSION on host $RCH__BACKEND_NAME..."

# Install Docker
# https://docs.docker.com/engine/install/debian/
sudo apt-get update
sudo apt-get upgrade --yes
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release \
    --yes
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes
echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
    $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin --yes

# Create `docker` group
# https://docs.docker.com/engine/install/linux-postinstall/
getent group docker || sudo groupadd docker
sudo usermod -aG docker debian
# Reload users and groups, see
# https://superuser.com/questions/272061/reload-a-linux-users-group-assignments-without-logging-out
sudo su - $USER << EOF

# Stop all previous docker containers and images
docker stop reacher_backend
docker rm reacher_backend

# Run the backend
docker run -d \
    -e RUST_LOG=$RUST_LOG \
    -e RCH__BACKEND_NAME=$RCH__BACKEND_NAME \
    -e RCH__SENTRY_DSN=$RCH__SENTRY_DSN \
    -e RCH__HEADER_SECRET=$RCH__HEADER_SECRET \
    -e RCH__FROM_EMAIL=$RCH__FROM_EMAIL \
    -e RCH__HELLO_NAME=$RCH__HELLO_NAME \
    -e RCH__SMTP_TIMEOUT=$RCH__SMTP_TIMEOUT \
    -p 80:8080 \
    --name reacher_backend \
    reacherhq/backend:$RCH_VERSION

echo "Everything set. You can close this terminal."
EOF


================================================
FILE: backend/src/bin/prune_db.rs
================================================
use sqlx::PgPool;
use sqlx::Result;
use tracing::info;

#[tokio::main]
async fn main() -> Result<()> {
	dotenv::dotenv().expect("Unable to load environment variables from .env file");
	tracing_subscriber::fmt::init();

	let db_url = std::env::var("DATABASE_URL").expect("Unable to read DATABASE_URL env var");
	let dry_mode: bool = std::env::var("DRY_RUN").is_ok();
	let days_old_str = std::env::var("DAYS_OLD").expect("Unable to read DAYS_OLD env var");
	let days_old: i32 = days_old_str
		.parse()
		.expect("Unable to parse DAYS_OLD as integer");

	let pool = PgPool::connect(&db_url).await?;

	// Fetch the list of job IDs that match the criteria
	let query = format!(
		"SELECT b.id
        FROM bulk_jobs b
        JOIN (
            SELECT job_id, COUNT(*) as total_processed
            FROM email_results
            GROUP BY job_id
        ) e ON b.id = e.job_id
        WHERE b.total_records = e.total_processed
        AND b.created_at <= current_date - interval '{} days'",
		days_old
	);

	let job_ids_to_delete: Vec<(i32,)> = sqlx::query_as(&query).fetch_all(&pool).await?;

	match (dry_mode, job_ids_to_delete.is_empty()) {
		(true, _) => info!("Job ids to delete {:?}", job_ids_to_delete),
		(false, true) => info!("No jobs to delete"),
		(false, false) => {
			// Start a transaction
			let tx = pool.begin().await?;

			// Before deleting from bulk_jobs, delete the corresponding records from email_results in a batch
			let delete_email_results_query =
				"DELETE FROM email_results WHERE job_id = ANY($1::int[])";

			// Convert job_ids_to_delete to Vec<i32> before binding
			let job_ids_to_delete_vec: Vec<i32> =
				job_ids_to_delete.iter().map(|&(id,)| id).collect();

			// Execute the delete query for email_results in a batch within the transaction
			sqlx::query(delete_email_results_query)
				.bind(&job_ids_to_delete_vec)
				.execute(&pool) // Use execute on the query builder
				.await?;

			info!(
				"Email results for job IDs {:?} deleted successfully.",
				job_ids_to_delete
			);

			// safely delete the records from bulk_jobs
			let delete_bulk_jobs_query = "DELETE FROM bulk_jobs WHERE id = ANY($1::int[])";

			// Execute the delete query for bulk_jobs in a batch within the transaction
			sqlx::query(delete_bulk_jobs_query)
				.bind(&job_ids_to_delete_vec)
				.execute(&pool) // Use execute on the query builder
				.await?;

			info!(
				"Bulk jobs records with IDs {:?} deleted successfully.",
				job_ids_to_delete
			);

			// Commit the transaction if both deletes are successful
			tx.commit().await?;
		}
	}

	Ok(())
}


================================================
FILE: backend/src/config.rs
================================================
// Reacher - Email Verification
// Copyright (C) 2018-2023 Reacher

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::storage::{postgres::PostgresStorage, StorageAdapter};
use crate::throttle::ThrottleManager;
use crate::worker::do_work::TaskWebhook;
use crate::worker::setup_rabbit_mq;
use anyhow::{bail, Context};
use check_if_email_exists::smtp::verif_method::{
	EverythingElseVerifMethod, GmailVerifMethod, HotmailB2BVerifMethod, HotmailB2CVerifMethod,
	MimecastVerifMethod, ProofpointVerifMethod, VerifMethod, VerifMethodSmtpConfig,
	YahooVerifMethod, DEFAULT_PROXY_ID,
};
use check_if_email_exists::{CheckEmailInputProxy, WebdriverConfig, LOG_TARGET};
use config::Config;
use lapin::Channel;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tracing::warn;

#[derive(Debug, Serialize, Deserialize)]
pub struct BackendConfig {
	/// Name of the backend.
	pub backend_name: String,

	// Fields from VerifMethodSmtpConfig
	pub from_email: String,
	pub hello_name: String,
	/// Timeout for each SMTP connection, in seconds. Leaving it commented out
	/// will not set a timeout, i.e. the connection will wait indefinitely.
	pub smtp_timeout: Option<u
Download .txt
gitextract_puvhqcc5/

├── .dockerignore
├── .editorconfig
├── .envrc
├── .gitbook.yaml
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── config.yml
│   │   └── issue_form.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── deploy_backend.yml
│       ├── deploy_cli.yml
│       ├── pr.yml
│       └── pr_cli.yml
├── .gitignore
├── .sqlx/
│   ├── query-068521cf9e77f563b3791cce500d95660c56e852770a4eac47576089e704322a.json
│   ├── query-0968b040845ecce236576f65df3d3648f7ce03bc5ae0ebe9f36f878c309061c8.json
│   ├── query-1039a6d3d732a86b9b3b2e19e6c8e3c857125d1c4cf916ac32789bfd0176b6b5.json
│   ├── query-13862fe23ea729215fb1cfee3aadc14dfa9373dc8137c4f1da199e3ae66efd50.json
│   ├── query-1a964da4784832e5f631f2c2e727382532c86c7e66e46254d72ef0af03021975.json
│   ├── query-1e5a713008953f95d626e578ed0f2bc2cfc2b1599d8c5d09d11287a661fccd0e.json
│   ├── query-323970c6cb4f5d4b7c0cb3293d1e6fd146c7d682e28e6cb72de6bbe14a3e04b7.json
│   ├── query-47af0157fa867e147e49d80b121b1881df93a6619434a1fd1fc9a58315b4044b.json
│   ├── query-594dfc319a34dfdcf316758798a9ad099c298480b4f1dabc69b1dc6e2f4687a0.json
│   ├── query-96a32768eb750971bad4df10560b0331bf52b44dbd59020369adbbc6bc8dc830.json
│   ├── query-981f650b6c663feeae8a93e7ecf86326e7a5e6d5c8fd03c03565d86982d0381a.json
│   ├── query-ac5e197ca20a1393e4ea45248d5e702c0edbbf57624f2bb416f0fd0401a44dcf.json
│   ├── query-d682d43a07f3969bcb3113ae3600766e9197d48bdc7ccb73a53c938cb45f910f.json
│   ├── query-de8a3af8119e17c38b2f60cafac3b712359553bc295db5689f254f623d172326.json
│   └── query-f58d4d05a6ab4c1ffda39396df4c403f7588266ae8d954985fc1eda9751febcc.json
├── .well-known/
│   └── funding-manifest-urls
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE.AGPL
├── LICENSE.md
├── Makefile
├── README.md
├── backend/
│   ├── Cargo.toml
│   ├── Dockerfile
│   ├── README.md
│   ├── backend_config.toml
│   ├── docker.sh
│   ├── migrations/
│   │   ├── 20210316025847_setup.down.sql
│   │   ├── 20210316025847_setup.up.sql
│   │   ├── 20210921115907_clear.down.sql
│   │   ├── 20210921115907_clear.up.sql
│   │   ├── 20211013151757_fix_mq_latest_message.down.sql
│   │   ├── 20211013151757_fix_mq_latest_message.up.sql
│   │   ├── 20220117025847_email_data.down.sql
│   │   ├── 20220117025847_email_data.up.sql
│   │   ├── 20220208120856_fix_concurrent_poll.down.sql
│   │   ├── 20220208120856_fix_concurrent_poll.up.sql
│   │   ├── 20220713122907_fix-clear_all-keep-nil-message.down.sql
│   │   ├── 20220713122907_fix-clear_all-keep-nil-message.up.sql
│   │   ├── 20220810141100_result_created_at.down.sql
│   │   ├── 20220810141100_result_created_at.up.sql
│   │   ├── 20240929230957_v1_worker_results.down.sql
│   │   ├── 20240929230957_v1_worker_results.up.sql
│   │   └── README.md
│   ├── openapi.json
│   ├── scripts/
│   │   └── debian11.sh
│   ├── src/
│   │   ├── bin/
│   │   │   └── prune_db.rs
│   │   ├── config.rs
│   │   ├── http/
│   │   │   ├── error.rs
│   │   │   ├── mod.rs
│   │   │   ├── v0/
│   │   │   │   ├── bulk/
│   │   │   │   │   ├── db.rs
│   │   │   │   │   ├── error.rs
│   │   │   │   │   ├── get.rs
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   ├── post.rs
│   │   │   │   │   ├── results/
│   │   │   │   │   │   ├── csv_helper.rs
│   │   │   │   │   │   └── mod.rs
│   │   │   │   │   └── task.rs
│   │   │   │   ├── check_email/
│   │   │   │   │   ├── backwardcompat.rs
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── post.rs
│   │   │   │   └── mod.rs
│   │   │   ├── v1/
│   │   │   │   ├── bulk/
│   │   │   │   │   ├── get_progress.rs
│   │   │   │   │   ├── get_results/
│   │   │   │   │   │   ├── csv_helper.rs
│   │   │   │   │   │   └── mod.rs
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── post.rs
│   │   │   │   ├── check_email/
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── post.rs
│   │   │   │   └── mod.rs
│   │   │   └── version/
│   │   │       ├── get.rs
│   │   │       └── mod.rs
│   │   ├── lib.rs
│   │   ├── main.rs
│   │   ├── storage/
│   │   │   ├── commercial_license_trial.rs
│   │   │   ├── error.rs
│   │   │   ├── mod.rs
│   │   │   └── postgres.rs
│   │   ├── throttle.rs
│   │   └── worker/
│   │       ├── consume.rs
│   │       ├── do_work.rs
│   │       ├── mod.rs
│   │       └── single_shot.rs
│   └── tests/
│       ├── README.md
│       └── check_email.rs
├── ci/
│   ├── build.bash
│   ├── build.ps1
│   ├── common.bash
│   ├── set_rust_version.bash
│   └── test.bash
├── cli/
│   ├── Cargo.toml
│   ├── README.md
│   └── src/
│       └── main.rs
├── core/
│   ├── Cargo.toml
│   └── src/
│       ├── haveibeenpwned.rs
│       ├── lib.rs
│       ├── misc/
│       │   ├── b2c.txt
│       │   ├── gravatar.rs
│       │   ├── mod.rs
│       │   └── roles.txt
│       ├── mx/
│       │   └── mod.rs
│       ├── rules.json
│       ├── rules.rs
│       ├── smtp/
│       │   ├── connect.rs
│       │   ├── error.rs
│       │   ├── gmail.rs
│       │   ├── headless.rs
│       │   ├── http_api.rs
│       │   ├── mod.rs
│       │   ├── outlook/
│       │   │   ├── headless.rs
│       │   │   ├── microsoft365.rs
│       │   │   └── mod.rs
│       │   ├── parser.rs
│       │   ├── verif_method.rs
│       │   └── yahoo/
│       │       ├── api.rs
│       │       ├── headless.rs
│       │       └── mod.rs
│       ├── syntax/
│       │   ├── mod.rs
│       │   └── normalize.rs
│       └── util/
│           ├── input_output.rs
│           ├── mod.rs
│           ├── sentry.rs
│           └── ser_with_display.rs
├── docs/
│   ├── README.md
│   ├── SUMMARY.md
│   ├── advanced/
│   │   ├── migrations/
│   │   │   ├── README.md
│   │   │   ├── bulk.md
│   │   │   ├── docker-environment-variables.md
│   │   │   ├── migrating-from-0.7-to-0.10-beta.md
│   │   │   └── reacher-configuration-v0.10.md
│   │   ├── openapi/
│   │   │   ├── README.md
│   │   │   ├── v0-check_email.md
│   │   │   ├── v1-bulk.md
│   │   │   └── v1-check_email.md
│   │   └── run-your-own-proxy.md
│   ├── getting-started/
│   │   ├── is-reachable.md
│   │   └── quickstart.md
│   └── self-hosting/
│       ├── debugging-reacher.md
│       ├── install.md
│       ├── licensing/
│       │   ├── README.md
│       │   └── commercial-license-trial.md
│       ├── proxies/
│       │   ├── README.md
│       │   └── multiple-proxies.md
│       ├── reacher-configuration-v0.10.md
│       ├── saas-vs-self-host.md
│       └── scaling-for-production/
│           ├── README.md
│           ├── option-1-manage-scaling-yourself.md
│           └── option-2-rabbitmq-based-queue-architecture.md
├── rabbitmq/
│   └── docker-compose.yaml
├── rustfmt.toml
└── sqs/
    ├── .gitignore
    ├── Cargo.toml
    ├── Dockerfile
    ├── Makefile
    ├── README.md
    ├── main.tf
    └── src/
        └── main.rs
Download .txt
SYMBOL INDEX (395 symbols across 66 files)

FILE: backend/migrations/20210316025847_setup.up.sql
  type mq_msgs (line 31) | CREATE TABLE mq_msgs (
  function mq_uuid_exists (line 47) | CREATE FUNCTION mq_uuid_exists(
  type mq_msgs (line 54) | CREATE INDEX ON mq_msgs(channel_name, channel_args, attempt_at) WHERE id...
  type mq_msgs (line 56) | CREATE INDEX ON mq_msgs(channel_name, channel_args, created_at, id) WHER...
  type mq_msgs_channel_name_channel_args_after_message_id_idx (line 59) | CREATE UNIQUE INDEX mq_msgs_channel_name_channel_args_after_message_id_i...
  type mq_payloads (line 63) | CREATE TABLE mq_payloads(
  function mq_latest_message (line 71) | CREATE FUNCTION mq_latest_message(from_channel_name TEXT, from_channel_a...
  function mq_active_channels (line 88) | CREATE FUNCTION mq_active_channels(channel_names TEXT[], batch_size INT)
  function mq_poll (line 102) | CREATE FUNCTION mq_poll(channel_names TEXT[], batch_size INT DEFAULT 1)
  function mq_commit (line 216) | CREATE FUNCTION mq_commit(msg_ids UUID[])
  function mq_delete (line 231) | CREATE FUNCTION mq_delete(msg_ids UUID[])
  function mq_keep_alive (line 254) | CREATE FUNCTION mq_keep_alive(msg_ids UUID[], duration INTERVAL)
  function mq_checkpoint (line 268) | CREATE FUNCTION mq_checkpoint(

FILE: backend/migrations/20210921115907_clear.up.sql
  function mq_clear (line 2) | CREATE FUNCTION mq_clear(channel_names TEXT[])
  function mq_clear_all (line 13) | CREATE FUNCTION mq_clear_all()

FILE: backend/migrations/20211013151757_fix_mq_latest_message.down.sql
  function mq_latest_message (line 1) | CREATE OR REPLACE FUNCTION mq_latest_message(from_channel_name TEXT, fro...

FILE: backend/migrations/20211013151757_fix_mq_latest_message.up.sql
  function mq_latest_message (line 1) | CREATE OR REPLACE FUNCTION mq_latest_message(from_channel_name TEXT, fro...

FILE: backend/migrations/20220117025847_email_data.up.sql
  type bulk_jobs (line 1) | CREATE TABLE bulk_jobs (
  type email_results (line 6) | CREATE TABLE email_results (
  type job_emails (line 12) | CREATE INDEX job_emails ON email_results USING HASH (job_id)

FILE: backend/migrations/20220208120856_fix_concurrent_poll.up.sql
  function mq_poll (line 3) | CREATE OR REPLACE FUNCTION mq_poll(channel_names TEXT[], batch_size INT ...

FILE: backend/migrations/20220713122907_fix-clear_all-keep-nil-message.up.sql
  function mq_clear (line 1) | CREATE OR REPLACE FUNCTION mq_clear(channel_names TEXT[])
  function mq_clear_all (line 17) | CREATE OR REPLACE FUNCTION mq_clear_all()

FILE: backend/migrations/20240929230957_v1_worker_results.up.sql
  type v1_bulk_job (line 1) | CREATE TABLE v1_bulk_job (
  type v1_task_result (line 7) | CREATE TABLE v1_task_result (
  type idx_v1_task_result_job_id (line 17) | CREATE INDEX idx_v1_task_result_job_id ON v1_task_result (job_id)

FILE: backend/src/bin/prune_db.rs
  function main (line 6) | async fn main() -> Result<()> {

FILE: backend/src/config.rs
  type BackendConfig (line 38) | pub struct BackendConfig {
    method empty (line 95) | pub fn empty() -> Self {
    method get_verif_method (line 121) | pub fn get_verif_method(&self) -> VerifMethod {
    method must_worker_config (line 176) | pub fn must_worker_config(&self) -> Result<MustWorkerConfig, anyhow::E...
    method connect (line 191) | pub async fn connect(&mut self) -> Result<(), anyhow::Error> {
    method get_storage_adapter (line 223) | pub fn get_storage_adapter(&self) -> Arc<StorageAdapter> {
    method get_pg_pool (line 228) | pub fn get_pg_pool(&self) -> Option<PgPool> {
    method get_throttle_manager (line 235) | pub fn get_throttle_manager(&self) -> Arc<ThrottleManager> {
  type OverridesConfig (line 241) | pub struct OverridesConfig {
  type WorkerConfig (line 252) | pub struct WorkerConfig {
  type MustWorkerConfig (line 262) | pub struct MustWorkerConfig {
  type RabbitMQConfig (line 269) | pub struct RabbitMQConfig {
  type ThrottleConfig (line 276) | pub struct ThrottleConfig {
    method new_without_throttle (line 285) | pub fn new_without_throttle() -> Self {
  type StorageConfig (line 297) | pub enum StorageConfig {
  type PostgresConfig (line 306) | pub struct PostgresConfig {
  type CommercialLicenseTrialConfig (line 312) | pub struct CommercialLicenseTrialConfig {
  function load_config (line 319) | pub async fn load_config() -> Result<BackendConfig, anyhow::Error> {
  function test_proxies (line 357) | async fn test_proxies() {
  function test_default_proxy (line 378) | async fn test_default_proxy() {
  function test_deserialize_verif_method (line 406) | fn test_deserialize_verif_method() {
  function test_env_vars (line 521) | async fn test_env_vars() {
  function test_serialize_storage_config (line 536) | async fn test_serialize_storage_config() {

FILE: backend/src/http/error.rs
  type DisplayDebug (line 27) | pub trait DisplayDebug: fmt::Display + Debug + Sync + Send {}
  type ReacherResponseError (line 32) | pub struct ReacherResponseError {
    method fmt (line 51) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    method new (line 57) | pub fn new<T: DisplayDebug + 'static>(code: StatusCode, error: T) -> S...
    method from (line 66) | fn from(e: CheckEmailInputBuilderError) -> Self {
    method from (line 75) | fn from(e: serde_json::Error) -> Self {
    method from (line 81) | fn from(e: lapin::Error) -> Self {
    method from (line 87) | fn from(e: sqlx::Error) -> Self {
    method from (line 93) | fn from(e: csv::Error) -> Self {
    method from (line 99) | fn from(e: csv::IntoInnerError<csv::Writer<Vec<u8>>>) -> Self {
    method from (line 105) | fn from(e: warp::http::status::InvalidStatusCode) -> Self {
    method from (line 111) | fn from(e: anyhow::Error) -> Self {
    method from (line 117) | fn from(e: StorageError) -> Self {
    method from (line 123) | fn from(e: reqwest::Error) -> Self {
  method serialize (line 40) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  function handle_rejection (line 137) | pub async fn handle_rejection(err: warp::Rejection) -> Result<impl warp:...

FILE: backend/src/http/mod.rs
  function create_routes (line 34) | pub fn create_routes(
  function run_warp_server (line 64) | pub async fn run_warp_server(
  constant REACHER_SECRET_HEADER (line 103) | pub const REACHER_SECRET_HEADER: &str = "x-reacher-secret";
  function check_header (line 107) | pub fn check_header(config: Arc<BackendConfig>) -> warp::filters::BoxedF...

FILE: backend/src/http/v0/bulk/db.rs
  function with_db (line 22) | pub fn with_db(

FILE: backend/src/http/v0/bulk/error.rs
  type CsvError (line 20) | pub enum CsvError {
  type BulkError (line 28) | pub enum BulkError {
    method from (line 41) | fn from(e: sqlx::Error) -> Self {

FILE: backend/src/http/v0/bulk/get.rs
  type ValidStatus (line 32) | enum ValidStatus {
  type JobRecord (line 44) | struct JobRecord {
  type JobStatusSummary (line 52) | struct JobStatusSummary {
  type JobStatusResponseBody (line 61) | struct JobStatusResponseBody {
  function job_status (line 71) | async fn job_status(
  function get_bulk_job_status (line 162) | pub fn get_bulk_job_status(

FILE: backend/src/http/v0/bulk/mod.rs
  function create_job_registry (line 34) | pub async fn create_job_registry(pool: &Pool<Postgres>) -> Result<JobRun...

FILE: backend/src/http/v0/bulk/post.rs
  type CreateBulkRequestBody (line 34) | struct CreateBulkRequestBody {
  type CreateBulkRequestBodyIterator (line 43) | struct CreateBulkRequestBodyIterator {
  type Item (line 49) | type Item = TaskInput;
  type IntoIter (line 50) | type IntoIter = CreateBulkRequestBodyIterator;
  method into_iter (line 52) | fn into_iter(self) -> Self::IntoIter {
  type Item (line 61) | type Item = TaskInput;
  method next (line 63) | fn next(&mut self) -> Option<Self::Item> {
  type CreateBulkResponseBody (line 84) | struct CreateBulkResponseBody {
  function create_bulk_request (line 89) | async fn create_bulk_request(
  function create_bulk_job (line 135) | pub fn create_bulk_job(

FILE: backend/src/http/v0/bulk/results/csv_helper.rs
  type CsvWrapper (line 23) | pub struct CsvWrapper(pub serde_json::Value);
  type JobResultCsvResponse (line 28) | pub struct JobResultCsvResponse {
    type Error (line 62) | type Error = &'static str;
    method try_from (line 64) | fn try_from(value: CsvWrapper) -> Result<Self, Self::Error> {

FILE: backend/src/http/v0/bulk/results/mod.rs
  type JobResultResponseFormat (line 39) | enum JobResultResponseFormat {
  type JobResultRequest (line 47) | struct JobResultRequest {
  type JobResultJsonResponse (line 54) | struct JobResultJsonResponse {
  function job_result (line 58) | async fn job_result(
  function job_result_as_iter (line 133) | async fn job_result_as_iter(
  function job_result_json (line 170) | async fn job_result_json(
  function job_result_csv (line 186) | async fn job_result_csv(
  function get_bulk_job_result (line 238) | pub fn get_bulk_job_result(

FILE: backend/src/http/v0/bulk/task.rs
  type TaskInput (line 36) | pub struct TaskInput {
  type TaskInputIterator (line 45) | pub struct TaskInputIterator {
  type Item (line 51) | type Item = CheckEmailInput;
  type IntoIter (line 52) | type IntoIter = TaskInputIterator;
  method into_iter (line 54) | fn into_iter(self) -> Self::IntoIter {
  type Item (line 64) | type Item = CheckEmailInput;
  method next (line 66) | fn next(&mut self) -> Option<Self::Item> {
  type TaskPayload (line 115) | struct TaskPayload {
  function submit_job (line 120) | pub async fn submit_job(
  function email_verification_task (line 167) | pub async fn email_verification_task(

FILE: backend/src/http/v0/check_email/backwardcompat.rs
  type BackwardCompatYahooVerifMethod (line 17) | pub enum BackwardCompatYahooVerifMethod {
    method to_yahoo_verif_method (line 25) | pub fn to_yahoo_verif_method(
  type BackwardCompatHotmailB2CVerifMethod (line 55) | pub enum BackwardCompatHotmailB2CVerifMethod {
    method to_hotmailb2c_verif_method (line 62) | pub fn to_hotmailb2c_verif_method(

FILE: backend/src/http/v0/check_email/post.rs
  type CheckEmailRequest (line 32) | pub struct CheckEmailRequest {
    method to_check_email_input (line 45) | pub fn to_check_email_input(&self, config: Arc<BackendConfig>) -> Chec...
  function http_handler (line 114) | async fn http_handler(
  function post_check_email (line 133) | pub fn post_check_email<'a>(
  function with_config (line 150) | pub fn with_config(

FILE: backend/src/http/v1/bulk/get_progress.rs
  type ValidStatus (line 36) | enum ValidStatus {
  type JobRecord (line 48) | struct JobRecord {
  type ResponseSummary (line 56) | struct ResponseSummary {
  type Response (line 65) | struct Response {
  function http_handler (line 75) | async fn http_handler(job_id: i32, conn_pool: PgPool) -> Result<impl war...
  function v1_get_bulk_job_progress (line 148) | pub fn v1_get_bulk_job_progress(

FILE: backend/src/http/v1/bulk/get_results/csv_helper.rs
  type CsvWrapper (line 23) | pub struct CsvWrapper(pub serde_json::Value);
  type CsvResponse (line 28) | pub struct CsvResponse {
    type Error (line 59) | type Error = &'static str;
    method try_from (line 61) | fn try_from(value: CsvWrapper) -> Result<Self, Self::Error> {

FILE: backend/src/http/v1/bulk/get_results/mod.rs
  type ResponseFormat (line 38) | enum ResponseFormat {
  type Request (line 46) | struct Request {
  type Response (line 53) | struct Response {
  function http_handler (line 57) | async fn http_handler(
  function job_result_as_iter (line 112) | async fn job_result_as_iter(
  function job_result_json (line 141) | async fn job_result_json(
  function job_result_csv (line 157) | async fn job_result_csv(
  function v1_get_bulk_job_results (line 179) | pub fn v1_get_bulk_job_results(

FILE: backend/src/http/v1/bulk/mod.rs
  function with_worker_db (line 31) | pub fn with_worker_db(

FILE: backend/src/http/v1/bulk/post.rs
  type Request (line 45) | struct Request {
  type Response (line 52) | struct Response {
  function http_handler (line 56) | async fn http_handler(
  function publish_task (line 123) | pub async fn publish_task(
  function v1_create_bulk_job (line 150) | pub fn v1_create_bulk_job(

FILE: backend/src/http/v1/check_email/post.rs
  function handle_without_worker (line 40) | async fn handle_without_worker(
  function handle_with_worker (line 79) | async fn handle_with_worker(
  function http_handler (line 184) | async fn http_handler(
  function v1_check_email (line 224) | pub fn v1_check_email(

FILE: backend/src/http/version/get.rs
  type EndpointVersion (line 25) | struct EndpointVersion {
  function get_version (line 30) | pub fn get_version() -> impl Filter<Extract = (impl warp::Reply,), Error...
  function test_get_version (line 47) | async fn test_get_version() {

FILE: backend/src/lib.rs
  constant CARGO_PKG_VERSION (line 23) | const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");

FILE: backend/src/main.rs
  constant CARGO_PKG_VERSION (line 27) | const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
  function main (line 31) | async fn main() -> Result<(), anyhow::Error> {

FILE: backend/src/storage/commercial_license_trial.rs
  function send_to_reacher (line 27) | pub async fn send_to_reacher(

FILE: backend/src/storage/error.rs
  type StorageError (line 20) | pub enum StorageError {

FILE: backend/src/storage/mod.rs
  type StorageAdapter (line 28) | pub enum StorageAdapter {
    method store (line 35) | pub async fn store(
    method get_extra (line 47) | pub fn get_extra(&self) -> Option<serde_json::Value> {

FILE: backend/src/storage/postgres.rs
  type PostgresStorage (line 25) | pub struct PostgresStorage {
    method new (line 31) | pub async fn new(db_url: &str, extra: Option<serde_json::Value>) -> Re...
    method store (line 45) | pub async fn store(
    method get_extra (line 99) | pub fn get_extra(&self) -> Option<serde_json::Value> {

FILE: backend/src/throttle.rs
  type ThrottleLimit (line 29) | pub enum ThrottleLimit {
    method fmt (line 37) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type ThrottleResult (line 51) | pub struct ThrottleResult {
  type Throttle (line 57) | struct Throttle {
    method new (line 85) | fn new() -> Self {
    method reset_if_needed (line 99) | fn reset_if_needed(&mut self) {
    method increment_counters (line 127) | fn increment_counters(&mut self) {
    method should_throttle (line 134) | fn should_throttle(&self, config: &ThrottleConfig) -> Option<ThrottleR...
  method default (line 69) | fn default() -> Self {
  type ThrottleManager (line 178) | pub struct ThrottleManager {
    method new (line 184) | pub fn new(config: ThrottleConfig) -> Self {
    method check_throttle (line 191) | pub async fn check_throttle(&self) -> Option<ThrottleResult> {
    method increment_counters (line 197) | pub async fn increment_counters(&self) {
  function test_throttle_limits (line 208) | async fn test_throttle_limits() {

FILE: backend/src/worker/consume.rs
  constant CHECK_EMAIL_QUEUE (line 30) | pub const CHECK_EMAIL_QUEUE: &str = "check_email";
  constant MAX_QUEUE_PRIORITY (line 31) | pub const MAX_QUEUE_PRIORITY: u8 = 5;
  function setup_rabbit_mq (line 39) | pub async fn setup_rabbit_mq(
  function run_worker (line 88) | pub async fn run_worker(config: Arc<BackendConfig>) -> Result<(), anyhow...
  function consume_check_email (line 93) | async fn consume_check_email(config: Arc<BackendConfig>) -> Result<(), a...

FILE: backend/src/worker/do_work.rs
  type CheckEmailTask (line 37) | pub struct CheckEmailTask {
  type CheckEmailJobId (line 45) | pub enum CheckEmailJobId {
  type TaskError (line 54) | pub enum TaskError {
    method status_code (line 71) | pub fn status_code(&self) -> StatusCode {
    method from (line 82) | fn from(err: lapin::Error) -> Self {
    method from (line 88) | fn from(err: reqwest::Error) -> Self {
  method serialize (line 94) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  type TaskWebhook (line 103) | pub struct TaskWebhook {
  type Webhook (line 108) | pub struct Webhook {
  type WebhookOutput (line 115) | struct WebhookOutput<'a> {
  function do_check_email_work (line 121) | pub(crate) async fn do_check_email_work(
  function check_email_and_send_result (line 179) | pub async fn check_email_and_send_result(

FILE: backend/src/worker/single_shot.rs
  type SingleShotReply (line 34) | pub enum SingleShotReply {
    type Error (line 44) | type Error = serde_json::Error;
    method try_from (line 46) | fn try_from(result: &Result<CheckEmailOutput, TaskError>) -> Result<Se...
  function send_single_shot_reply (line 67) | pub async fn send_single_shot_reply(

FILE: backend/tests/check_email.rs
  constant FOO_BAR_RESPONSE (line 26) | const FOO_BAR_RESPONSE: &str = r#"{"input":"foo@bar","is_reachable":"inv...
  constant FOO_BAR_BAZ_RESPONSE (line 27) | const FOO_BAR_BAZ_RESPONSE: &str = r#"{"input":"foo@bar.baz","is_reachab...
  function create_backend_config (line 29) | fn create_backend_config(header_secret: &str) -> Arc<BackendConfig> {
  function test_input_foo_bar (line 36) | async fn test_input_foo_bar() {
  function test_input_foo_bar_baz (line 50) | async fn test_input_foo_bar_baz() {
  function test_reacher_secret_missing_header (line 67) | async fn test_reacher_secret_missing_header() {
  function test_reacher_secret_wrong_secret (line 83) | async fn test_reacher_secret_wrong_secret() {
  function test_reacher_secret_correct_secret (line 100) | async fn test_reacher_secret_correct_secret() {
  function test_reacher_to_mail_empty (line 114) | async fn test_reacher_to_mail_empty() {

FILE: cli/src/main.rs
  type Cli (line 26) | pub struct Cli {
  function main (line 74) | async fn main() -> Result<(), anyhow::Error> {

FILE: core/src/haveibeenpwned.rs
  constant MAIN_API_URL (line 20) | const MAIN_API_URL: &str = "https://haveibeenpwned.com/api/v3/";
  function check_haveibeenpwned (line 26) | pub async fn check_haveibeenpwned(to_email: &str, api_key: Option<String...

FILE: core/src/lib.rs
  constant LOG_TARGET (line 103) | pub const LOG_TARGET: &str = "reacher";
  function initialize_crypto_provider (line 109) | pub fn initialize_crypto_provider() {
  function calculate_reachable (line 120) | fn calculate_reachable(misc: &MiscDetails, smtp: &Result<SmtpDetails, Sm...
  function check_email (line 146) | pub async fn check_email(input: &CheckEmailInput) -> CheckEmailOutput {

FILE: core/src/misc/gravatar.rs
  constant API_BASE_URL (line 20) | const API_BASE_URL: &str = "https://www.gravatar.com/avatar/";
  function check_gravatar (line 22) | pub async fn check_gravatar(to_email: &str) -> Option<String> {

FILE: core/src/misc/mod.rs
  constant ROLE_ACCOUNTS (line 26) | const ROLE_ACCOUNTS: &str = include_str!("./roles.txt");
  constant FREE_EMAIL_PROVIDERS (line 27) | const FREE_EMAIL_PROVIDERS: &str = include_str!("./b2c.txt");
  function load_str_as_hashset (line 35) | fn load_str_as_hashset(file_content: &str) -> HashSet<String> {
  type MiscDetails (line 44) | pub struct MiscDetails {
  type MiscError (line 63) | pub enum MiscError {}
  function check_misc (line 66) | pub async fn check_misc(
  function test_check_misc (line 108) | async fn test_check_misc() {

FILE: core/src/mx/mod.rs
  type MxDetails (line 29) | pub struct MxDetails {
    method from (line 43) | fn from(lookup: MxLookup) -> Self {
  method default (line 35) | fn default() -> Self {
  method serialize (line 49) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  type MxError (line 74) | pub enum MxError {
    method from (line 86) | fn from(e: io::Error) -> Self {
    method from (line 92) | fn from(e: ResolveError) -> Self {
  function check_mx (line 98) | pub async fn check_mx(syntax: &SyntaxDetails) -> Result<MxDetails, MxErr...
  function is_gmail (line 115) | pub fn is_gmail(mx_host: &str) -> bool {
  function is_hotmail (line 135) | pub fn is_hotmail(mx_host: &str) -> bool {
  function is_hotmail_b2b (line 140) | pub fn is_hotmail_b2b(mx_host: &str) -> bool {
  function is_hotmail_b2c (line 145) | pub fn is_hotmail_b2c(mx_host: &str) -> bool {
  function is_mimecast (line 150) | pub fn is_mimecast(mx_host: &str) -> bool {
  function is_proofpoint (line 155) | pub fn is_proofpoint(mx_host: &str) -> bool {
  function is_yahoo (line 163) | pub fn is_yahoo(mx_host: &str) -> bool {

FILE: core/src/rules.rs
  type Rule (line 28) | pub enum Rule {
  type RulesByDomain (line 36) | struct RulesByDomain {
  type AllRules (line 41) | struct AllRules {
  function does_domain_have_rule (line 54) | fn does_domain_have_rule(domain: &str, rule: &Rule) -> bool {
  function does_mx_have_rule (line 62) | fn does_mx_have_rule(host: &str, rule: &Rule) -> bool {
  function does_mx_suffix_have_rule (line 70) | fn does_mx_suffix_have_rule(host: &str, rule: &Rule) -> bool {
  function has_rule (line 81) | pub fn has_rule(domain: &str, host: &str, rule: &Rule) -> bool {
  function should_skip_catch_all (line 92) | fn should_skip_catch_all() {

FILE: core/src/smtp/connect.rs
  type AsyncReadWrite (line 37) | trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send {}
  function connect_to_smtp_host (line 61) | async fn connect_to_smtp_host(
  type Deliverability (line 139) | struct Deliverability {
  function check_email_deliverability (line 149) | async fn check_email_deliverability<S: AsyncBufRead + AsyncWrite + Unpin...
  function smtp_is_catch_all (line 226) | async fn smtp_is_catch_all<S: AsyncBufRead + AsyncWrite + Unpin + Send>(
  function create_smtp_future (line 256) | async fn create_smtp_future(
  function check_smtp_without_retry (line 312) | async fn check_smtp_without_retry(
  function check_smtp_with_retry (line 344) | pub async fn check_smtp_with_retry(

FILE: core/src/smtp/error.rs
  type SmtpError (line 31) | pub enum SmtpError {
    method from (line 68) | fn from(e: YahooError) -> Self {
    method from (line 74) | fn from(e: GmailError) -> Self {
    method from (line 80) | fn from(e: HeadlessError) -> Self {
    method from (line 86) | fn from(e: Microsoft365Error) -> Self {
    method from (line 92) | fn from(e: AsyncSmtpError) -> Self {
    method from (line 98) | fn from(e: std::io::Error) -> Self {
    method from (line 104) | fn from(e: fast_socks5::SocksError) -> Self {
    method from (line 110) | fn from(e: anyhow::Error) -> Self {
    method get_description (line 120) | pub fn get_description(&self) -> Option<SmtpErrorDesc> {
  type SmtpErrorDesc (line 139) | pub enum SmtpErrorDesc {

FILE: core/src/smtp/gmail.rs
  constant GLXU_PAGE (line 28) | const GLXU_PAGE: &str = "https://mail.google.com/mail/gxlu";
  type GmailError (line 32) | pub enum GmailError {
    method from (line 40) | fn from(error: ReqwestError) -> Self {
  function check_gmail_via_api (line 52) | pub async fn check_gmail_via_api(
  function should_return_is_deliverable_true (line 86) | async fn should_return_is_deliverable_true() {

FILE: core/src/smtp/headless.rs
  type HeadlessError (line 27) | pub enum HeadlessError {
  function create_headless_client (line 39) | pub async fn create_headless_client(

FILE: core/src/smtp/http_api.rs
  function create_client (line 21) | pub fn create_client(

FILE: core/src/smtp/mod.rs
  type SmtpDebugVerifMethodSmtp (line 42) | pub struct SmtpDebugVerifMethodSmtp {
  type SmtpDebugVerifMethod (line 51) | pub enum SmtpDebugVerifMethod {
  type SmtpDetails (line 65) | pub struct SmtpDetails {
  type SmtpDebug (line 80) | pub struct SmtpDebug {
  function check_smtp (line 87) | pub async fn check_smtp(
  function should_timeout (line 199) | fn should_timeout() {

FILE: core/src/smtp/outlook/headless.rs
  function check_password_recovery (line 34) | pub async fn check_password_recovery(
  function test_hotmail_address (line 140) | async fn test_hotmail_address() {
  function test_parallel (line 172) | async fn test_parallel() {

FILE: core/src/smtp/outlook/microsoft365.rs
  type Microsoft365Error (line 28) | pub enum Microsoft365Error {
    method from (line 35) | fn from(error: ReqwestError) -> Self {
  function get_onedrive_url (line 42) | fn get_onedrive_url(email_address: &str) -> String {
  function check_microsoft365_api (line 69) | pub async fn check_microsoft365_api(
  function test_onedrive_url (line 103) | fn test_onedrive_url() {

FILE: core/src/smtp/parser.rs
  function is_invalid (line 25) | pub fn is_invalid(e: &str, email: &EmailAddress) -> bool {
  function is_full_inbox (line 118) | pub fn is_full_inbox(e: &str) -> bool {
  function is_disabled_account (line 133) | pub fn is_disabled_account(e: &str) -> bool {
  function is_err_io_errors (line 143) | pub fn is_err_io_errors(e: &SmtpError) -> bool {
  function is_err_ip_blacklisted (line 151) | pub fn is_err_ip_blacklisted(e: &SmtpError) -> bool {
  function is_err_needs_rdns (line 221) | pub fn is_err_needs_rdns(e: &SmtpError) -> bool {
  function test_is_invalid (line 254) | fn test_is_invalid() {
  function test_is_err_ip_blacklisted (line 274) | fn test_is_err_ip_blacklisted() {

FILE: core/src/smtp/verif_method.rs
  type VerifMethodError (line 30) | pub enum VerifMethodError {
  type EmailProvider (line 38) | pub enum EmailProvider {
    method from_mx_host (line 50) | pub fn from_mx_host(host: &str) -> Self {
  type ProxyID (line 69) | type ProxyID = String;
  constant DEFAULT_PROXY_ID (line 73) | pub const DEFAULT_PROXY_ID: &str = "default";
  type VerifMethod (line 78) | pub struct VerifMethod {
    method new_with_same_config_for_all (line 102) | pub fn new_with_same_config_for_all(
    method validate_proxies (line 165) | pub fn validate_proxies(&self) -> Result<(), VerifMethodError> {
    method get_proxy (line 224) | pub fn get_proxy(&self, email_provider: EmailProvider) -> Option<&Chec...
  type GmailVerifMethod (line 276) | pub enum GmailVerifMethod {
  method default (line 281) | fn default() -> Self {
  type HotmailB2BVerifMethod (line 288) | pub enum HotmailB2BVerifMethod {
  method default (line 294) | fn default() -> Self {
  type HotmailB2CVerifMethod (line 301) | pub enum HotmailB2CVerifMethod {
  type MimecastVerifMethod (line 316) | pub enum MimecastVerifMethod {
  method default (line 322) | fn default() -> Self {
  type ProofpointVerifMethod (line 329) | pub enum ProofpointVerifMethod {
  method default (line 335) | fn default() -> Self {
  type YahooVerifMethod (line 342) | pub enum YahooVerifMethod {
  type EverythingElseVerifMethod (line 359) | pub enum EverythingElseVerifMethod {
  method default (line 365) | fn default() -> Self {
  type VerifMethodSmtpConfig (line 375) | pub struct VerifMethodSmtpConfig {
  method default (line 413) | fn default() -> Self {
  type VerifMethodSmtp (line 426) | pub struct VerifMethodSmtp {
    method new (line 432) | pub fn new(config: VerifMethodSmtpConfig, proxy: Option<CheckEmailInpu...
  function test_validate_proxies (line 442) | fn test_validate_proxies() {
  function test_get_proxy (line 485) | fn test_get_proxy() {

FILE: core/src/smtp/yahoo/api.rs
  constant SIGNUP_PAGE (line 26) | const SIGNUP_PAGE: &str = "https://login.yahoo.com/account/create?specId...
  constant SIGNUP_API (line 27) | const SIGNUP_API: &str = "https://login.yahoo.com/account/module/create?...
  constant USER_AGENT (line 28) | const USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6...
  type FormRequest (line 32) | struct FormRequest {
    method new (line 54) | fn new(acrumb: String, session_index: String, user_id: String) -> Self {
  method default (line 43) | fn default() -> Self {
  type FormResponseItem (line 66) | struct FormResponseItem {
  type FormResponse (line 73) | struct FormResponse {
  function check_api (line 79) | pub async fn check_api(to_email: &str, input: &CheckEmailInput) -> Resul...

FILE: core/src/smtp/yahoo/headless.rs
  function check_headless (line 34) | pub async fn check_headless(
  function check_headless_inner (line 66) | async fn check_headless_inner(
  function test_yahoo_address (line 151) | async fn test_yahoo_address() {

FILE: core/src/smtp/yahoo/mod.rs
  type YahooError (line 32) | pub enum YahooError {
    method from (line 53) | fn from(error: ReqwestError) -> Self {
    method from (line 59) | fn from(error: SerdeError) -> Self {

FILE: core/src/syntax/mod.rs
  type SyntaxDetails (line 27) | pub struct SyntaxDetails {
  method default (line 46) | fn default() -> Self {
  function check_syntax (line 60) | pub fn check_syntax(email_address: &str) -> SyntaxDetails {
  constant MAIL_PROVIDERS (line 110) | const MAIL_PROVIDERS: &[&str] = &[
  function get_similar_mail_provider (line 121) | pub fn get_similar_mail_provider(syntax: &mut SyntaxDetails) {
  function should_return_invalid_for_invalid_email (line 142) | fn should_return_invalid_for_invalid_email() {
  function should_return_invalid_for_invalid_email_with_at (line 157) | fn should_return_invalid_for_invalid_email_with_at() {
  function should_work_for_valid_email (line 172) | fn should_work_for_valid_email() {
  function should_suggest_a_correct_mail_if_similar (line 187) | fn should_suggest_a_correct_mail_if_similar() {

FILE: core/src/syntax/normalize.rs
  function normalize_email (line 1) | pub fn normalize_email(username: &str, domain: &str) -> String {
  function normalize_gmail (line 20) | fn normalize_gmail(username: &str) -> String {
  function test_gmail_removes_periods (line 40) | fn test_gmail_removes_periods() {
  function test_gmail_removes_subaddress (line 45) | fn test_gmail_removes_subaddress() {
  function test_gmail_uses_gmail_com (line 50) | fn test_gmail_uses_gmail_com() {
  function test_gmail (line 55) | fn test_gmail() {
  function test_gmail_idempotent (line 63) | fn test_gmail_idempotent() {

FILE: core/src/util/input_output.rs
  type EmailAddress (line 34) | pub struct EmailAddress(AsyncSmtpEmailAddress);
    method deserialize (line 46) | fn deserialize<D>(deserializer: D) -> Result<EmailAddress, D::Error>
    method new (line 72) | pub fn new(email: String) -> Result<Self, anyhow::Error> {
    method into_inner (line 76) | pub fn into_inner(self) -> AsyncSmtpEmailAddress {
    method as_ref (line 82) | fn as_ref(&self) -> &AsyncSmtpEmailAddress {
    method as_ref (line 88) | fn as_ref(&self) -> &str {
  method serialize (line 37) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  method fmt (line 58) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type Err (line 64) | type Err = anyhow::Error;
  method from_str (line 66) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  type CheckEmailInputProxy (line 96) | pub struct CheckEmailInputProxy {
  type CheckEmailInput (line 113) | pub struct CheckEmailInput {
  method default (line 149) | fn default() -> Self {
  type Reachable (line 167) | pub enum Reachable {
  type DebugDetails (line 187) | pub struct DebugDetails {
  method default (line 201) | fn default() -> Self {
  type CheckEmailOutput (line 214) | pub struct CheckEmailOutput {
  method default (line 231) | fn default() -> Self {
  method serialize (line 246) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  type WebdriverConfig (line 300) | pub struct WebdriverConfig {
  function should_serialize_correctly (line 310) | fn should_serialize_correctly() {

FILE: core/src/util/sentry.rs
  constant CARGO_PKG_VERSION (line 32) | const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
  function setup_sentry (line 35) | pub fn setup_sentry(sentry_dsn: &str) -> sentry::ClientInitGuard {
  type SentryError (line 47) | enum SentryError<'a> {
  function get_exception_type (line 58) | fn get_exception_type(&self) -> String {
  function error (line 69) | fn error(err: SentryError, result: &CheckEmailOutput, backend_name: &str) {
  function redact (line 95) | fn redact(input: &str, username: &str) -> String {
  function skip_smtp_transient_errors (line 100) | fn skip_smtp_transient_errors(message: &[String]) -> bool {
  function log_unknown_errors (line 111) | pub fn log_unknown_errors(result: &CheckEmailOutput, backend_name: &str) {
  function test_redact (line 154) | fn test_redact() {

FILE: core/src/util/ser_with_display.rs
  function ser_with_display (line 22) | pub fn ser_with_display<T, S>(value: &T, serializer: S) -> Result<S::Ok,...

FILE: sqs/src/main.rs
  constant CARGO_PKG_VERSION (line 31) | const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
  type SQSMessage (line 36) | struct SQSMessage {
  type SQSPayload (line 42) | struct SQSPayload {
  type CheckEmailPartialTask (line 51) | struct CheckEmailPartialTask {
    method into_check_email_task (line 58) | fn into_check_email_task(self, backend_config: Arc<BackendConfig>) -> ...
  function main (line 68) | async fn main() -> Result<(), Error> {
  function handler (line 91) | async fn handler(event: LambdaEvent<SQSPayload>) -> Result<CheckEmailOut...
  function run_and_wait_chromedriver (line 131) | async fn run_and_wait_chromedriver() -> Result<(), Error> {
Condensed preview — 169 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,173K chars).
[
  {
    "path": ".dockerignore",
    "chars": 56,
    "preview": ".git\n.github\n.vscode\nci\ntarget/\nDockerfile.*\ndocs/\n.env\n"
  },
  {
    "path": ".editorconfig",
    "chars": 269,
    "preview": "# top-most EditorConfig file\nroot = true\n\n[*]\nindent_style=tab\nindent_size=tab\ntab_width=4\nend_of_line=lf\ncharset=utf-8\n"
  },
  {
    "path": ".envrc",
    "chars": 49,
    "preview": "# Config for direnv\n# https://direnv.net/\ndotenv\n"
  },
  {
    "path": ".gitbook.yaml",
    "chars": 14,
    "preview": "root: ./docs/\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 27,
    "preview": "blank_issues_enabled: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue_form.yml",
    "chars": 1635,
    "preview": "name: Bug Report\nlabels: bug\ndescription: Create a bug report to help us improve.\nbody:\n  - type: markdown\n    attribute"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 174,
    "preview": "version: 2\nupdates:\n- package-ecosystem: cargo\n  directory: \"/\"\n  schedule:\n    interval: weekly\n    time: \"02:00\"\n    t"
  },
  {
    "path": ".github/workflows/deploy_backend.yml",
    "chars": 933,
    "preview": "name: deploy_backend\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\njobs:\n  get-tag:\n    runs-on: ubuntu-latest\n    outputs:\n "
  },
  {
    "path": ".github/workflows/deploy_cli.yml",
    "chars": 6575,
    "preview": "name: deploy_cli\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\nenv:\n  # TODO: Rename to your binary\n  BIN: check_if_email_exi"
  },
  {
    "path": ".github/workflows/pr.yml",
    "chars": 1836,
    "preview": "# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md\n\nname: pr\n\non:\n  pull_request:\n  push:\n "
  },
  {
    "path": ".github/workflows/pr_cli.yml",
    "chars": 5645,
    "preview": "# CI actions for the CLI only. We test that the CLI works on various platforms.\nname: cli\n\non:\n  push:\n    branches:\n   "
  },
  {
    "path": ".gitignore",
    "chars": 238,
    "preview": "target\n**/*.rs.bk\n.DS_Store\nprivate\n\n# docker-compose\nbackend/postgres_data\n\n# Output when debugging Hotmail password re"
  },
  {
    "path": ".sqlx/query-068521cf9e77f563b3791cce500d95660c56e852770a4eac47576089e704322a.json",
    "chars": 549,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\t\\t\\t\\tINSERT INTO v1_task_result (payload, job_id, extra, error)\\n\\t\\t\\t\\"
  },
  {
    "path": ".sqlx/query-0968b040845ecce236576f65df3d3648f7ce03bc5ae0ebe9f36f878c309061c8.json",
    "chars": 685,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tSELECT id, created_at, total_records FROM v1_bulk_job\\n\\t\\tWHERE id = $1\\"
  },
  {
    "path": ".sqlx/query-1039a6d3d732a86b9b3b2e19e6c8e3c857125d1c4cf916ac32789bfd0176b6b5.json",
    "chars": 502,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tSELECT result FROM email_results\\n\\t\\tWHERE job_id = $1\\n\\t\\tORDER BY id\\"
  },
  {
    "path": ".sqlx/query-13862fe23ea729215fb1cfee3aadc14dfa9373dc8137c4f1da199e3ae66efd50.json",
    "chars": 1509,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tSELECT\\n\\t\\t\\tCOUNT(*) as total_processed,\\n\\t\\t\\tCOUNT(CASE WHEN result "
  },
  {
    "path": ".sqlx/query-1a964da4784832e5f631f2c2e727382532c86c7e66e46254d72ef0af03021975.json",
    "chars": 346,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\t\\tINSERT INTO email_results (job_id, result)\\n\\t\\t\\tVALUES ($1, $2)\\n\\t\\t"
  },
  {
    "path": ".sqlx/query-1e5a713008953f95d626e578ed0f2bc2cfc2b1599d8c5d09d11287a661fccd0e.json",
    "chars": 421,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"SELECT total_records FROM v1_bulk_job WHERE id = $1;\",\n  \"describe\": {\n    \"col"
  },
  {
    "path": ".sqlx/query-323970c6cb4f5d4b7c0cb3293d1e6fd146c7d682e28e6cb72de6bbe14a3e04b7.json",
    "chars": 503,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tSELECT result FROM v1_task_result\\n\\t\\tWHERE job_id = $1\\n\\t\\tORDER BY id"
  },
  {
    "path": ".sqlx/query-47af0157fa867e147e49d80b121b1881df93a6619434a1fd1fc9a58315b4044b.json",
    "chars": 683,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tSELECT id, created_at, total_records FROM bulk_jobs\\n\\t\\tWHERE id = $1\\n\\"
  },
  {
    "path": ".sqlx/query-594dfc319a34dfdcf316758798a9ad099c298480b4f1dabc69b1dc6e2f4687a0.json",
    "chars": 414,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"SELECT COUNT(*) FROM v1_task_result WHERE job_id = $1;\",\n  \"describe\": {\n    \"c"
  },
  {
    "path": ".sqlx/query-96a32768eb750971bad4df10560b0331bf52b44dbd59020369adbbc6bc8dc830.json",
    "chars": 444,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tINSERT INTO v1_bulk_job (total_records)\\n\\t\\tVALUES ($1)\\n\\t\\tRETURNING i"
  },
  {
    "path": ".sqlx/query-981f650b6c663feeae8a93e7ecf86326e7a5e6d5c8fd03c03565d86982d0381a.json",
    "chars": 442,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tINSERT INTO bulk_jobs (total_records)\\n\\t\\tVALUES ($1)\\n\\t\\tRETURNING id\\"
  },
  {
    "path": ".sqlx/query-ac5e197ca20a1393e4ea45248d5e702c0edbbf57624f2bb416f0fd0401a44dcf.json",
    "chars": 419,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"SELECT total_records FROM bulk_jobs WHERE id = $1;\",\n  \"describe\": {\n    \"colum"
  },
  {
    "path": ".sqlx/query-d682d43a07f3969bcb3113ae3600766e9197d48bdc7ccb73a53c938cb45f910f.json",
    "chars": 1511,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\tSELECT\\n\\t\\t\\tCOUNT(*) as total_processed,\\n\\t\\t\\tCOUNT(CASE WHEN result "
  },
  {
    "path": ".sqlx/query-de8a3af8119e17c38b2f60cafac3b712359553bc295db5689f254f623d172326.json",
    "chars": 551,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"\\n\\t\\t\\t\\t\\tINSERT INTO v1_task_result (payload, job_id, extra, result)\\n\\t\\t\\t"
  },
  {
    "path": ".sqlx/query-f58d4d05a6ab4c1ffda39396df4c403f7588266ae8d954985fc1eda9751febcc.json",
    "chars": 413,
    "preview": "{\n  \"db_name\": \"PostgreSQL\",\n  \"query\": \"SELECT COUNT(*) FROM email_results WHERE job_id = $1;\",\n  \"describe\": {\n    \"co"
  },
  {
    "path": ".well-known/funding-manifest-urls",
    "chars": 35,
    "preview": "https://reacher.email/funding.json\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 53240,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file. The changes in this project follow [C"
  },
  {
    "path": "Cargo.toml",
    "chars": 56,
    "preview": "[workspace]\nmembers = [\"backend\", \"cli\", \"core\", \"sqs\"]\n"
  },
  {
    "path": "LICENSE.AGPL",
    "chars": 34523,
    "preview": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C)"
  },
  {
    "path": "LICENSE.md",
    "chars": 712,
    "preview": "`check-if-email-exists`'s source code is provided under a **dual license model**.\n\n### Commercial license\n\nIf you want t"
  },
  {
    "path": "Makefile",
    "chars": 2605,
    "preview": "###############################################################################\n# Run\n##################################"
  },
  {
    "path": "README.md",
    "chars": 10440,
    "preview": "[![Crate](https://img.shields.io/crates/v/check-if-email-exists.svg)](https://crates.io/crates/check-if-email-exists)\n[!"
  },
  {
    "path": "backend/Cargo.toml",
    "chars": 1226,
    "preview": "[package]\nname = \"reacher_backend\"\nversion = \"0.11.7\"\nedition = \"2018\"\nlicense = \"AGPL-3.0\"\npublish = false\n\n[dependenci"
  },
  {
    "path": "backend/Dockerfile",
    "chars": 1680,
    "preview": "# From https://shaneutt.com/blog/rust-fast-small-docker-image-builds/\n\n# -----------------------------------------------"
  },
  {
    "path": "backend/README.md",
    "chars": 2615,
    "preview": "[![Docker](https://img.shields.io/docker/v/reacherhq/backend?color=0db7ed&label=docker&sort=date)](https://hub.docker.co"
  },
  {
    "path": "backend/backend_config.toml",
    "chars": 8036,
    "preview": "# Backend configuration.\n\n# Name to identify the backend.\n#\n# Env variable: RCH__BACKEND_NAME\nbackend_name = \"backend-de"
  },
  {
    "path": "backend/docker.sh",
    "chars": 161,
    "preview": "#!/bin/ash\n\n# This is the Dockerfile's entrypoint script.\n# https://docs.docker.com/config/containers/multi-service_cont"
  },
  {
    "path": "backend/migrations/20210316025847_setup.down.sql",
    "chars": 317,
    "preview": "DROP FUNCTION mq_checkpoint;\nDROP FUNCTION mq_keep_alive;\nDROP FUNCTION mq_delete;\nDROP FUNCTION mq_commit;\nDROP FUNCTIO"
  },
  {
    "path": "backend/migrations/20210316025847_setup.up.sql",
    "chars": 8834,
    "preview": "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n\n-- The UDT for creating messages\nCREATE TYPE mq_new_t AS (\n    -- Unique me"
  },
  {
    "path": "backend/migrations/20210921115907_clear.down.sql",
    "chars": 52,
    "preview": "DROP FUNCTION mq_clear;\nDROP FUNCTION mq_clear_all;\n"
  },
  {
    "path": "backend/migrations/20210921115907_clear.up.sql",
    "chars": 586,
    "preview": "-- Deletes all messages from a list of channel names.\nCREATE FUNCTION mq_clear(channel_names TEXT[])\nRETURNS VOID AS $$\n"
  },
  {
    "path": "backend/migrations/20211013151757_fix_mq_latest_message.down.sql",
    "chars": 482,
    "preview": "CREATE OR REPLACE FUNCTION mq_latest_message(from_channel_name TEXT, from_channel_args TEXT)\nRETURNS UUID AS $$\n    SELE"
  },
  {
    "path": "backend/migrations/20211013151757_fix_mq_latest_message.up.sql",
    "chars": 625,
    "preview": "CREATE OR REPLACE FUNCTION mq_latest_message(from_channel_name TEXT, from_channel_args TEXT)\nRETURNS UUID AS $$\n    SELE"
  },
  {
    "path": "backend/migrations/20220117025847_email_data.down.sql",
    "chars": 94,
    "preview": "DROP INDEX job_emails;\nDROP TABLE email_results;\nDROP TABLE bulk_jobs;\nDROP TYPE valid_status;"
  },
  {
    "path": "backend/migrations/20220117025847_email_data.up.sql",
    "chars": 349,
    "preview": "CREATE TABLE bulk_jobs (\n    id SERIAL PRIMARY KEY,\n    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n    total_records"
  },
  {
    "path": "backend/migrations/20220208120856_fix_concurrent_poll.down.sql",
    "chars": 2025,
    "preview": "-- Main entry-point for job runner: pulls a batch of messages from the queue.\nCREATE OR REPLACE FUNCTION mq_poll(channel"
  },
  {
    "path": "backend/migrations/20220208120856_fix_concurrent_poll.up.sql",
    "chars": 2072,
    "preview": "\n-- Main entry-point for job runner: pulls a batch of messages from the queue.\nCREATE OR REPLACE FUNCTION mq_poll(channe"
  },
  {
    "path": "backend/migrations/20220713122907_fix-clear_all-keep-nil-message.down.sql",
    "chars": 34,
    "preview": "-- Add down migration script here\n"
  },
  {
    "path": "backend/migrations/20220713122907_fix-clear_all-keep-nil-message.up.sql",
    "chars": 825,
    "preview": "CREATE OR REPLACE FUNCTION mq_clear(channel_names TEXT[])\nRETURNS VOID AS $$\nBEGIN\n    WITH deleted_ids AS (\n        DEL"
  },
  {
    "path": "backend/migrations/20220810141100_result_created_at.down.sql",
    "chars": 50,
    "preview": "ALTER TABLE email_results\nDROP COLUMN created_at;\n"
  },
  {
    "path": "backend/migrations/20220810141100_result_created_at.up.sql",
    "chars": 76,
    "preview": "ALTER TABLE email_results\nADD created_at TIMESTAMPTZ NOT NULL DEFAULT NOW();"
  },
  {
    "path": "backend/migrations/20240929230957_v1_worker_results.down.sql",
    "chars": 152,
    "preview": "-- Add down migration script here\nDROP INDEX IF EXISTS v1_worker_results_job_id;\nDROP TABLE IF EXISTS v1_task_result;\nDR"
  },
  {
    "path": "backend/migrations/20240929230957_v1_worker_results.up.sql",
    "chars": 508,
    "preview": "CREATE TABLE v1_bulk_job (\n    id SERIAL PRIMARY KEY,\n    total_records INTEGER NOT NULL,\n    created_at TIMESTAMPTZ DEF"
  },
  {
    "path": "backend/migrations/README.md",
    "chars": 1252,
    "preview": "# Database and Migrations\n\n## Migrations\n\nAll migrations in this folder are embedded directly in the `reacher_backend` b"
  },
  {
    "path": "backend/openapi.json",
    "chars": 22850,
    "preview": "{\n\t\"openapi\": \"3.0.0\",\n\t\"info\": {\n\t\t\"title\": \"Reacher\",\n\t\t\"version\": \"0.11.0\",\n\t\t\"description\": \"### What is Reacher?\\n\\"
  },
  {
    "path": "backend/scripts/debian11.sh",
    "chars": 3072,
    "preview": "#!/usr/bin/env bash\n\n# Install script of Reacher Backend on an OVH debian 11 server.\n# As a postinstall, this script is "
  },
  {
    "path": "backend/src/bin/prune_db.rs",
    "chars": 2578,
    "preview": "use sqlx::PgPool;\nuse sqlx::Result;\nuse tracing::info;\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n\tdotenv::dotenv()"
  },
  {
    "path": "backend/src/config.rs",
    "chars": 15298,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/error.rs",
    "chars": 4309,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/mod.rs",
    "chars": 4100,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/db.rs",
    "chars": 1180,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/error.rs",
    "chars": 1295,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/get.rs",
    "chars": 4725,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/mod.rs",
    "chars": 2225,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/post.rs",
    "chars": 4094,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/results/csv_helper.rs",
    "chars": 6401,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/results/mod.rs",
    "chars": 6384,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/bulk/task.rs",
    "chars": 7113,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/check_email/backwardcompat.rs",
    "chars": 2243,
    "preview": "use std::time::Duration;\n\nuse check_if_email_exists::smtp::verif_method::{\n\tHotmailB2CVerifMethod, VerifMethodSmtpConfig"
  },
  {
    "path": "backend/src/http/v0/check_email/mod.rs",
    "chars": 767,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/check_email/post.rs",
    "chars": 5211,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v0/mod.rs",
    "chars": 35,
    "preview": "pub mod bulk;\npub mod check_email;\n"
  },
  {
    "path": "backend/src/http/v1/bulk/get_progress.rs",
    "chars": 4580,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/bulk/get_results/csv_helper.rs",
    "chars": 4907,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/bulk/get_results/mod.rs",
    "chars": 5108,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/bulk/mod.rs",
    "chars": 1808,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/bulk/post.rs",
    "chars": 4581,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/check_email/mod.rs",
    "chars": 747,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/check_email/post.rs",
    "chars": 7268,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/v1/mod.rs",
    "chars": 768,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/version/get.rs",
    "chars": 1710,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/http/version/mod.rs",
    "chars": 746,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/lib.rs",
    "chars": 874,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/main.rs",
    "chars": 2085,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/storage/commercial_license_trial.rs",
    "chars": 2091,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/storage/error.rs",
    "chars": 1043,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/storage/mod.rs",
    "chars": 1613,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/storage/postgres.rs",
    "chars": 2988,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/throttle.rs",
    "chars": 6490,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/worker/consume.rs",
    "chars": 6058,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/worker/do_work.rs",
    "chars": 6367,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/worker/mod.rs",
    "chars": 1078,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/src/worker/single_shot.rs",
    "chars": 3421,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "backend/tests/README.md",
    "chars": 152,
    "preview": "# Integration Tests\n\nFiles in this folder are Reacher backend's integration tests. They need the following to work:\n\n-  "
  },
  {
    "path": "backend/tests/check_email.rs",
    "chars": 5040,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "ci/build.bash",
    "chars": 572,
    "preview": "#!/usr/bin/env bash\n# Script for building your rust projects.\nset -e\n\nsource ci/common.bash\n\n# $1 {path} = Path to cross"
  },
  {
    "path": "ci/build.ps1",
    "chars": 342,
    "preview": "$cross = $args[0]\n$target_triple = $args[1]\n$release_build = $args[2]\n\nif ( $release_build -ne \"RELEASE\" ) {\n    Invoke-"
  },
  {
    "path": "ci/common.bash",
    "chars": 110,
    "preview": "required_arg() {\n    if [ -z \"$1\" ]; then\n        echo \"Required argument $2 missing\"\n        exit 1\n    fi\n}\n"
  },
  {
    "path": "ci/set_rust_version.bash",
    "chars": 66,
    "preview": "#!/usr/bin/env bash\nset -e\nrustup default $1\nrustup target add $2\n"
  },
  {
    "path": "ci/test.bash",
    "chars": 359,
    "preview": "#!/usr/bin/env bash\n# Script for building your rust projects.\nset -e\n\nsource ci/common.bash\n\n# $1 {path} = Path to cross"
  },
  {
    "path": "cli/Cargo.toml",
    "chars": 738,
    "preview": "[package]\nname = \"check-if-email-exists-cli\"\nversion = \"0.11.7\"\ndefault-run = \"check_if_email_exists\"\nedition = \"2018\"\nd"
  },
  {
    "path": "cli/README.md",
    "chars": 3925,
    "preview": "<br /><br /><br />\n\n<h1 align=\"center\">check-if-email-exists CLI</h1>\n<h4 align=\"center\">Email verification from your te"
  },
  {
    "path": "cli/src/main.rs",
    "chars": 3558,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/Cargo.toml",
    "chars": 1499,
    "preview": "[package]\nname = \"check-if-email-exists\"\nversion = \"0.11.7\"\nauthors = [\"Amaury <amaury@reacher.email>\"]\ncategories = [\"e"
  },
  {
    "path": "core/src/haveibeenpwned.rs",
    "chars": 2079,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/lib.rs",
    "chars": 8799,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/misc/b2c.txt",
    "chars": 1461235,
    "preview": "0-00.usa.cc\n0-180.com\n0-30-24.com\n0-420.com\n0-900.com\n0-aa.com\n0-mail.com\n0-z.xyz\n0.pl\n00.pe\n000000pay.com\n000476.com\n00"
  },
  {
    "path": "core/src/misc/gravatar.rs",
    "chars": 1644,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/misc/mod.rs",
    "chars": 4230,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/misc/roles.txt",
    "chars": 8310,
    "preview": "123\n2015\n2016\n2017\n2018\n2019\n2020\naaa\nabc\nabuse\nacademy\naccessibility\naccount\naccountant\naccounting\naccountmanager\naccou"
  },
  {
    "path": "core/src/mx/mod.rs",
    "chars": 5171,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/rules.json",
    "chars": 1287,
    "preview": "{\n\t\"by_domain\": {\n\t\t\"gmail.com\": { \"rules\": [\"SkipCatchAll\"] },\n\t\t\"hotmail.com\": { \"rules\": [\"SkipCatchAll\"] },\n\t\t\"hotma"
  },
  {
    "path": "core/src/rules.rs",
    "chars": 2875,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/connect.rs",
    "chars": 12208,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/error.rs",
    "chars": 4144,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/gmail.rs",
    "chars": 3015,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/headless.rs",
    "chars": 3166,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/http_api.rs",
    "chars": 1070,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/mod.rs",
    "chars": 6930,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/outlook/headless.rs",
    "chars": 5518,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/outlook/microsoft365.rs",
    "chars": 3235,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/outlook/mod.rs",
    "chars": 40,
    "preview": "pub mod headless;\npub mod microsoft365;\n"
  },
  {
    "path": "core/src/smtp/parser.rs",
    "chars": 12980,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "core/src/smtp/verif_method.rs",
    "chars": 15621,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/yahoo/api.rs",
    "chars": 4744,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/yahoo/headless.rs",
    "chars": 5443,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/smtp/yahoo/mod.rs",
    "chars": 2141,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/syntax/mod.rs",
    "chars": 5104,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/syntax/normalize.rs",
    "chars": 1829,
    "preview": "pub fn normalize_email(username: &str, domain: &str) -> String {\n\tmatch domain {\n\t\t\"gmail.com\" | \"googlemail.com\" => nor"
  },
  {
    "path": "core/src/util/input_output.rs",
    "chars": 10856,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/util/mod.rs",
    "chars": 817,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "core/src/util/sentry.rs",
    "chars": 5576,
    "preview": "// Reacher - Email Verification\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribu"
  },
  {
    "path": "core/src/util/ser_with_display.rs",
    "chars": 1075,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  },
  {
    "path": "docs/README.md",
    "chars": 1514,
    "preview": "---\ncover: .gitbook/assets/docs_cover.jpg\ncoverY: 0\nlayout:\n  cover:\n    visible: true\n    size: full\n  title:\n    visib"
  },
  {
    "path": "docs/SUMMARY.md",
    "chars": 1656,
    "preview": "# Table of contents\n\n* [Welcome to Reacher](README.md)\n\n## Getting Started\n\n* [Verify your 1st email](getting-started/qu"
  },
  {
    "path": "docs/advanced/migrations/README.md",
    "chars": 99,
    "preview": "# Migrations\n\n* [migrating-from-0.7-to-0.10-beta.md](migrating-from-0.7-to-0.10-beta.md \"mention\")\n"
  },
  {
    "path": "docs/advanced/migrations/bulk.md",
    "chars": 5330,
    "preview": "# Bulk Verification (v0.7)\n\nThe default Reacher API only exposes one endpoint, `/v0/check_email`, which allows verifying"
  },
  {
    "path": "docs/advanced/migrations/docker-environment-variables.md",
    "chars": 3546,
    "preview": "# Docker Environment Variables (v0.7)\n\n{% hint style=\"info\" %}\nThis page only applies to Reacher version 0.7. For the `b"
  },
  {
    "path": "docs/advanced/migrations/migrating-from-0.7-to-0.10-beta.md",
    "chars": 3745,
    "preview": "# Migrating from 0.7 to 0.10\n\nReacher v0.10 introduces the `/v1/*` endpoints, namely:\n\n* `/v1/check_email`: Performs a s"
  },
  {
    "path": "docs/advanced/migrations/reacher-configuration-v0.10.md",
    "chars": 6571,
    "preview": "# Reacher Configuration (v0.10)\n\nYou can find below the exhaustive list of configurable parameters to optimize Reacher.\n"
  },
  {
    "path": "docs/advanced/openapi/README.md",
    "chars": 153,
    "preview": "# OpenAPI\n\n* [v0-check\\_email.md](v0-check_email.md \"mention\")\n* [v1-check\\_email.md](v1-check_email.md \"mention\")\n* [v1"
  },
  {
    "path": "docs/advanced/openapi/v0-check_email.md",
    "chars": 413,
    "preview": "# /v0/check\\_email\n\n{% swagger src=\"https://raw.githubusercontent.com/reacherhq/check-if-email-exists/refs/heads/master/"
  },
  {
    "path": "docs/advanced/openapi/v1-bulk.md",
    "chars": 1194,
    "preview": "# /v1/bulk\n\n{% swagger src=\"https://raw.githubusercontent.com/reacherhq/check-if-email-exists/refs/heads/master/backend/"
  },
  {
    "path": "docs/advanced/openapi/v1-check_email.md",
    "chars": 413,
    "preview": "# /v1/check\\_email\n\n{% swagger src=\"https://raw.githubusercontent.com/reacherhq/check-if-email-exists/refs/heads/master/"
  },
  {
    "path": "docs/advanced/run-your-own-proxy.md",
    "chars": 345,
    "preview": "# Run your own Proxy\n\nReacher integrates seamlessy with [proxies](../self-hosting/proxies/ \"mention\"), and we propose so"
  },
  {
    "path": "docs/getting-started/is-reachable.md",
    "chars": 4391,
    "preview": "# Understanding \"is\\_reachable\"\n\n## `is_reachable`?\n\nReacher provides a confidence score for how likely an email is to b"
  },
  {
    "path": "docs/getting-started/quickstart.md",
    "chars": 1797,
    "preview": "# Verify your 1st email\n\nThere are two ways to verify an email with Reacher:\n\n1. Using the Reacher Dashboard (quick & ea"
  },
  {
    "path": "docs/self-hosting/debugging-reacher.md",
    "chars": 6194,
    "preview": "# Debugging Reacher\n\n## How to debug Reacher?&#x20;\n\nThe reflex to have when debugging Reacher is to set the `-e RUST_LO"
  },
  {
    "path": "docs/self-hosting/install.md",
    "chars": 5331,
    "preview": "# Install Reacher in 20min\n\nReacher is designed for seamless self-hosting, giving you full control over its operation on"
  },
  {
    "path": "docs/self-hosting/licensing/README.md",
    "chars": 1870,
    "preview": "---\ndescription: >-\n  Is Reacher open-source? Does it mean it's free? When should you pay for a\n  Commercial License?\n--"
  },
  {
    "path": "docs/self-hosting/licensing/commercial-license-trial.md",
    "chars": 2261,
    "preview": "# Commercial License Trial\n\nThe Commercial License Trial allows you to test the self-hosted software for a limited perio"
  },
  {
    "path": "docs/self-hosting/proxies/README.md",
    "chars": 2611,
    "preview": "# Proxies\n\nMaintaining a good IP reputation is hard. Reacher integrates seamlessly with SOCKS5 proxies.\n\n## What is a SO"
  },
  {
    "path": "docs/self-hosting/proxies/multiple-proxies.md",
    "chars": 3941,
    "preview": "# Multiple Proxies\n\n{% hint style=\"info\" %}\nThis feature is only available starting from Reacher v0.11.0.\n{% endhint %}\n"
  },
  {
    "path": "docs/self-hosting/reacher-configuration-v0.10.md",
    "chars": 9559,
    "preview": "# Reacher Configuration\n\n{% hint style=\"info\" %}\nThis configuration is for the current 0.11 version. For the older versi"
  },
  {
    "path": "docs/self-hosting/saas-vs-self-host.md",
    "chars": 2389,
    "preview": "# SaaS vs Self-Host\n\nWhen using Reacher, you can choose between two options: the SaaS version hosted by Reacher or self-"
  },
  {
    "path": "docs/self-hosting/scaling-for-production/README.md",
    "chars": 1846,
    "preview": "# Scaling for Production\n\nReacher's stateless architecture enables efficient horizontal scaling, allowing companies to t"
  },
  {
    "path": "docs/self-hosting/scaling-for-production/option-1-manage-scaling-yourself.md",
    "chars": 2807,
    "preview": "# Manage scaling yourself\n\nReacher is stateless by design. This means that you can spawn instances of Reacher concurrent"
  },
  {
    "path": "docs/self-hosting/scaling-for-production/option-2-rabbitmq-based-queue-architecture.md",
    "chars": 3801,
    "preview": "# Option 1: RabbitMQ-based Queue Architecture\n\nReacher includes an optional, opinionated queue-based architecture design"
  },
  {
    "path": "rabbitmq/docker-compose.yaml",
    "chars": 1851,
    "preview": "version: \"3.8\"\n\nservices:\n    rabbitmq:\n        image: rabbitmq:4.0-management\n        container_name: rabbitmq\n        "
  },
  {
    "path": "rustfmt.toml",
    "chars": 17,
    "preview": "hard_tabs = true\n"
  },
  {
    "path": "sqs/.gitignore",
    "chars": 22,
    "preview": "terraform*\n.terraform*"
  },
  {
    "path": "sqs/Cargo.toml",
    "chars": 554,
    "preview": "[package]\nname = \"reacher_sqs\"\nversion = \"0.11.6\"\nedition = \"2018\"\nlicense = \"AGPL-3.0\"\npublish = false\n\n[dependencies]\n"
  },
  {
    "path": "sqs/Dockerfile",
    "chars": 1545,
    "preview": "FROM messense/rust-musl-cross:x86_64-musl AS builder\n\n# Set the working directory\nWORKDIR /usr/src/app\n\n# Copy the Rust "
  },
  {
    "path": "sqs/Makefile",
    "chars": 191,
    "preview": "TAG = beta\n\n.PHONY: docker_deploy\ndocker_deploy:\n\tcd .. && docker buildx build --platform linux/amd64 -t 430118836964.dk"
  },
  {
    "path": "sqs/README.md",
    "chars": 55,
    "preview": "# SQS\n\n## Build Docker\n\n```bash\nmake docker_deploy\n```\n"
  },
  {
    "path": "sqs/main.tf",
    "chars": 4187,
    "preview": "provider \"aws\" {\n  region = var.aws_region\n}\n\n# Define variables for reuse\nvariable \"aws_region\" {\n  default = \"eu-west-"
  },
  {
    "path": "sqs/src/main.rs",
    "chars": 5144,
    "preview": "// check-if-email-exists\n// Copyright (C) 2018-2023 Reacher\n\n// This program is free software: you can redistribute it a"
  }
]

About this extraction

This page contains the full source code of the reacherhq/check-if-email-exists GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 169 files (1.9 MB), approximately 733.5k tokens, and a symbol index with 395 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!