├── .github ├── renovate.json └── workflows │ ├── go.yml │ ├── release-please.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── bench.sh ├── bench_check.sh ├── docker-compose.yml ├── go.mod ├── go.sum └── main.go /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | 4 | "extends": [ 5 | "github>nabeken/renovate-config-oss", 6 | "github>nabeken/renovate-config-oss:recommended", 7 | "github>nabeken/renovate-config-oss:githubLocalActionsDefaultVersions", 8 | "github>nabeken/renovate-config-oss:semanticCommitsFixDeps", 9 | "github>nabeken/renovate-config-oss:automergeGoPatch", 10 | "github>nabeken/renovate-config-oss:groupGithubActions" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | # renovate: datasource=golang-version depName=golang 11 | GO_VERSION: '1.24.4' 12 | 13 | jobs: 14 | 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 22 | with: 23 | go-version: '${{ env.GO_VERSION }}' 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 28 | 29 | - name: Build 30 | run: go build -v ./... 31 | 32 | - name: Test 33 | run: go test -v -cover ./... 34 | 35 | bench: 36 | name: Bench 37 | runs-on: ubuntu-latest 38 | steps: 39 | 40 | - name: Check out code into the Go module directory 41 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 42 | 43 | - name: Run bench 44 | run: | 45 | docker compose build 46 | docker compose run --rm bench /root/bench_check.sh 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Get GITHUB_TOKEN for release 17 | uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2 18 | id: app-token 19 | with: 20 | app-id: ${{ secrets.release_gh_app_id }} 21 | private-key: ${{ secrets.release_gh_app_priv_key }} 22 | 23 | - uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445 # v4 24 | with: 25 | release-type: go 26 | token: ${{ steps.app-token.outputs.token }} 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v0.*' 7 | 8 | env: 9 | # renovate: datasource=golang-version depName=golang 10 | GO_VERSION: '1.24.4' 11 | 12 | jobs: 13 | build: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Set up Go 19 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 20 | with: 21 | go-version: '${{ env.GO_VERSION }}' 22 | id: go 23 | 24 | - name: Check out code into the Go module directory 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 26 | 27 | - name: Run GoReleaser 28 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6 29 | with: 30 | version: latest 31 | args: release --clean 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | go-smtp-source-linux-amd64 2 | go-smtp-source 3 | vendor 4 | snapshot 5 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | version: 2 4 | before: 5 | hooks: 6 | # You may remove this if you don't use go modules. 7 | - go mod download 8 | # you may remove this if you don't need go generate 9 | - go generate ./... 10 | builds: 11 | - env: 12 | - CGO_ENABLED=0 13 | goos: 14 | - linux 15 | - windows 16 | - darwin 17 | goarch: 18 | - amd64 19 | - arm64 20 | checksum: 21 | name_template: 'checksums.txt' 22 | snapshot: 23 | name_template: "{{ .Tag }}-next" 24 | changelog: 25 | sort: asc 26 | filters: 27 | exclude: 28 | - '^docs:' 29 | - '^test:' 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.7.5](https://github.com/nabeken/go-smtp-source/compare/v0.7.4...v0.7.5) (2025-06-17) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **deps:** update actions/create-github-app-token action to v2 ([9035c28](https://github.com/nabeken/go-smtp-source/commit/9035c285fc08267df728c74cf0175378b1f71688)) 9 | * **deps:** update actions/create-github-app-token action to v2 ([80c18fd](https://github.com/nabeken/go-smtp-source/commit/80c18fd93d65b98eb8cdf034128b5ef93f7492cb)) 10 | * **deps:** update actions/create-github-app-token digest to 0d56448 ([#105](https://github.com/nabeken/go-smtp-source/issues/105)) ([8342085](https://github.com/nabeken/go-smtp-source/commit/834208585fb22682d3ae92d0817fe3ab67a667a7)) 11 | * **deps:** update actions/create-github-app-token digest to 21cfef2 ([#106](https://github.com/nabeken/go-smtp-source/issues/106)) ([3023a0a](https://github.com/nabeken/go-smtp-source/commit/3023a0ac254353f6bc2dcec0950497ec1edc885c)) 12 | * **deps:** update actions/create-github-app-token digest to 30bf625 ([#117](https://github.com/nabeken/go-smtp-source/issues/117)) ([756c7fe](https://github.com/nabeken/go-smtp-source/commit/756c7fe47acef610973866095702bc4fc5ae2211)) 13 | * **deps:** update actions/create-github-app-token digest to af35eda ([#111](https://github.com/nabeken/go-smtp-source/issues/111)) ([6a332c9](https://github.com/nabeken/go-smtp-source/commit/6a332c9c32f68087f8af3ac4522f5c32d74640bd)) 14 | * **deps:** update actions/create-github-app-token digest to d72941d ([#112](https://github.com/nabeken/go-smtp-source/issues/112)) ([c33208c](https://github.com/nabeken/go-smtp-source/commit/c33208ca36b65cb92a45d62def826424dd809946)) 15 | * **deps:** update actions/create-github-app-token digest to db3cdf4 ([#118](https://github.com/nabeken/go-smtp-source/issues/118)) ([5873eb4](https://github.com/nabeken/go-smtp-source/commit/5873eb45cb95efbc7be199c8c391e4601b474b4d)) 16 | * **deps:** update actions/create-github-app-token digest to df432ce ([#119](https://github.com/nabeken/go-smtp-source/issues/119)) ([5cb5be7](https://github.com/nabeken/go-smtp-source/commit/5cb5be78ac2bd8a8653ac830fb91819edfec1231)) 17 | * **deps:** update actions/setup-go digest to 0aaccfd ([#110](https://github.com/nabeken/go-smtp-source/issues/110)) ([559af6b](https://github.com/nabeken/go-smtp-source/commit/559af6b641216709e13ed0e5a1e6d30d69454d6c)) 18 | * **deps:** update actions/setup-go digest to d35c59a ([#122](https://github.com/nabeken/go-smtp-source/issues/122)) ([dfdde4a](https://github.com/nabeken/go-smtp-source/commit/dfdde4afecfe8493d0a22cde56779b696b5fa079)) 19 | * **deps:** update dependency go to v1.24.2 ([#114](https://github.com/nabeken/go-smtp-source/issues/114)) ([22c5560](https://github.com/nabeken/go-smtp-source/commit/22c5560bccaa03d2abf67f1f3b3b9cf5749503bb)) 20 | * **deps:** update dependency go to v1.24.3 ([#120](https://github.com/nabeken/go-smtp-source/issues/120)) ([b320a8c](https://github.com/nabeken/go-smtp-source/commit/b320a8c7200c03cea2e06dddc3644f719a4be730)) 21 | * **deps:** update dependency go to v1.24.4 ([#124](https://github.com/nabeken/go-smtp-source/issues/124)) ([ddcb7f9](https://github.com/nabeken/go-smtp-source/commit/ddcb7f914df8c96c00f58a25bb48ebaa2beee887)) 22 | * **deps:** update dependency golang ([d0479ce](https://github.com/nabeken/go-smtp-source/commit/d0479ce6ab0e1f305e26c278f6b755769733ea5f)) 23 | * **deps:** update dependency golang ([199c3c3](https://github.com/nabeken/go-smtp-source/commit/199c3c32b4207e923e8c2a3e3ba7a2c415483bb5)) 24 | * **deps:** update dependency golang to v1.24.2 ([#115](https://github.com/nabeken/go-smtp-source/issues/115)) ([cc7046b](https://github.com/nabeken/go-smtp-source/commit/cc7046b9cad5d44fb44552df8eccaafd9009be82)) 25 | * **deps:** update dependency golang to v1.24.3 ([#121](https://github.com/nabeken/go-smtp-source/issues/121)) ([a0f025e](https://github.com/nabeken/go-smtp-source/commit/a0f025eeb83c95b892be5346b5860dc9a281af60)) 26 | * **deps:** update dependency golang to v1.24.4 ([#125](https://github.com/nabeken/go-smtp-source/issues/125)) ([2ce4580](https://github.com/nabeken/go-smtp-source/commit/2ce45803dc17c384648a81ebd1f77da346744869)) 27 | * **deps:** update googleapis/release-please-action digest to 26ac09b ([#108](https://github.com/nabeken/go-smtp-source/issues/108)) ([029f63c](https://github.com/nabeken/go-smtp-source/commit/029f63cbdeb47d9ff737a2fcaf20d11cfafd2bd6)) 28 | * **deps:** update googleapis/release-please-action digest to a02a34c ([#109](https://github.com/nabeken/go-smtp-source/issues/109)) ([88d717b](https://github.com/nabeken/go-smtp-source/commit/88d717b233bc312b6e1ecae21ebdcf98647b4383)) 29 | * **deps:** update goreleaser/goreleaser-action digest to 90a3faa ([c3b4b20](https://github.com/nabeken/go-smtp-source/commit/c3b4b209c083dc5cb5a2efe20ebc711737d5e779)) 30 | * **deps:** update goreleaser/goreleaser-action digest to 90a3faa ([ac61e5d](https://github.com/nabeken/go-smtp-source/commit/ac61e5da50a8d09a6a6e877f9791b7a87ade8946)) 31 | * **deps:** update goreleaser/goreleaser-action digest to 9c156ee ([b4d1032](https://github.com/nabeken/go-smtp-source/commit/b4d10321c5cb0c26445863dd38b50a5cb56751cf)) 32 | * **deps:** update goreleaser/goreleaser-action digest to 9c156ee ([698a51f](https://github.com/nabeken/go-smtp-source/commit/698a51fdddc0957f392b4c9a8917028c5e4053dd)) 33 | * **deps:** update module golang.org/x/time to v0.11.0 ([b1ebd93](https://github.com/nabeken/go-smtp-source/commit/b1ebd9323c31841566e37c0922b01b58f7b64f13)) 34 | * **deps:** update module golang.org/x/time to v0.11.0 ([4d88b21](https://github.com/nabeken/go-smtp-source/commit/4d88b2137c2a71403ec542da28da1e7b174c27c4)) 35 | * **deps:** update module golang.org/x/time to v0.12.0 ([01a829d](https://github.com/nabeken/go-smtp-source/commit/01a829d66010fd8c60f163aa55cec4aa09d8facd)) 36 | * **deps:** update module golang.org/x/time to v0.12.0 ([f9ffbc4](https://github.com/nabeken/go-smtp-source/commit/f9ffbc4aca21f7fff5d2f950e62431af48744b67)) 37 | 38 | ## [0.7.4](https://github.com/nabeken/go-smtp-source/compare/v0.7.3...v0.7.4) (2025-02-10) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * **deps:** update actions/create-github-app-token digest to 136412a ([#95](https://github.com/nabeken/go-smtp-source/issues/95)) ([fdcd0ed](https://github.com/nabeken/go-smtp-source/commit/fdcd0ed1410c2780b17f775f66a4137d3981b11f)) 44 | * **deps:** update actions/create-github-app-token digest to 67e27a7 ([#96](https://github.com/nabeken/go-smtp-source/issues/96)) ([0a63a10](https://github.com/nabeken/go-smtp-source/commit/0a63a1002bcb108f422956e95cb482742475fd3b)) 45 | * **deps:** update actions/setup-go digest to f111f33 ([#94](https://github.com/nabeken/go-smtp-source/issues/94)) ([cbde419](https://github.com/nabeken/go-smtp-source/commit/cbde41917ba395b0e3e377cca0fdc7a52ee0e26e)) 46 | * **deps:** update dependency golang to v1.23.5 ([#92](https://github.com/nabeken/go-smtp-source/issues/92)) ([b789ca8](https://github.com/nabeken/go-smtp-source/commit/b789ca824e8657ee779e151bdc72ef91a9bb0c99)) 47 | * **deps:** update dependency golang to v1.23.6 ([#100](https://github.com/nabeken/go-smtp-source/issues/100)) ([08fa14e](https://github.com/nabeken/go-smtp-source/commit/08fa14e8fbd6f950aa7b3f21535229a3896e4168)) 48 | * **deps:** update module golang.org/x/time to v0.10.0 ([e47396f](https://github.com/nabeken/go-smtp-source/commit/e47396f3981950850f8bc1c68bfc5f047d8fdd59)) 49 | 50 | ## [0.7.3](https://github.com/nabeken/go-smtp-source/compare/v0.7.2...v0.7.3) (2025-01-04) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * **deps:** pin actions/create-github-app-token action to 31c86eb ([339494d](https://github.com/nabeken/go-smtp-source/commit/339494d6820f883a9a187526c4c70b1b85c8ad79)) 56 | * **deps:** update actions/checkout digest to 11bd719 ([4abe755](https://github.com/nabeken/go-smtp-source/commit/4abe755d817241a8a0914c209ec679ec56f6153f)) 57 | * **deps:** update actions/checkout digest to eef6144 ([eeea04e](https://github.com/nabeken/go-smtp-source/commit/eeea04e17110796e532cd7c59c2b088125a3a4e8)) 58 | * **deps:** update actions/create-github-app-token digest to 3378cda ([183458e](https://github.com/nabeken/go-smtp-source/commit/183458e128fd61eb8324e8498a9fd300ce852bbb)) 59 | * **deps:** update actions/create-github-app-token digest to 5d869da ([e536a37](https://github.com/nabeken/go-smtp-source/commit/e536a37e9fcbbd045623be5f1373c8b739365aa6)) 60 | * **deps:** update actions/setup-go digest to 41dfa10 ([6586004](https://github.com/nabeken/go-smtp-source/commit/6586004668e8de682c510a77442e0215ba38962b)) 61 | * **deps:** update dependency golang to v1.23.1 ([1a1ee7d](https://github.com/nabeken/go-smtp-source/commit/1a1ee7d8e4ecfd4f2c8d148b723fb80b7cdf0401)) 62 | * **deps:** update dependency golang to v1.23.2 ([c13bef9](https://github.com/nabeken/go-smtp-source/commit/c13bef9cfcec9e4bd956c6103ff55535c9fd3b69)) 63 | * **deps:** update dependency golang to v1.23.3 ([d79b509](https://github.com/nabeken/go-smtp-source/commit/d79b5094d9c315a785aab5c3e14f01b2c5d5dcae)) 64 | * **deps:** update dependency golang to v1.23.4 ([#89](https://github.com/nabeken/go-smtp-source/issues/89)) ([84003d8](https://github.com/nabeken/go-smtp-source/commit/84003d8787cf9547c2ebc26a4496b36b3de39770)) 65 | * **deps:** update github-actions ([64206b6](https://github.com/nabeken/go-smtp-source/commit/64206b6a442d236831749a17969482f779e527fe)) 66 | * **deps:** update go ([699c897](https://github.com/nabeken/go-smtp-source/commit/699c897907527c3f8c1835113115e91abdc38bc5)) 67 | * **deps:** update go ([f562f70](https://github.com/nabeken/go-smtp-source/commit/f562f7003d5f3b973a21944ad11a85c21a71ea63)) 68 | * **deps:** update goreleaser/goreleaser-action digest to 9ed2f89 ([997f0d6](https://github.com/nabeken/go-smtp-source/commit/997f0d6fadf7005ecee3e52772e5c7a2d9059c92)) 69 | * **deps:** update module golang.org/x/time to v0.7.0 ([b71eef1](https://github.com/nabeken/go-smtp-source/commit/b71eef1a020f84dc7abb0f2b6b179c8096764675)) 70 | * **deps:** update module golang.org/x/time to v0.8.0 ([93c2475](https://github.com/nabeken/go-smtp-source/commit/93c2475531d7baecbfaffcb0db19812e60e55f24)) 71 | * **deps:** update module golang.org/x/time to v0.9.0 ([3f3f91c](https://github.com/nabeken/go-smtp-source/commit/3f3f91c2f1bd62d103aab18ea73e8ff801794923)) 72 | * **deps:** update nabeken/go-github-apps digest to afd4a1d ([ef33d12](https://github.com/nabeken/go-smtp-source/commit/ef33d12c8d87b18d7e56340be0997933f8f9f0fb)) 73 | 74 | ## [0.7.2](https://github.com/nabeken/go-smtp-source/compare/v0.7.1...v0.7.2) (2024-07-16) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * **deps:** pin googleapis/release-please-action action to f3969c0 ([27368af](https://github.com/nabeken/go-smtp-source/commit/27368af6c258ec9ca1d96ce719c6ef7da4ba659c)) 80 | * **deps:** update actions/checkout digest to 692973e ([21a80f8](https://github.com/nabeken/go-smtp-source/commit/21a80f82e7e68fae846193e1f2d94a1228a78320)) 81 | * **deps:** update actions/checkout digest to a5ac7e5 ([258c181](https://github.com/nabeken/go-smtp-source/commit/258c181fdc3c3a58d187ed6db040948cc4389ff8)) 82 | * **deps:** update actions/setup-go digest to 0a12ed9 ([e7ce898](https://github.com/nabeken/go-smtp-source/commit/e7ce8986ea6b2bd53ce0e45050a5e5d67d4763f8)) 83 | * **deps:** update actions/setup-go digest to cdcb360 ([b5b5dfc](https://github.com/nabeken/go-smtp-source/commit/b5b5dfc02bf3b986f807e940198de3e16f0e0fa2)) 84 | * **deps:** update dependency golang to v1.22.3 ([590978a](https://github.com/nabeken/go-smtp-source/commit/590978a36cadfe483b4cf56e07bbd69a1ab3b700)) 85 | * **deps:** update dependency golang to v1.22.4 ([82a710e](https://github.com/nabeken/go-smtp-source/commit/82a710eaa341101f366744838ff15fe8ef32503e)) 86 | * **deps:** update dependency golang to v1.22.5 ([c62439e](https://github.com/nabeken/go-smtp-source/commit/c62439e5fbbea35e8b6d973c52309237ed6b3f20)) 87 | * **deps:** update google-github-actions/release-please-action digest to e4dc86b ([27d5357](https://github.com/nabeken/go-smtp-source/commit/27d53573a37e1003531abf5e524e5188ca2b5eb6)) 88 | * **deps:** update googleapis/release-please-action digest to 7987652 ([bc5e5c4](https://github.com/nabeken/go-smtp-source/commit/bc5e5c4030f4b907429a68eb9d82fca077b9f8cc)) 89 | * **deps:** update goreleaser/goreleaser-action action to v6 ([bfcfd94](https://github.com/nabeken/go-smtp-source/commit/bfcfd9421d855828bfa894bed6dc3906973b5c33)) 90 | * **deps:** update goreleaser/goreleaser-action digest to 5742e2a ([4452615](https://github.com/nabeken/go-smtp-source/commit/4452615400b384f9482f708bb7581fc364bc8e34)) 91 | * **deps:** update nabeken/go-github-apps digest to ad9874f ([f8f1c79](https://github.com/nabeken/go-smtp-source/commit/f8f1c79efd01ffaac55e800034162ee07c91d202)) 92 | 93 | ## [0.7.1](https://github.com/nabeken/go-smtp-source/compare/v0.7.0...v0.7.1) (2024-04-29) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * **deps:** update actions/checkout digest to 0ad4b8f ([a885647](https://github.com/nabeken/go-smtp-source/commit/a885647ef836f241aa2c80c0ae27858595a7907d)) 99 | * **deps:** update actions/checkout digest to 1d96c77 ([101d360](https://github.com/nabeken/go-smtp-source/commit/101d360e669f151858076414763685135229c3f2)) 100 | * **deps:** update actions/checkout digest to 8ade135 ([fdf6df9](https://github.com/nabeken/go-smtp-source/commit/fdf6df9e02249195323b7eed5fc18d7bac3e926b)) 101 | * **deps:** update actions/checkout digest to b4ffde6 ([fdb54ef](https://github.com/nabeken/go-smtp-source/commit/fdb54efa86e6611fb0b02928d702d366b15016c9)) 102 | * **deps:** update actions/setup-go action to v5 ([c5cc34d](https://github.com/nabeken/go-smtp-source/commit/c5cc34d278efdc2ed9ac1361b04c3ff9a5f3dd76)) 103 | * **deps:** update dependency golang to v1.21.2 ([7f9f784](https://github.com/nabeken/go-smtp-source/commit/7f9f784a75fc1197e988eed0ca12a1989d383850)) 104 | * **deps:** update dependency golang to v1.21.3 ([69806fe](https://github.com/nabeken/go-smtp-source/commit/69806fecdb5521b836a7d12aaed6b469911268e0)) 105 | * **deps:** update dependency golang to v1.21.4 ([5a5a16a](https://github.com/nabeken/go-smtp-source/commit/5a5a16a19b4ceb04a9992ed3fe297304a9725f8a)) 106 | * **deps:** update dependency golang to v1.21.5 ([26e9e16](https://github.com/nabeken/go-smtp-source/commit/26e9e168681c4bb6c70e7068c614c6786fa0ff72)) 107 | * **deps:** update dependency golang to v1.21.6 ([ef21a6e](https://github.com/nabeken/go-smtp-source/commit/ef21a6ef8c912af58cb378028a237c11efa95711)) 108 | * **deps:** update dependency golang to v1.22.1 ([c1574b4](https://github.com/nabeken/go-smtp-source/commit/c1574b46faac8f80deb2bc32b1370d4ef56458e6)) 109 | * **deps:** update dependency golang to v1.22.2 ([b6a4e5d](https://github.com/nabeken/go-smtp-source/commit/b6a4e5d1742af96da37aa5181c029accf4200695)) 110 | * **deps:** update go ([2806604](https://github.com/nabeken/go-smtp-source/commit/28066046f3bdd76c9cba545bba4d2909934078d1)) 111 | * **deps:** update google-github-actions/release-please-action action to v4 ([d7db4e5](https://github.com/nabeken/go-smtp-source/commit/d7db4e511b9aa431d3156d7be5827caf6d6975fb)) 112 | * **deps:** update google-github-actions/release-please-action digest to 4c5670f ([61fee60](https://github.com/nabeken/go-smtp-source/commit/61fee605f05b79768e063309d2034c61e7240747)) 113 | * **deps:** update google-github-actions/release-please-action digest to a37ac6e ([ddd0d7c](https://github.com/nabeken/go-smtp-source/commit/ddd0d7c701af483d04ae31fcf259df10cb5cadf7)) 114 | * **deps:** update google-github-actions/release-please-action digest to cc61a07 ([39566dc](https://github.com/nabeken/go-smtp-source/commit/39566dc2937f635e1f99d13a0cab6f5b1cab5569)) 115 | * **deps:** update google-github-actions/release-please-action digest to db8f2c6 ([bdba1f8](https://github.com/nabeken/go-smtp-source/commit/bdba1f8ed19be60d6c0b0cc9db067c99d9b21d4b)) 116 | * **deps:** update goreleaser/goreleaser-action action to v5 ([3256f11](https://github.com/nabeken/go-smtp-source/commit/3256f11fed410dc64bf73ce66ad52efc1c826b04)) 117 | * **deps:** update module golang.org/x/time to v0.4.0 ([2a0fe94](https://github.com/nabeken/go-smtp-source/commit/2a0fe941092e2682d315898da8c700636f73ee47)) 118 | * **deps:** update module golang.org/x/time to v0.5.0 ([50c5bb6](https://github.com/nabeken/go-smtp-source/commit/50c5bb6bc866a0cd7f5098cbdc07d263904f934c)) 119 | * **deps:** update nabeken/go-github-apps digest to 0577caf ([3b7355d](https://github.com/nabeken/go-smtp-source/commit/3b7355d3a77e11146b917890cd9e81b34fb0b8b8)) 120 | * **deps:** update nabeken/go-github-apps digest to 6eb3a08 ([cf2a59f](https://github.com/nabeken/go-smtp-source/commit/cf2a59f00f05a4fab94da400c111f6f1f2ceb1f5)) 121 | * **deps:** update nabeken/go-github-apps digest to fc859a2 ([457ba5f](https://github.com/nabeken/go-smtp-source/commit/457ba5f101a40e7fa2187e641e2f913a3074cd79)) 122 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | RUN apt-get update && apt-get install -y --no-install-recommends \ 6 | postfix \ 7 | time \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | WORKDIR /usr/src/app 11 | 12 | COPY go.mod go.sum ./ 13 | RUN go mod download && go mod verify 14 | 15 | COPY . . 16 | RUN go build -v -o /usr/local/bin/go-smtp-source 17 | 18 | COPY bench.sh /root/bench.sh 19 | COPY bench_check.sh /root/bench_check.sh 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, TANABE Ken-ichi 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of go-smtp-source nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-smtp-source 2 | 3 | [![Go](https://github.com/nabeken/go-smtp-source/actions/workflows/go.yml/badge.svg)](https://github.com/nabeken/go-smtp-source/actions/workflows/go.yml) 4 | 5 | go-smtp-source is a simple drop-in replacement for smtp-source in Postfix distribution written in Go 6 | 7 | ## Motivation 8 | 9 | I want to add some feature to smtp-source. I don't want to go with C because we have [Go](http://golang.org). 10 | 11 | ## Features (including TODOs) 12 | 13 | go-smtp-source does not providea all features that smtp-source provided but it has some additional feature. 14 | 15 | - :heavy_check_mark: STARTTLS support 16 | - :construction: Precious time metrics support (smtp-source does not provide elasped time. We need to use `time` with smtp-source.) 17 | - :construction: Clustering support for distributed load testing 18 | 19 | See [smtp-source(1)](http://www.postfix.org/smtp-source.1.html) about original smtp-source. 20 | 21 | ## Bench 22 | 23 | go-smtp-source should be performant. 24 | I measured the performance for go-smtp-source and smtp-source against smtp-sink with sending 10000 messages. 25 | 26 | ```sh 27 | docker compose build 28 | docker compose up -d sink 29 | docker compose run --rm bench 30 | Start sending 10000 messages... (GOMAXPROCS=default) 31 | 32 | Concurrency: 1 33 | smtp-source: 34 | 0.53user 6.06system 0:14.88elapsed 44%CPU (0avgtext+0avgdata 5880maxresident)k 35 | 0inputs+0outputs (0major+310minor)pagefaults 0swaps 36 | 37 | go-smtp-source: 38 | 0.87user 2.51system 0:03.31elapsed 102%CPU (0avgtext+0avgdata 11408maxresident)k 39 | 0inputs+8outputs (0major+4612minor)pagefaults 0swaps 40 | 41 | smtp-source (-d): 42 | 0.33user 2.86system 0:07.96elapsed 40%CPU (0avgtext+0avgdata 5876maxresident)k 43 | 0inputs+0outputs (0major+308minor)pagefaults 0swaps 44 | 45 | go-smtp-source (-d): 46 | 1.13user 4.92system 0:08.46elapsed 71%CPU (0avgtext+0avgdata 10460maxresident)k 47 | 0inputs+8outputs (0major+1697minor)pagefaults 0swaps 48 | ------------------------- 49 | Concurrency: 100 50 | smtp-source: 51 | 0.31user 1.59system 0:02.16elapsed 88%CPU (0avgtext+0avgdata 6644maxresident)k 52 | 0inputs+0outputs (0major+1098minor)pagefaults 0swaps 53 | 54 | go-smtp-source: 55 | 0.85user 2.55system 0:03.35elapsed 101%CPU (0avgtext+0avgdata 11496maxresident)k 56 | 0inputs+8outputs (0major+5212minor)pagefaults 0swaps 57 | 58 | smtp-source (-d): 59 | 0.20user 0.57system 0:01.07elapsed 72%CPU (0avgtext+0avgdata 6532maxresident)k 60 | 0inputs+0outputs (0major+604minor)pagefaults 0swaps 61 | 62 | go-smtp-source (-d): 63 | 0.29user 0.65system 0:00.91elapsed 104%CPU (0avgtext+0avgdata 12080maxresident)k 64 | 0inputs+8outputs (0major+2140minor)pagefaults 0swaps 65 | ------------------------- 66 | Concurrency: 1000 67 | smtp-source: 68 | 0.40user 2.08system 0:44.61elapsed 5%CPU (0avgtext+0avgdata 6324maxresident)k 69 | 0inputs+0outputs (0major+548minor)pagefaults 0swaps 70 | 71 | go-smtp-source: 72 | 0.89user 2.51system 0:03.35elapsed 101%CPU (0avgtext+0avgdata 11448maxresident)k 73 | 0inputs+8outputs (0major+5538minor)pagefaults 0swaps 74 | 75 | smtp-source (-d): 76 | 0.18user 0.71system 0:04.36elapsed 20%CPU (0avgtext+0avgdata 6792maxresident)k 77 | 0inputs+0outputs (0major+625minor)pagefaults 0swaps 78 | 79 | go-smtp-source (-d): 80 | 0.29user 0.64system 0:00.89elapsed 104%CPU (0avgtext+0avgdata 11748maxresident)k 81 | 0inputs+8outputs (0major+2175minor)pagefaults 0swaps 82 | ------------------------- 83 | ``` 84 | 85 | ## Test 86 | 87 | To confirm the delivery result, you can run `bench_check.sh` instead. 88 | 89 | ```sh 90 | docker compose build 91 | docker compose up -d sink 92 | docker compose run --rm bench /root/bench_check.sh 93 | Start sending 10000 messages... (GOMAXPROCS=default) 94 | 95 | Concurrency: 1 96 | smtp-source: 97 | 0.54user 5.93system 0:15.28elapsed 42%CPU (0avgtext+0avgdata 5852maxresident)k 98 | 0inputs+0outputs (0major+304minor)pagefaults 0swaps 99 | OK: got '10000' messages 100 | 101 | go-smtp-source: 102 | 0.99user 2.94system 0:04.31elapsed 91%CPU (0avgtext+0avgdata 11436maxresident)k 103 | 0inputs+8outputs (0major+5532minor)pagefaults 0swaps 104 | OK: got '10000' messages 105 | 106 | smtp-source (-d): 107 | 0.29user 2.80system 0:08.33elapsed 37%CPU (0avgtext+0avgdata 5804maxresident)k 108 | 0inputs+0outputs (0major+309minor)pagefaults 0swaps 109 | OK: got '10000' messages 110 | 111 | go-smtp-source (-d): 112 | 1.05user 5.16system 0:09.32elapsed 66%CPU (0avgtext+0avgdata 9964maxresident)k 113 | 0inputs+8outputs (0major+1565minor)pagefaults 0swaps 114 | OK: got '10000' messages 115 | ------------------------- 116 | Concurrency: 100 117 | smtp-source: 118 | 0.43user 2.28system 0:04.28elapsed 63%CPU (0avgtext+0avgdata 6196maxresident)k 119 | 0inputs+0outputs (0major+433minor)pagefaults 0swaps 120 | OK: got '10000' messages 121 | 122 | go-smtp-source: 123 | 0.95user 3.02system 0:04.32elapsed 91%CPU (0avgtext+0avgdata 11360maxresident)k 124 | 0inputs+8outputs (0major+5624minor)pagefaults 0swaps 125 | OK: got '10000' messages 126 | 127 | smtp-source (-d): 128 | 0.33user 1.11system 0:03.05elapsed 47%CPU (0avgtext+0avgdata 6544maxresident)k 129 | 0inputs+0outputs (0major+602minor)pagefaults 0swaps 130 | OK: got '10000' messages 131 | 132 | go-smtp-source (-d): 133 | 0.62user 1.64system 0:02.70elapsed 83%CPU (0avgtext+0avgdata 11556maxresident)k 134 | 0inputs+8outputs (0major+2047minor)pagefaults 0swaps 135 | OK: got '10000' messages 136 | ------------------------- 137 | Concurrency: 1000 138 | smtp-source: 139 | 0.51user 2.96system 1:12.16elapsed 4%CPU (0avgtext+0avgdata 6324maxresident)k 140 | 0inputs+0outputs (0major+719minor)pagefaults 0swaps 141 | OK: got '10000' messages 142 | 143 | go-smtp-source: 144 | 0.99user 2.96system 0:04.32elapsed 91%CPU (0avgtext+0avgdata 11424maxresident)k 145 | 0inputs+8outputs (0major+5805minor)pagefaults 0swaps 146 | OK: got '10000' messages 147 | 148 | smtp-source (-d): 149 | 0.30user 1.33system 0:09.50elapsed 17%CPU (0avgtext+0avgdata 7216maxresident)k 150 | 0inputs+0outputs (0major+634minor)pagefaults 0swaps 151 | OK: got '10000' messages 152 | 153 | go-smtp-source (-d): 154 | 0.60user 1.41system 0:02.48elapsed 81%CPU (0avgtext+0avgdata 11480maxresident)k 155 | 0inputs+8outputs (0major+2045minor)pagefaults 0swaps 156 | OK: got '10000' messages 157 | ------------------------- 158 | ``` 159 | 160 | ## Installation 161 | 162 | Download from [releases](https://github.com/nabeken/go-smtp-source/releases). 163 | 164 | Or 165 | 166 | ```sh 167 | go get -u github.com/nabeken/go-smtp-source 168 | ``` 169 | 170 | ## Usage 171 | 172 | Send 100 messages in 10 concurrency to SMTP server running on 127.0.0.1:10025 over TLS. 173 | 174 | ```sh 175 | go-smtp-source -s 10 -m 100 -tls 127.0.0.1:10025 176 | ``` 177 | 178 | ## smtp-sink 179 | 180 | [smtp-sink(1)](http://www.postfix.org/smtp-sink.1.html) is a good friend for benchmarking {go-,}smtp-source. 181 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | M=${M:-10000} 5 | 6 | HOST=${HOST:-sink} 7 | PORT=${PORT:-10025} 8 | 9 | echo "Start sending $M messages... (GOMAXPROCS=${GOMAXPROCS:-default})" 10 | echo 11 | 12 | for s in 1 100 1000; do 13 | echo "Concurrency: $s" 14 | echo "smtp-source:" 15 | /usr/bin/time smtp-source -s $s -m $M -f from@example.com -t to@example.com -M smtp.example.com ${HOST}:${PORT} 16 | 17 | echo 18 | echo "go-smtp-source:" 19 | /usr/bin/time go-smtp-source -s $s -m $M -resolve-once ${HOST}:${PORT} 20 | 21 | echo 22 | echo "smtp-source (-d):" 23 | /usr/bin/time smtp-source -d -s $s -m $M -f from@example.com -t to@example.com -M smtp.example.com ${HOST}:${PORT} 24 | 25 | echo 26 | echo "go-smtp-source (-d):" 27 | /usr/bin/time go-smtp-source -d -s $s -m $M -resolve-once ${HOST}:${PORT} 28 | 29 | echo "-------------------------" 30 | done 31 | -------------------------------------------------------------------------------- /bench_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | check() { 5 | local r=$1 6 | WANT=$(expr ${M} \* ${r}) 7 | GOT=$(grep 'X-Rcpt-Args:' /sink/dump | wc -l) 8 | 9 | if [ "${GOT}" -ne "${WANT}" ]; then 10 | echo "NOT OK: wants '${WANT}' messages but got '${GOT}' messages" >&2 11 | exit 1 12 | else 13 | echo "OK: got '${GOT}' messages" >&2 14 | fi 15 | } 16 | 17 | reset_dump() { 18 | rm -f "/sink/dump" 19 | } 20 | 21 | M=${M:-10000} 22 | 23 | HOST=${HOST:-sink_check} 24 | PORT=${PORT:-10026} 25 | 26 | reset_dump 27 | 28 | echo "Start sending $M messages... (GOMAXPROCS=${GOMAXPROCS:-default})" 29 | echo 30 | 31 | for s in 1 100 1000; do 32 | for r in 1 3; do 33 | echo "Concurrency: $s / Recipients: $r" 34 | echo "smtp-source:" 35 | /usr/bin/time smtp-source -s $s -m $M -r $r -f from@example.com -t to@example.com -M smtp.example.com ${HOST}:${PORT} 36 | check $r && reset_dump 37 | 38 | echo 39 | echo "go-smtp-source:" 40 | /usr/bin/time go-smtp-source -s $s -m $M -r $r -resolve-once ${HOST}:${PORT} 41 | check $r && reset_dump 42 | 43 | echo 44 | echo "smtp-source (-d):" 45 | /usr/bin/time smtp-source -d -s $s -m $M -r $r -f from@example.com -t to@example.com -M smtp.example.com ${HOST}:${PORT} 46 | check $r && reset_dump 47 | 48 | echo 49 | echo "go-smtp-source (-d):" 50 | /usr/bin/time go-smtp-source -d -s $s -m $M -r $r -resolve-once ${HOST}:${PORT} 51 | check $r && reset_dump 52 | 53 | echo "-------------------------" 54 | done 55 | done 56 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | sink: 5 | image: "ghcr.io/nabeken/docker-smtp-sink:latest" 6 | command: [-v, -h, mx.example.com, -m, '100', ':10025', '100'] 7 | ports: 8 | - "10025:10025" 9 | 10 | sink_check: 11 | image: "ghcr.io/nabeken/docker-smtp-sink:latest" 12 | volumes: 13 | - "sink:/sink" 14 | command: [-D, /sink/dump, -v, -h, mx.example.com, -m, '100', ':10026', '100'] 15 | ports: 16 | - "10026:10026" 17 | 18 | bench: 19 | build: "." 20 | volumes: 21 | - "sink:/sink" 22 | depends_on: 23 | - sink 24 | - sink_check 25 | command: /root/bench.sh 26 | 27 | volumes: 28 | sink: 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nabeken/go-smtp-source 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.4 6 | 7 | require ( 8 | github.com/google/gops v0.3.28 9 | github.com/pkg/errors v0.9.1 10 | golang.org/x/time v0.12.0 11 | ) 12 | 13 | require golang.org/x/sys v0.11.0 // indirect 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark= 2 | github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 6 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 8 | golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "crypto/tls" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "log" 12 | "net" 13 | "net/smtp" 14 | "net/textproto" 15 | "os" 16 | "strings" 17 | "sync" 18 | "time" 19 | 20 | "github.com/google/gops/agent" 21 | "github.com/pkg/errors" 22 | "golang.org/x/time/rate" 23 | ) 24 | 25 | var ( 26 | myDate = time.Now() 27 | myPid = os.Getpid() 28 | myhostname = "localhost" 29 | ) 30 | 31 | var ( 32 | defaultSender = "from@example.com" 33 | defaultRecipient = "to@example.com" 34 | defaultSubject = "from go-smtp-source" 35 | ) 36 | 37 | var config *Config 38 | 39 | type Config struct { 40 | Host string 41 | Sender string 42 | 43 | Recipient string 44 | RecipientCount int 45 | 46 | MessageCount int 47 | Sessions int 48 | MessageSize int 49 | Subject string 50 | 51 | DontDisconnect bool 52 | Verbose bool 53 | 54 | // extension 55 | UseTLS bool 56 | ResolveOnce bool 57 | QPS rate.Limit 58 | 59 | File string 60 | 61 | tlsConfig *tls.Config 62 | } 63 | 64 | func usage(m, def string) string { 65 | return fmt.Sprintf("%s [default: %s]", m, def) 66 | } 67 | 68 | func Parse() error { 69 | var ( 70 | msgcount = flag.Int("m", 1, usage("specify a number of messages to send.", "1")) 71 | msgsize = flag.Int("l", 0, usage("specify the size of the body.", "0")) 72 | session = flag.Int("s", 1, usage("specify a number of concurrent sessions.", "1")) 73 | sender = flag.String("f", defaultSender, usage("specify a sender address.", defaultSender)) 74 | subject = flag.String("S", defaultSubject, usage("specify a subject.", defaultSubject)) 75 | 76 | dontDisconnect = flag.Bool("d", false, usage("do not disconnect after sending a message; send the next message over the same connection.", "false")) 77 | 78 | verbose = flag.Bool("v", false, usage("enable verbose mode.", "false")) 79 | 80 | recipient = flag.String("t", defaultRecipient, usage("specify a recipient address.", defaultRecipient)) 81 | 82 | recipientCount = flag.Int( 83 | "r", 1, 84 | usage("specify the number of recipients to send per transaction. Recipient names are generated by prepending a number to the recipient address.", "1"), 85 | ) 86 | 87 | usetls = flag.Bool("tls", false, usage("specify if STARTTLS is needed.", "false")) 88 | resolveOnce = flag.Bool("resolve-once", false, usage("resolve the hostname only once.", "false")) 89 | 90 | qps = flag.Float64("q", 0, usage("specify a queries per second.", "no rate limit")) 91 | 92 | file = flag.String("F", "", "send a preformatted header and message specified in the file.") 93 | ) 94 | 95 | flag.Parse() 96 | 97 | host := flag.Arg(0) 98 | if host == "" { 99 | return errors.New("host is missing") 100 | } 101 | 102 | config = &Config{ 103 | Host: host, 104 | Sender: *sender, 105 | 106 | Recipient: *recipient, 107 | RecipientCount: *recipientCount, 108 | 109 | MessageCount: *msgcount, 110 | MessageSize: *msgsize, 111 | Sessions: *session, 112 | Subject: *subject, 113 | 114 | DontDisconnect: *dontDisconnect, 115 | Verbose: *verbose, 116 | 117 | UseTLS: *usetls, 118 | ResolveOnce: *resolveOnce, 119 | 120 | QPS: rate.Limit(*qps), 121 | 122 | File: *file, 123 | 124 | tlsConfig: &tls.Config{ 125 | InsecureSkipVerify: true, 126 | }, 127 | } 128 | return nil 129 | } 130 | 131 | type clientCall struct { 132 | c *smtp.Client 133 | err error 134 | 135 | initialized bool 136 | } 137 | 138 | type transaction struct { 139 | Sender string 140 | Recipients []string 141 | TxIdx int 142 | Data []byte 143 | } 144 | 145 | func sendMail(c *smtp.Client, initialized bool, tx *transaction) (bool, error) { 146 | if !initialized { 147 | if config.UseTLS { 148 | if err := c.StartTLS(config.tlsConfig); err != nil { 149 | return initialized, errors.Wrap(err, "unable to issue STARTTLS") 150 | } 151 | } else { 152 | if err := c.Hello(myhostname); err != nil { 153 | return initialized, errors.Wrap(err, "unable to say hello") 154 | } 155 | } 156 | 157 | initialized = true 158 | } 159 | 160 | if err := c.Mail(config.Sender); err != nil { 161 | return initialized, errors.Wrap(err, "unable to start SMTP transaction") 162 | } 163 | 164 | for i := range tx.Recipients { 165 | if err := c.Rcpt(tx.Recipients[i]); err != nil { 166 | return initialized, errors.Wrap(err, "unable to issue RCPT") 167 | } 168 | } 169 | 170 | wc, err := c.Data() 171 | if err != nil { 172 | return initialized, errors.Wrap(err, "unable to start DATA") 173 | } 174 | if data := tx.Data; len(data) > 0 { 175 | if _, err := wc.Write(data); err != nil { 176 | return initialized, errors.Wrap(err, "unable to write preformatted data") 177 | } 178 | } else { 179 | fmt.Fprintf(wc, "From: <%s>\n", config.Sender) 180 | fmt.Fprintf(wc, "To: <%s>\n", config.Recipient) 181 | fmt.Fprintf(wc, "Date: %s\n", myDate.Format(time.RFC1123)) 182 | 183 | subject := fmt.Sprintf(config.Subject, tx.TxIdx) 184 | if subjectIdx := strings.Index(subject, "%!(EXTRA"); subjectIdx >= 0 { 185 | fmt.Fprintf(wc, "Subject: %s\n", subject[0:subjectIdx]) 186 | } else { 187 | fmt.Fprintf(wc, "Subject: %s\n", subject) 188 | } 189 | fmt.Fprintf(wc, "Message-Id: <%04x.%04x@%s>\n", myPid, config.MessageCount, myhostname) 190 | fmt.Fprintln(wc, "") 191 | 192 | if config.MessageSize == 0 { 193 | for i := 1; i < 5; i++ { 194 | fmt.Fprintf(wc, "La de da de da %d.\n", i) 195 | } 196 | } else { 197 | for i := 1; i < config.MessageSize; i++ { 198 | fmt.Fprint(wc, "X") 199 | if i%80 == 0 { 200 | fmt.Fprint(wc, "\n") 201 | } 202 | } 203 | } 204 | } 205 | 206 | return initialized, errors.Wrap(wc.Close(), "unable to commit the SMTP transaction") 207 | } 208 | 209 | // txidx starts from 1. 210 | func generateRecipients(rcpt string, txidx, recipientCount, nrcpt int) []string { 211 | pos := 1 212 | if txidx > 1 { 213 | pos += (txidx - 1) * recipientCount 214 | } 215 | 216 | recipients := make([]string, 0, nrcpt) 217 | for i := 0; i < nrcpt; i++ { 218 | recipients = append(recipients, fmt.Sprintf("%d_%s", pos, rcpt)) 219 | pos++ 220 | } 221 | 222 | return recipients 223 | } 224 | 225 | func formatData(fn string) ([]byte, error) { 226 | f, err := os.Open(fn) 227 | if err != nil { 228 | return nil, errors.Wrapf(err, "failed to open '%s'", fn) 229 | } 230 | defer f.Close() 231 | 232 | buf := &bytes.Buffer{} 233 | w := textproto.NewWriter(bufio.NewWriter(buf)).DotWriter() 234 | if _, err := io.Copy(w, f); err != nil { 235 | return nil, err 236 | } 237 | if err := w.Close(); err != nil { 238 | return nil, errors.Wrap(err, "failed to close") 239 | } 240 | 241 | return buf.Bytes(), nil 242 | } 243 | 244 | func main() { 245 | if err := agent.Listen(agent.Options{}); err != nil { 246 | log.Fatal(err) 247 | } 248 | if err := Parse(); err != nil { 249 | log.Fatal(err) 250 | } 251 | 252 | var data []byte 253 | if fn := config.File; fn != "" { 254 | data_, err := formatData(fn) 255 | if err != nil { 256 | log.Fatal(err) 257 | } 258 | data = data_ 259 | } 260 | 261 | addr, port, err := net.SplitHostPort(config.Host) 262 | if err != nil { 263 | log.Fatal(err) 264 | } 265 | 266 | if config.ResolveOnce { 267 | addrs, err := net.LookupHost(addr) 268 | if err != nil { 269 | log.Fatal(err) 270 | } 271 | 272 | // use first one 273 | addr = addrs[0] 274 | } 275 | 276 | clientCh := make(chan *clientCall, config.Sessions) 277 | clientCtx, clientCancel := context.WithCancel(context.Background()) 278 | 279 | clientSessionDone := make(chan struct{}) 280 | 281 | if config.DontDisconnect { 282 | go func() { 283 | defer func() { 284 | close(clientSessionDone) 285 | 286 | if config.Verbose { 287 | log.Print("clientSession is done") 288 | } 289 | }() 290 | 291 | for i := 1; i <= config.Sessions; i++ { 292 | select { 293 | case <-clientCtx.Done(): 294 | if config.Verbose { 295 | log.Print("client ctx is canceled. no longer create a new connection.") 296 | } 297 | 298 | return 299 | default: 300 | } 301 | 302 | if config.Verbose { 303 | log.Printf("Opening a SMTP session (%d)...", i) 304 | } 305 | 306 | // opening a connection first 307 | conn, err := net.Dial("tcp", addr+":"+port) 308 | if err != nil { 309 | log.Fatalf("opening a SMTP session: %s", err) 310 | } 311 | 312 | if tcpConn, ok := conn.(*net.TCPConn); ok { 313 | // smtp-source does this so we just follow it 314 | if err := tcpConn.SetLinger(0); err != nil { 315 | log.Fatalf("setting linger to SMTP session (%d)...", i) 316 | } 317 | } 318 | 319 | sc, err := smtp.NewClient(conn, addr) 320 | clientCh <- &clientCall{sc, nil, false} 321 | } 322 | }() 323 | } 324 | 325 | ntx := config.MessageCount 326 | txCh := make(chan *transaction, ntx) 327 | 328 | go func() { 329 | for i := 0; i < ntx; i++ { 330 | txidx := i + 1 331 | 332 | nrcpt := config.RecipientCount 333 | 334 | tx := &transaction{ 335 | Sender: config.Sender, 336 | TxIdx: txidx, 337 | Data: data, 338 | } 339 | 340 | if config.RecipientCount > 1 { 341 | tx.Recipients = generateRecipients(config.Recipient, txidx, config.RecipientCount, nrcpt) 342 | } else { 343 | tx.Recipients = []string{config.Recipient} 344 | } 345 | 346 | if config.Verbose { 347 | log.Printf("ntx:%d i:%d txidx:%d nrcpt:%d recipients:%v", ntx, i, txidx, nrcpt, tx.Recipients) 348 | } 349 | 350 | if !config.DontDisconnect { 351 | if config.Verbose { 352 | log.Printf("Opening a SMTP session (%d)...", txidx) 353 | } 354 | 355 | conn, err := net.Dial("tcp", addr+":"+port) 356 | if err != nil { 357 | clientCh <- &clientCall{nil, err, false} 358 | continue 359 | } 360 | 361 | if tcpConn, ok := conn.(*net.TCPConn); ok { 362 | // smtp-source does this so we just follow it 363 | if err := tcpConn.SetLinger(0); err != nil { 364 | clientCh <- &clientCall{nil, err, false} 365 | continue 366 | } 367 | } 368 | 369 | c, err := smtp.NewClient(conn, addr) 370 | clientCh <- &clientCall{c, err, false} 371 | } 372 | 373 | txCh <- tx 374 | } 375 | }() 376 | 377 | // wait group for all attempts 378 | var wg sync.WaitGroup 379 | wg.Add(ntx) 380 | 381 | limiter := rate.NewLimiter(rate.Inf, 0) 382 | if config.QPS > 0 { 383 | limiter = rate.NewLimiter(config.QPS, config.RecipientCount) 384 | } 385 | 386 | for i := 0; i < ntx; i++ { 387 | cc := <-clientCh 388 | 389 | go func(cc *clientCall, txidx int) { 390 | defer func() { 391 | if config.DontDisconnect { 392 | clientCh <- cc 393 | } 394 | wg.Done() 395 | }() 396 | 397 | if cc.err != nil { 398 | log.Println("unable to connect to the server:", cc.err) 399 | return 400 | } 401 | 402 | tx := <-txCh 403 | 404 | limiter.WaitN(context.TODO(), len(tx.Recipients)) 405 | initialized, err := sendMail(cc.c, cc.initialized, tx) 406 | if err != nil { 407 | log.Fatal("unable to send a mail:", err) 408 | } 409 | 410 | cc.initialized = initialized 411 | 412 | if !config.DontDisconnect { 413 | if err := cc.c.Quit(); err != nil { 414 | log.Println("unable to quit a session:", err) 415 | } 416 | 417 | if config.Verbose { 418 | log.Printf("The SMTP session (%d) has been closed.", txidx+1) 419 | } 420 | } 421 | }(cc, i) 422 | } 423 | 424 | if config.Verbose { 425 | log.Printf("Waiting for the messages to be sent....") 426 | } 427 | 428 | wg.Wait() 429 | clientCancel() 430 | 431 | if config.Verbose { 432 | log.Printf("Client opening session context has been canceled") 433 | } 434 | 435 | clientCloseDone := make(chan struct{}) 436 | 437 | if config.DontDisconnect { 438 | go func() { 439 | defer func() { 440 | close(clientCloseDone) 441 | }() 442 | 443 | if config.Verbose { 444 | log.Print("Closing all the SMTP sessions...") 445 | } 446 | 447 | i := 1 448 | for cc := range clientCh { 449 | if err := cc.c.Quit(); err != nil { 450 | log.Fatal("unable to quit a session:", err) 451 | } 452 | 453 | if config.Verbose { 454 | log.Printf("The SMTP session (%d) has been closed.", i) 455 | } 456 | 457 | i++ 458 | } 459 | }() 460 | 461 | if config.Verbose { 462 | log.Print("Waiting for the client session is done...") 463 | } 464 | 465 | <-clientSessionDone 466 | close(clientCh) 467 | 468 | if config.Verbose { 469 | log.Print("Waiting for all the sessions are closed...") 470 | } 471 | 472 | <-clientCloseDone 473 | 474 | if config.Verbose { 475 | log.Print("All the sessions has been closed.") 476 | } 477 | } 478 | } 479 | --------------------------------------------------------------------------------