zerodrop

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 10 Imported by: 0

README

zerodrop-go

Go Reference Go Report Card license

Email verification infrastructure for CI pipelines and AI agents.

Send a verification email. Catch it at the edge. Get email.OTP and email.MagicLink back — auto-extracted, no regex, no Docker, no signup.

email, err := client.WaitForLatest(ctx, inbox, nil)

email.OTP       // "123456" — auto-extracted
email.MagicLink // "https://..." — no regex needed

Documentation · GitHub · Status

Install

go get github.com/zerodrop-dev/zerodrop-go

Zero dependencies — stdlib only. Go 1.21+.

Zero-Auth Mode (Local Development)

package main

import (
	"context"
	"fmt"

	zerodrop "github.com/zerodrop-dev/zerodrop-go"
)

func main() {
	client := zerodrop.New()
	inbox := client.GenerateInbox()
	// → "swift-x7k29a@zerodrop-sandbox.online"

	email, err := client.WaitForLatest(context.Background(), inbox, nil)
	if err != nil {
		panic(err)
	}

	fmt.Println(email.Subject)   // "Reset your password"
	fmt.Println(email.OTP)       // "123456" — auto-extracted, no regex
	fmt.Println(email.MagicLink) // "https://..." — auto-extracted
}

CI Pipeline Mode (go test)

func TestSignupEmailVerification(t *testing.T) {
	client := zerodrop.New()
	inbox := client.GenerateInbox()

	// Trigger your app's signup flow with the inbox address...
	signUp(t, inbox)

	ctx := context.Background()
	email, err := client.WaitForLatest(ctx, inbox, &zerodrop.Options{
		Timeout: 15 * time.Second,
	})
	if err != nil {
		t.Fatalf("no verification email: %v", err)
	}

	if email.OTP == "" {
		t.Fatal("expected OTP in verification email")
	}

	// Continue the flow with email.OTP...
	verify(t, inbox, email.OTP)
}

SSE — Sub-Second Delivery

WaitForLatest uses Server-Sent Events by default. Emails arrive in under a second instead of waiting for the next poll. Polling fallback is automatic.

// Force polling mode if needed
email, err := client.WaitForLatest(ctx, inbox, &zerodrop.Options{
	DisableSSE: true,
})

Email Filtering

Filter by sender, subject, body, or extracted fields when multiple emails land in the same inbox:

email, err := client.WaitForLatest(ctx, inbox, &zerodrop.Options{
	Timeout: 15 * time.Second,
	Filter: &zerodrop.Filter{
		From:    "noreply@yourapp.com",
		Subject: "Verify",
		HasOTP:  zerodrop.Bool(true),
	},
})

All string filters are case-insensitive partial matches.

OTP Auto-Extraction

ZeroDrop extracts OTP codes and magic links at Cloudflare's edge before emails reach your test. No regex required.

email.OTP       // "123456" — 4-8 digit verification code
email.MagicLink // "https://app.com/verify?token=abc" — verification/reset link
email.Body      // Full plain-text body if you need it

Both fields are empty strings if not detected.

Parallel Test Runs

GenerateInbox runs locally — no network request, no throttling:

// Safe to run in parallel — each test gets an isolated inbox
func TestParallelSignups(t *testing.T) {
	t.Parallel()
	client := zerodrop.New()

	for i := 0; i < 50; i++ {
		i := i
		t.Run(fmt.Sprintf("user-%d", i), func(t *testing.T) {
			t.Parallel()
			inbox := client.GenerateInbox() // unique, zero collision
			// ...
		})
	}
}

Workspaces

client := zerodrop.New(
	zerodrop.WithAPIKey(os.Getenv("ZERODROP_API_KEY")),
)

Self-Hosted

client := zerodrop.New(
	zerodrop.WithBaseURL("https://your-instance.yourdomain.com"),
)

API

Function Description
New(opts ...ClientOption) *Client Create a client. No options = free sandbox mode.
(*Client).GenerateInbox() string Instant inbox address. No network request.
(*Client).FetchLatest(ctx, inbox, filter) (*Email, error) Latest matching email or nil.
(*Client).WaitForLatest(ctx, inbox, opts) (*Email, error) Block until email arrives. SSE + polling fallback.
Bool(v bool) *bool Helper for Filter.HasOTP / Filter.HasMagicLink.
Errors
email, err := client.WaitForLatest(ctx, inbox, nil)

var timeoutErr *zerodrop.TimeoutError
if errors.As(err, &timeoutErr) {
	// No email arrived — check your app is sending correctly
}

if errors.Is(err, zerodrop.ErrUnauthorized) {
	// Invalid API key
}

Free vs Workspace

Free Workspace
Inbox generation
OTP auto-extraction
Magic link extraction
Email filtering
SSE delivery
Email retention 30 min Extended
Custom domains
API key

Get a Workspace at zerodrop.dev

License

MIT

Documentation

Overview

Package zerodrop provides disposable email inboxes for testing auth flows in CI pipelines. OTPs and magic links are auto-extracted at Cloudflare's edge — no regex, no Docker, no signup.

Basic usage:

client := zerodrop.New()
inbox := client.GenerateInbox()
email, err := client.WaitForLatest(ctx, inbox, nil)
fmt.Println(email.OTP)       // "847291" — auto-extracted
fmt.Println(email.MagicLink) // "https://..." — auto-extracted
Example
package main

import (
	"context"
	"fmt"
	"time"

	zerodrop "github.com/zerodrop-dev/zerodrop-go"
)

func main() {
	client := zerodrop.New()
	inbox := client.GenerateInbox()

	// Trigger your app's email flow with the inbox address...

	ctx := context.Background()
	email, err := client.WaitForLatest(ctx, inbox, &zerodrop.Options{
		Timeout: 15 * time.Second,
	})
	if err != nil {
		fmt.Println("no email:", err)
		return
	}

	fmt.Println(email.OTP)       // auto-extracted
	fmt.Println(email.MagicLink) // auto-extracted
}
Example (Filter)
package main

import (
	"context"
	"time"

	zerodrop "github.com/zerodrop-dev/zerodrop-go"
)

func main() {
	client := zerodrop.New()
	inbox := client.GenerateInbox()

	ctx := context.Background()
	email, _ := client.WaitForLatest(ctx, inbox, &zerodrop.Options{
		Timeout: 15 * time.Second,
		Filter: &zerodrop.Filter{
			From:   "noreply@yourapp.com",
			HasOTP: zerodrop.Bool(true),
		},
	})

	_ = email
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrUnauthorized = fmt.Errorf("zerodrop: invalid or missing API key")

ErrUnauthorized is returned when an invalid API key is provided.

Functions

func Bool

func Bool(v bool) *bool

Bool is a helper for setting Filter.HasOTP / Filter.HasMagicLink.

filter := &zerodrop.Filter{HasOTP: zerodrop.Bool(true)}

Types

type Client

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

Client is the ZeroDrop API client.

func New

func New(opts ...ClientOption) *Client

New creates a ZeroDrop client. With no options it runs in free sandbox mode — shared domain, 30-minute TTL, no signup required.

func (*Client) FetchLatest

func (c *Client) FetchLatest(ctx context.Context, inbox string, filter *Filter) (*Email, error)

FetchLatest returns the newest email matching the filter, or nil if the inbox is empty or nothing matches.

func (*Client) GenerateInbox

func (c *Client) GenerateInbox() string

GenerateInbox returns a ready-to-use email address instantly. No network request is made.

func (*Client) WaitForLatest

func (c *Client) WaitForLatest(ctx context.Context, inbox string, opts *Options) (*Email, error)

WaitForLatest blocks until an email matching the filter arrives, using SSE for sub-second delivery with a polling fallback. It returns a TimeoutError if nothing arrives within the timeout.

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client.

func WithAPIKey

func WithAPIKey(key string) ClientOption

WithAPIKey sets a Workspace API key. Omit for free sandbox mode.

func WithBaseURL

func WithBaseURL(url string) ClientOption

WithBaseURL points the client at a self-hosted instance.

func WithHTTPClient

func WithHTTPClient(h *http.Client) ClientOption

WithHTTPClient sets a custom *http.Client.

type Email

type Email struct {
	ID         string    `json:"id"`
	From       string    `json:"from"`
	To         string    `json:"to"`
	Subject    string    `json:"subject"`
	Body       string    `json:"body"`
	RawBody    string    `json:"rawBody"`
	ReceivedAt time.Time `json:"receivedAt"`
	// OTP is the auto-extracted 4-8 digit verification code.
	// Empty string if not detected.
	OTP string `json:"otp"`
	// MagicLink is the auto-extracted verification/reset URL.
	// Empty string if not detected.
	MagicLink string `json:"magicLink"`
}

Email represents a caught email with auto-extracted fields.

type Filter

type Filter struct {
	// From matches the sender address.
	From string
	// Subject matches the subject line.
	Subject string
	// Body matches the email body.
	Body string
	// HasOTP, when non-nil, requires (or excludes) an extracted OTP.
	HasOTP *bool
	// HasMagicLink, when non-nil, requires (or excludes) a magic link.
	HasMagicLink *bool
}

Filter narrows which email WaitForLatest and FetchLatest return. All string matches are case-insensitive partial matches.

type NetworkError

type NetworkError struct {
	Err error
}

NetworkError wraps transport-level failures.

func (*NetworkError) Error

func (e *NetworkError) Error() string

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

type Options

type Options struct {
	// Timeout is the total time to wait. Defaults to 10s.
	Timeout time.Duration
	// PollInterval is the delay between polls. Defaults to 2s.
	PollInterval time.Duration
	// DisableSSE forces polling mode. By default SSE is used
	// for sub-second delivery with automatic polling fallback.
	DisableSSE bool
	// Filter narrows which email is returned.
	Filter *Filter
}

Options configures WaitForLatest.

type TimeoutError

type TimeoutError struct {
	Inbox   string
	Timeout time.Duration
}

TimeoutError is returned when no email arrives within the timeout.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

Jump to

Keyboard shortcuts

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