smtp

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package smtp probes a mailbox over SMTP to see whether the receiving server accepts mail for it, and detects catch-all servers.

It is a separate module from github.com/bakhod1r/emailx: it is the only part of the project that opens an outbound mail connection, and keeping it out of the core package means a program that only parses addresses links no third-party code.

import smtpx "github.com/bakhod1r/emailx/smtp"

res := smtpx.Verify("user@example.com")
res.Status   // StatusValid, StatusInvalid, StatusCatchAll, StatusUnknown
res.CatchAll // the server also accepted a random address

Caveats

A StatusValid result is a strong signal but not proof. Most cloud providers block outbound port 25 entirely, so probes from them return StatusUnknown. Unthrottled probing is the fastest way to get a sending IP blocklisted, so a probe-heavy workload should always set Options.Limiter. Many large providers deliberately answer every probe the same way, which shows up as StatusCatchAll.

Concurrency

All exported functions are safe for concurrent use.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Limiter

type Limiter interface {
	// Wait blocks until the caller may proceed, or until ctx is done.
	Wait(ctx context.Context) error
}

Limiter paces outbound work. Verify calls Wait before each connection, so a caller can keep probe volume under whatever the receiving side and their own reputation will tolerate.

type Options

type Options struct {
	// HelloName is the domain sent in EHLO. It should be a hostname that
	// resolves back to the connecting IP, otherwise many servers refuse.
	HelloName string
	// FromAddress is the envelope sender used in MAIL FROM. An empty
	// sender ("<>") is standard for verification but some servers reject it.
	FromAddress string
	// Timeout bounds the whole probe. Defaults to 10s.
	Timeout time.Duration
	// Port is the SMTP port to dial. Defaults to 25.
	Port string
	// SkipCatchAllCheck skips the extra random-address probe, halving the
	// work when catch-all status is not needed.
	SkipCatchAllCheck bool
	// Limiter paces connections. Nil means no pacing, which is only safe
	// for occasional one-off probes: sustained unthrottled probing gets the
	// sending IP blocklisted. Share one Limiter across all probes.
	Limiter Limiter
}

Options tunes an SMTP probe.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter is a token bucket: rate tokens per second, up to burst held in reserve. Unthrottled SMTP probing is the fastest way to get an IP blocklisted, so a probe-heavy workload should always set one.

func NewRateLimiter

func NewRateLimiter(perSecond float64, burst int) *RateLimiter

NewRateLimiter allows perSecond probes per second, permitting short bursts of up to burst. A non-positive perSecond means no limit; burst below 1 is treated as 1.

Example
package main

import (
	"fmt"

	smtpx "github.com/bakhod1r/emailx/smtp"
)

func main() {
	// Five probes per second, allowing short bursts of ten.
	limiter := smtpx.NewRateLimiter(5, 10)

	opts := smtpx.Options{
		Limiter:           limiter,
		SkipCatchAllCheck: true,
	}
	_ = opts // pass to Verify for each address in the batch
	fmt.Println("limiter ready")
}
Output:
limiter ready

func (*RateLimiter) Wait

func (l *RateLimiter) Wait(ctx context.Context) error

Wait blocks until a token is available or ctx is done.

type Result

type Result struct {
	Status Status
	// MXHost is the mail server that answered.
	MXHost string
	// Code and Message are the last SMTP reply seen for the RCPT TO probe.
	Code    int
	Message string
	// CatchAll reports that a random, certainly-nonexistent address was
	// also accepted.
	CatchAll bool
	// Greylisted reports a 4xx temporary refusal; a retry later may succeed.
	Greylisted bool
	// STARTTLS reports whether the server advertised STARTTLS.
	STARTTLS bool
	// Duration is how long the probe took.
	Duration time.Duration
	// Err holds the transport-level failure, if any.
	Err error
}

Result describes an SMTP verification attempt.

func Verify

func Verify(address string, opts ...Options) *Result

Verify probes the mail server for a domain to see whether it accepts mail for an address. Note that a "valid" result is a strong signal but not proof: servers may accept mail they later bounce, and many providers deliberately answer every probe the same way.

func VerifyContext

func VerifyContext(ctx context.Context, address string, opts ...Options) *Result

VerifyContext is Verify bounded by ctx.

type Status

type Status string

Status is the outcome of an SMTP mailbox probe.

const (
	// StatusValid means the server accepted RCPT TO for the address.
	StatusValid Status = "valid"
	// StatusInvalid means the server rejected the address permanently (5xx).
	StatusInvalid Status = "invalid"
	// StatusCatchAll means the server accepts every address, so acceptance
	// proves nothing about this particular mailbox.
	StatusCatchAll Status = "catch-all"
	// StatusUnknown means the probe could not reach a conclusion: no MX,
	// connection refused, greylisting, or a 4xx temporary refusal.
	StatusUnknown Status = "unknown"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL