A driver is responsible for executing a single request and returning a result. Each target in your config specifies a type that selects the driver. All drivers map their results to HTTP-like status codes so the engine’s error classifier, backoff, and metrics work uniformly.

Replay support

sendit replay reuses HTTP, browser, DNS, WebSocket, and gRPC drivers with their captured expanded settings. Browser replay requires Chrome; gRPC still uses server reflection. Same-host HTTP redirects retain normal behavior and cross-host redirects follow the captured allow_cross_host_redirects setting, without the engine’s redirect rate limiter.

Targets with any populated auth field, custom HTTP headers (even in an inactive block), URL userinfo, or SFTP are marked non-replayable. Snapshots contain only the active block: HTTP method/body/timeout/redirect policy; browser scroll/selector/timeout; DNS resolver/record type; WebSocket messages/duration/expected count; gRPC body/timeout/TLS settings. Values already expanded by templating are replayed literally.

Replay does not recreate remote responses, DNS answers, proxy settings, system trust stores, browser versions, or connection state. Driver errors remain results and do not stop later requests. Cancellation waits for replay-owned driver calls; existing DNS exchange and WebSocket close latency still applies. See the CLI contract.

auth block

Any target (or target_defaults) can include an auth block to attach credentials to each request. The http and websocket drivers honour it; SFTP uses credentials from its sftp block; other drivers silently ignore it.

targets:
  - url: "https://api.example.com/data"
    type: http
    auth:
      type: bearer
      token_env: API_TOKEN       # resolved from environment at dispatch time
FieldDescription
typebearer | basic | header | query
tokenLiteral token value (triggers a startup warning — prefer token_env in production)
token_envName of the environment variable holding the token
username / username_envBasic auth username (literal or env var)
password / password_envBasic auth password (literal or env var) — optional
header_nameHeader name for type: header (e.g. X-API-Key)
param_nameQuery parameter name for type: query (e.g. api_key)

Auth types:

TypeEffect
bearerAdds Authorization: Bearer <token> header
basicAdds Authorization: Basic <base64(user:pass)> header
headerAdds <header_name>: <token> header
queryAdds or replaces <param_name> while preserving other query values

Authentication applies to HTTP and WebSocket targets. Query authentication adds or replaces the configured parameter while preserving other query values. Environment-backed tokens are resolved at dispatch time. Results and exported output retain the expanded target URL, not the credential-bearing dial URL. If an environment variable is unset when a request fires, the result carries an error and no request is made.

Shared credentials via target_defaults:

target_defaults:
  auth:
    type: bearer
    token_env: API_TOKEN

targets_file: "config/targets.txt"

All file-loaded targets inherit the shared auth. Inline targets can override or omit it.

Request templates

Request templates expand per dispatch before domain rate limiting and driver execution. The supported surfaces are:

  • Every target’s url
  • http.body
  • grpc.body
  • Each websocket.send_messages entry

The same selected or generated value is reused across all fields in one request. Headers, authentication values, browser options, DNS options, and SFTP options are not templated. See Configuration for vars, vars_file, built-ins, and validation rules.

http

Sends an HTTP/HTTPS request using Go’s standard net/http client.

targets:
  - url: "https://example.com/api"
    weight: 5
    type: http
    http:
      http_version: 0                    # 0 automatic | 1 HTTP/1.1 | 2 HTTPS-only HTTP/2
      method: GET                        # GET | POST | PUT | DELETE | ...
      headers:
        User-Agent: "Mozilla/5.0 ..."
        Accept: "application/json"
      body: '{"key":"value"}'            # optional request body (string)
      timeout_s: 15                      # per-request timeout in seconds
      allow_cross_host_redirects: false  # opt in to follow redirects to another host
FieldDefaultDescription
methodGETHTTP verb
http_version0Integer 0: automatic; 1: HTTP/1.1; 2: HTTPS-only HTTP/2, without HTTP/1 fallback
headers{}Key-value map of request headers
body""Optional request body; supports request templates
timeout_s15Per-request timeout (seconds)
allow_cross_host_redirectsfalseFollow redirects to a different host. Redirected hosts still use per-domain rate limits. Keep disabled when sending auth headers unless that forwarding is intended.

Note: HTTP header map keys are lowercased by the YAML parser (e.g. User-Agent is stored as user-agent). This is standard YAML behaviour.

Non-standard ports: include the port directly in the URL — Go’s net/http client handles it natively:

- url: "http://internal-api.example.com:8080/health"
  type: http
- url: "https://staging.example.com:8443/api"
  type: http

HTTP version selection

Mode 0 preserves automatic HTTP/1.1 for plaintext and ALPN negotiation of HTTP/1.1 or HTTP/2 for HTTPS. Mode 1 sends HTTP/1.1 requests. Mode 2 requires HTTPS with ALPN h2; an HTTP/1-only or no-ALPN server fails before the HTTP request is transmitted. Normal certificate verification remains enabled. No plaintext h2c or HTTP/3 is implemented; HTTP/3 is tracked in #320.

The policy applies to every redirect hop. A forced-H2 request cannot follow a redirect to plaintext or send an HTTP/1 request to an incompatible destination. Existing cross-host permission, auth, timeout, and engine redirect-limiter behavior is retained. Distinct transports are reused safely for concurrent mixed policies. HTTP protocol controls do not change browser, WebSocket or gRPC transports.

http_protocol in JSONL and debug logs reports the actual final response protocol (HTTP/1.1, HTTP/2.0, etc.), including policy-stopped redirects. No response means no protocol metadata. It is independent of requested http_version, which is preserved in v2 replay snapshots. V1 replay input remains automatic. Dry-run displays configured policy only and sends no traffic.

browser

Loads a page in a headless Chromium instance via chromedp. Each task spawns its own ExecAllocator — no shared browser state — which prevents memory accumulation across long runs.

targets:
  - url: "https://news.ycombinator.com"
    weight: 3
    type: browser
    browser:
      scroll: true                    # scroll to mid-page then bottom
      wait_for_selector: "#hnmain"    # CSS selector to wait for before returning
      timeout_s: 30                   # page load timeout in seconds
FieldDefaultDescription
scrollfalseScroll to mid-page then bottom after load
wait_for_selector""Wait for this CSS selector to be visible
timeout_s30Page load timeout (seconds)

Prerequisite: Chrome or Chromium must be installed on the machine running sendit.

Use max_browser_workers in limits to cap concurrent browser instances independently of the global worker pool:

limits:
  max_workers: 4
  max_browser_workers: 1   # at most 1 Chrome instance at a time

dns

Resolves a hostname using miekg/dns directly — no system resolver involved. DNS RCODEs are mapped to HTTP-like status codes:

DNS RCODEHTTP equivalentEffect
NOERROR (0)200success
NXDOMAIN (3)404permanent skip (no retry)
REFUSED (5)403permanent skip (no retry)
SERVFAIL (2)503transient backoff
other502transient backoff
targets:
  - url: "example.com"
    weight: 3
    type: dns
    dns:
      resolver: "8.8.8.8:53"    # DNS server address
      record_type: A             # A | AAAA | MX | TXT | CNAME | ...
FieldDefaultDescription
resolver8.8.8.8:53DNS server host:port
record_typeADNS record type to query

The resolver field is always host:port, so non-standard DNS ports are supported directly:

dns:
  resolver: "192.168.1.1:5353"   # custom resolver on non-standard port
  record_type: A

websocket

Opens a WebSocket connection using coder/websocket, optionally sends messages, and holds the connection open for a configurable duration.

targets:
  - url: "wss://stream.example.com/feed"
    weight: 2
    type: websocket
    websocket:
      duration_s: 30                            # hold connection for this many seconds
      send_messages: ['{"type":"subscribe"}']   # messages to send on connect
      expect_messages: 1                        # wait to receive this many messages
FieldDefaultDescription
duration_s30How long to hold the connection open (seconds)
send_messages[]List of text messages to send after connecting; supports request templates
expect_messages0Minimum messages to receive before considering success

Non-standard ports: include the port in the URL:

- url: "wss://stream.example.com:9443/feed"
  type: websocket

grpc

Executes a unary gRPC call using google.golang.org/grpc. No .proto files are required — the driver uses server reflection to discover request and response types at runtime, then marshals the JSON body to protobuf automatically.

targets:
  - url: grpc://localhost:50051/helloworld.Greeter/SayHello
    weight: 10
    type: grpc
    grpc:
      body: '{"name": "world"}'   # JSON-encoded request (optional — defaults to empty message)
      timeout_s: 15               # per-call timeout in seconds
      tls: false                  # force TLS even when scheme is grpc://
      insecure: false             # skip TLS certificate verification
FieldDefaultDescription
body""JSON-encoded request body. Must match the method’s input proto type. Empty sends a default-constructed message. Supports request templates.
timeout_s15Per-call timeout in seconds
tlsfalseForce TLS even when the URL scheme is grpc://
insecurefalseSkip TLS certificate verification (combine with tls: true or grpcs:// scheme)

URL scheme selects transport security:

SchemeTransport
grpc://host:port/Service/MethodPlaintext
grpcs://host:port/Service/MethodTLS

gRPC status codes are mapped to HTTP-like status codes so the engine’s backoff and error classifier work uniformly:

gRPC codeHTTP equivalentEffect
OK (0)200success
InvalidArgument (3), OutOfRange (11)400permanent skip
Unauthenticated (16)401permanent skip
PermissionDenied (7)403permanent skip
NotFound (5)404permanent skip
AlreadyExists (6)409permanent skip
ResourceExhausted (8)429transient backoff
Unimplemented (12)501permanent skip
Unavailable (14)503transient backoff
DeadlineExceeded (4)504transient backoff
other500transient backoff

Prerequisite: the gRPC server must have the server reflection service enabled. Most frameworks enable it via a single line (e.g. reflection.Register(s) in Go). If reflection is not available, the driver returns an error immediately.

Connection and descriptor caching: connections and method descriptors are cached per address+TLS mode. Reflection is called only on the first request to each method; subsequent calls reuse the cached descriptor.

# Multiple gRPC targets on the same server — connection is shared
targets:
  - url: grpc://api.example.com:50051/user.UserService/GetUser
    type: grpc
    weight: 8
    grpc:
      body: '{"user_id": "u-123"}'
  - url: grpc://api.example.com:50051/user.UserService/ListUsers
    type: grpc
    weight: 2
    grpc:
      body: '{}'

sftp

Executes SFTP operations over SSH using github.com/pkg/sftp and golang.org/x/crypto/ssh.

targets:
  - url: sftp://sftp.example.com/uploads/test.bin
    weight: 2
    type: sftp
    sftp:
      username: testuser
      password: secret
      insecure: false
      operation: upload
      file_size_min_bytes: 1024
      file_size_max_bytes: 1048576
      allowed_host_key_types: [ssh-ed25519]
FieldDefaultDescription
port22SSH port when the URL does not include one
operationuploadupload, download, or list
timeout_s30Connection and operation timeout in seconds
insecurefalseSkip ~/.ssh/known_hosts host-key verification; use only for trusted test hosts
username""SSH username; required
password""Password authentication; mutually exclusive with private_key
private_key""Private key file path or inline PEM string; mutually exclusive with password
file_size_bytes0Fixed upload payload size; omit or leave 0 to use a range or the default
file_size_min_bytes0Minimum random upload payload size
file_size_max_bytes0Maximum random upload payload size
eicarfalseUpload the EICAR test string instead of generated data; upload only
allowed_ciphers[]SSH cipher allow-list; empty accepts the library defaults
allowed_kex[]SSH key-exchange allow-list; empty accepts the library defaults
allowed_host_key_types[]SSH host-key algorithm allow-list; empty accepts the library defaults
allowed_macs[]SSH MAC allow-list; empty accepts the library defaults

Operations:

OperationEffect
uploadCreates or truncates the remote path and writes generated payload bytes
downloadReads the remote path and reports bytes transferred
listLists the remote directory and adds sftp_entry_count to JSONL metadata

Payload sizing: set file_size_bytes for a fixed upload size, or set file_size_min_bytes and file_size_max_bytes for a random size chosen per request. If no size is set, uploads use a small default payload. Set eicar: true only when you intentionally want antivirus or malware-scanning infrastructure to see the EICAR standard test string.

Host keys: by default, SFTP verifies host keys against ~/.ssh/known_hosts. Set insecure: true only for trusted lab or ephemeral hosts where pinning is impractical.

Algorithm policy: use the allowed_* lists to enforce SSH policy. If the server cannot negotiate an allowed algorithm, the result is mapped to 502.

sftp:
  allowed_ciphers: [aes256-gcm@openssh.com, chacha20-poly1305@openssh.com]
  allowed_kex: [curve25519-sha256]
  allowed_host_key_types: [ssh-ed25519]
  allowed_macs: [hmac-sha2-256-etm@openssh.com]

SFTP status codes are mapped to HTTP-like status codes:

ConditionHTTP equivalentEffect
Success200success
Auth failure401permanent skip
Permission denied403permanent skip
Missing download path404permanent skip
Host key or algorithm policy mismatch502transient backoff
SFTP protocol error502transient backoff
Timeout504transient backoff

JSONL metadata: SFTP results include sftp_server_version, sftp_host_key_type, sftp_host_key_fp, and sftp_auth_methods when available. list results also include sftp_entry_count.