Wiring an email alias API for custom Go applications requires parsing an explicit JSON contract, avoiding non-standard headers, and separating per-minute burst rate limits from daily account creation ceilings. If your backend service creates addresses on demand to isolate third-party notifications or trace user signups, integrating with Emcognito's REST API allows you to automate alias lifecycle management directly from your codebase.
Most email forwarding platforms operate behind consumer browser extensions or bundled desktop vaults. When you build automated backend infrastructure, you need predictable HTTP semantics, clean type unmarshaling, and resilient backoff loops. This guide walks through implementing a production-grade Go client for Emcognito's v1 API, parsing the exact response envelope, and handling edge cases across burst and daily limits.
What the v1 API Gives a Go Application
The Emcognito v1 developer surface is intentionally minimal. It exposes two endpoints rooted at https://api.emcognito.com/v1 (there is no /api/v1 prefix):
POST /v1/aliases: Creates a new forwarding alias.GET /v1/aliases: Lists account aliases with cursor pagination.
Authentication relies on a single HTTP header: Authorization: Bearer <key>. Emcognito API keys consist of the literal prefix emk_ followed by 43 URL-safe characters. There is no environment split: keys do not use separate staging prefixes, so test setups hit the real production endpoints.
When creating an address via POST /v1/aliases, the server returns an HTTP 200 OK status code rather than a 201 Created. As defined in RFC 9110: HTTP Semantics, a 200 status indicates that the request succeeded and the payload contains the representation of the result. Your client must check for 200 rather than 201.
The response does not use top-level fields for the address or identifier, nor does it wrap payloads in a generic { "status": "success", "data": ... } envelope. Instead, the payload contains a single top-level object key named "alias":
{
"alias": {
"id": "8f93a1c2d4",
"address": "k9x2m4p7q@emcognito.com",
"status": "active",
"created_at": 1729425600,
"forward_count": 0,
"label": "Stripe Webhooks",
"note": "Production payment events",
"source": "billing-service",
"category": "infrastructure",
"single_use": false,
"expires_at": 0
}
}
The developer API does not expose endpoints for updating or removing resources. There is no PATCH /v1/aliases, PUT, or DELETE /v1/aliases/{id} route. Suspending an alias is handled manually with a single click in the user dashboard ("Pause alias"), and permanent deletion requires a dashboard confirmation prompt. Your Go service should treat the API strictly as an ingestion and listing pipeline, persisting the returned identifiers for correlation with local database models.
Your API account limits depend on your subscription plan. On Emcognito Plus, programmatic creation is capped at 50 aliases per day. On Emcognito Pro, the ceiling increases to 200 aliases per day.
Designing the Go Client: Types That Match the Wire Format
Idiomatic Go avoids reflection overhead and unmarshaling bugs by aligning internal structs directly with external wire types. Following the standard layout recommendations in Effective Go, model the domain types, input parameters, and response wrappers cleanly.
According to the standard Go standard library encoding/json documentation, untyped null values can introduce parsing quirks if structs expect strings, but because Emcognito returns empty strings for omitted fields, plain string struct fields unmarshal deterministically without pointer indirection.
package emcognitoimport ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "time" )
// Alias represents a durable forwarding address returned by the v1 API. type Alias struct { ID string
json:"id"Address stringjson:"address"Status stringjson:"status"CreatedAt int64json:"created_at"ForwardCount intjson:"forward_count"Label stringjson:"label"Note stringjson:"note"Source stringjson:"source"Category stringjson:"category"SingleUse booljson:"single_use"ExpiresAt int64json:"expires_at"}type aliasEnvelope struct { Alias Alias
json:"alias"}type errorEnvelope struct { Message string
json:"message"}// CreateAliasParams contains optional parameters for programmatic alias creation. type CreateAliasParams struct { Label string
json:"label,omitempty"Note stringjson:"note,omitempty"Source stringjson:"source,omitempty"Category stringjson:"category,omitempty"SingleUse booljson:"single_use,omitempty"ExpiresAt int64json:"expires_at,omitempty"}// Client wraps an HTTP client configured for the Emcognito API. type Client struct { apiKey string baseURL string httpClient *http.Client }
func NewClient(apiKey string) *Client { return &Client{ apiKey: apiKey, baseURL: "https://api.emcognito.com/v1", httpClient: &http.Client{Timeout: 10 * time.Second}, } }
Next, implement the dispatch method for programmatic alias creation. The function sends a JSON payload to POST /v1/aliases, sets the required Bearer authorization header, and validates the HTTP status:
func (c *Client) CreateAlias(ctx context.Context, params CreateAliasParams) (*Alias, error) { bodyBytes, err := json.Marshal(params) if err != nil { return nil, fmt.Errorf("marshal create params: %w", err) }req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/aliases", bytes.NewReader(bodyBytes)) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.apiKey) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("execute request: %w", err) } defer resp.Body.Close() respBytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response body: %w", err) } if resp.StatusCode == http.StatusOK { var env aliasEnvelope if err := json.Unmarshal(respBytes, &env); err != nil { return nil, fmt.Errorf("unmarshal 200 payload: %w", err) } return &env.Alias, nil } // Surface specific API error messages var errEnv errorEnvelope _ = json.Unmarshal(respBytes, &errEnv) msg := errEnv.Message if msg == "" { msg = string(respBytes) } return nil, &APIError{ StatusCode: resp.StatusCode, Message: msg, Header: resp.Header, }}
type APIError struct { StatusCode int Message string Header http.Header }
func (e *APIError) Error() string { return fmt.Sprintf("emcognito api error (%d): %s", e.StatusCode, e.Message) }
Handling Both 429s: One Sends Retry-After, One Does Not
The HTTP 429 Too Many Requests status code is specified in RFC 6585: Additional HTTP Status Codes to indicate that the client has sent too many requests in a given time window. In the Emcognito API, you will encounter two fundamentally distinct 429 responses that require different mitigation strategies in your Go service.
POST /v1/aliases returns HTTP 200 with the new alias under "alias". The burst limit is 60 requests a minute per key; its 429 carries a Retry-After header giving the seconds until the window resets. The daily alias-creation cap (50 on Plus, 200 on Pro) resets at 00:00 UTC and its 429 carries no Retry-After. Emcognito sends no X-RateLimit-* headers. Source: Rfc Editor source.
The difference stems from architectural layers:
- The per-key burst rate limiter: When your application sends more than 60 calls in a sixty-second rolling window, edge infrastructure throttles the connection immediately. On September 20, 2026, live verification showed the 61st call within a minute returning a 429 status alongside a
retry-after: 60header. The body is a simple{"message": "Rate limit exceeded"}payload. (A related rate-limit 503 response can also occur under high edge congestion, returning a fixedRetry-After: 30header.) - The daily alias-creation cap: When your Go service exhausts its daily creation allowance (50 on Plus or 200 on Pro), internal application logic rejects the insert. Because this rejection is an application-level constraint rather than an edge throttle, the response contains no
Retry-Afterheader. It returns a bare{"message": "Daily alias creation limit reached"}.
If your Go client blindly treats every 429 as a burst event and retries on a short exponential curve, a daily-cap error will cause your application to spin uselessly until the quota resets. Your client should parse the error, extract the Retry-After header if present, and pause until the start of the next UTC day if the header is absent.
// HandleRateLimit pauses or errors based on which 429 limit was breached. func HandleRateLimit(err error) error { var apiErr *APIError if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusTooManyRequests { return err }retryHeader := apiErr.Header.Get("Retry-After") if retryHeader != "" { seconds, parseErr := strconv.Atoi(retryHeader) if parseErr == nil && seconds > 0 { // Burst limit hit: wait the instructed duration time.Sleep(time.Duration(seconds) * time.Second) return nil } } // No Retry-After header: daily creation ceiling reached. now := time.Now().UTC() nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC) durationUntilReset := nextMidnight.Sub(now) return fmt.Errorf("daily creation cap reached; resets in %s at 00:00 UTC: %w", durationUntilReset.Round(time.Minute), err)
}
Pagination and Listing Aliases From Go
Because Emcognito does not provide an endpoint to look up an alias by address or arbitrary search string, retrieving existing aliases requires listing them via GET /v1/aliases. The endpoint returns items in batches using cursor-based pagination.
The response contains an array of aliases and pagination indicators:
{
"aliases": [
{
"id": "8f93a1c2d4",
"address": "k9x2m4p7q@emcognito.com",
"status": "active",
"created_at": 1729425600,
"forward_count": 14,
"label": "Newsletter Delivery",
"note": "",
"source": "backend-sync",
"category": "marketing",
"single_use": false,
"expires_at": 0
}
],
"next_cursor": "cursor_9a8b7c6d5e",
"has_more": true
}
To list all aliases across your account, initialize an iterator loop that submits the cursor via query parameters until the response returns an empty next_cursor or indicates no additional records remain. A common pitfall in Go integrations is treating a missing or empty cursor string as an unmarshaling error instead of the clean completion of the list stream.
type ListAliasesResponse struct { Aliases []Alias `json:"aliases"` NextCursor string `json:"next_cursor"` HasMore bool `json:"has_more"` }func (c *Client) ListAliases(ctx context.Context, cursor string) (*ListAliasesResponse, error) { reqURL := c.baseURL + "/aliases" if cursor != "" { reqURL += "?cursor=" + cursor }
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil) if err != nil { return nil, fmt.Errorf("build list request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.apiKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("execute list request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected list status: %d", resp.StatusCode) } var listResp ListAliasesResponse if err := json.NewDecoder(resp.Body).Decode(&listResp); err != nil { return nil, fmt.Errorf("decode list response: %w", err) } return &listResp, nil
}
When running an integration to reconcile state, query the listing endpoint before minting new addresses. Persisting the returned id and address fields inside your own relational database allows you to map specific transactional workloads to unique addresses without risking duplicate creations.
Where the Daily Cap Bites: Batch Jobs and Backfills
If you are migrating existing infrastructure or backfilling customer accounts, you will immediately run into the daily alias-creation cap. While account storage allows unlimited total aliases, new creation actions are metered by the calendar day.
Consider a backfill job designed to assign unique forwarding addresses to 600 existing company suppliers. On Emcognito Plus, which permits 50 API creations per day, that backfill requires 12 continuous days to complete.
Because the daily creation counter resets promptly at 00:00 UTC, programmatic batch scripts must be designed around that cutover boundary. Avoid executing tight retry loops upon encountering creation failures. Instead, write persistent workers that store job progress in a transactional table:
// SyncSuppliers demonstrates state-driven, daily-capped backfilling func SyncSuppliers(ctx context.Context, client *Client, suppliers []SupplierRecord) error { for _, s := range suppliers { if s.AssignedAlias != "" { continue // Already provisioned }alias, err := client.CreateAlias(ctx, CreateAliasParams{ Label: s.Name, Source: "supplier-backfill", Category: "procurement", }) if err != nil { var apiErr *APIError if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusTooManyRequests { if apiErr.Header.Get("Retry-After") == "" { // Cap reached. Exit early and let cron resume after 00:00 UTC. return fmt.Errorf("daily creation cap reached during supplier %s: %w", s.ID, err) } // Burst rate limit: wait and continue time.Sleep(60 * time.Second) continue } return fmt.Errorf("failed creating alias for supplier %s: %w", s.ID, err) } // Persist the alias immediately to keep the batch run idempotent if err := saveSupplierAlias(ctx, s.ID, alias.Address); err != nil { return fmt.Errorf("persist supplier alias: %w", err) } } return nil}
type SupplierRecord struct { ID string Name string AssignedAlias string }
func saveSupplierAlias(ctx context.Context, supplierID, address string) error { // Persist to local database return nil }
Because retrying a failed or timed-out POST request could create an extra alias if the server processed the call before the network dropped, your Go workers must check local records before issuing creation calls.
Choosing Plus or Pro for a Go Integration
When selecting a plan for an email alias API for custom Go applications, compare your daily creation throughput rather than your total mailbox volume.
Review the breakdown of available plans on the Emcognito pricing page:
- Emcognito Free: Unlimited total aliases, 100 forwarded messages per month, no credit card required. Free accounts can create aliases via the web dashboard (capped at 200/day), but do not have access to the developer API. Free forwards carry a first-party alias status bar, not a sponsor card. Reply securely in the delivery log is included on every Emcognito plan until November 3, 2026, and on Plus and Pro from then on. Replying from your own mail app is a Plus and Pro feature.
- Emcognito Plus: The entry paid tier includes 2,500 forwards per month, access to compose new outbound mail from any alias, and developer API access capped at 50 alias creations per day. Source: Emcognito source.
- Emcognito Pro: The higher-tier developer option includes 15,000 forwards per month, higher compose caps, and developer API access capped at 200 alias creations per day, representing the standard choice for automated backend pipelines. Pro yearly includes three months free.
For automated services that regularly generate accounts, choose your tier based on how many addresses you expect to create in a given cycle.
What the API Does Not Do, and What to Build Around It
Understanding an external service's operational boundaries prevents architectural dead ends early in implementation.
Emcognito aliases use the shared emcognito.com domain. Custom domains are not available today; a custom subdomain at handle.emcognito.com is available on Pro. If an application architecture strictly mandates configuring custom routing records, an alternative service is required.
Second, Emcognito is closed source and runs only as a hosted service; if open source or self-hosting is a requirement, addy.io is the better fit.
Third, Emcognito is an email forwarding platform, not a temporary burner inbox. The addresses you create are permanent routing endpoints designed to deliver mail securely to your destination mailbox. Emcognito forwards mail over TLS-encrypted transport and does not read message contents or retain them after delivery, apart from a brief hold on mail that arrives over your monthly forward cap, but it is not end-to-end encrypted. For content confidentiality, pair it with an encrypted mailbox such as Proton Mail or Tuta.
Fourth, lifecycle operations remain outside the v1 API. You cannot pause, unpause, or delete an alias through code. Pausing is performed via the dashboard button ("Pause alias"), which severs inbound delivery instantly. Deleting an address requires interactive dashboard confirmation.
Finally, outbound mail composition behaves differently from API creation. While your Go code mints addresses via HTTP, actual outbound message composition occurs via web interfaces or supported client mail applications on Plus and Pro. On Plus, outbound sending is capped at 20 messages per day, ramping over the first 7 days; on Pro, it starts at 100 per day. Every outbound send draws against your monthly account forwarding limit.
Testing a Go Integration Without Burning Your Daily Cap
Because Emcognito does not offer sandbox environments or separate test key prefixes, every successful call to POST /v1/aliases consumes your real production quota. In a continuous integration pipeline, a suite of integration tests running on every pull request can quickly exhaust your daily allocation.
To avoid hitting these limits in continuous integration, decouple your business logic using Go interfaces. Define an interface that models alias creation, and provide an in-memory stub or mock for standard unit tests:
type AliasCreator interface {
CreateAlias(ctx context.Context, params CreateAliasParams) (*Alias, error)
}
For end-to-end HTTP validation, use Go's standard net/http/httptest package to mock the Emcognito v1 endpoint locally. Your mock server should return the exact JSON payload, check for the Bearer emk_... header, and simulate both 429 variants (one with Retry-After and one without) to verify that your service handles rate limiting gracefully without ever making live network calls to the production API.
Frequently Asked Questions
How do I differentiate between burst and daily limit errors in Go?
Inspect the HTTP response headers when receiving an HTTP 429 status code. The edge burst rate limiter (60 requests per minute) returns a Retry-After header stating the number of seconds to wait. The daily creation limit (50 per day on Plus, 200 per day on Pro) is returned from application code and carries no Retry-After header, resetting at 00:00 UTC.
Can my Go service pause or delete an alias using the API?
No. The v1 API only supports listing (GET /v1/aliases) and creating (POST /v1/aliases). Suspending an alias is done manually via the dashboard with the "Pause alias" button, and deleting an alias requires confirmation in the web interface.
Does Emcognito provide a sandbox environment or test API keys?
No. There is no separate test key prefix or sandbox environment. All keys use the standard emk_ prefix followed by 43 URL-safe characters and interact with live production infrastructure. Use Go's net/http/httptest package to test client interactions locally without consuming your daily quota.
What happens when my Go service exceeds its monthly forward cap?
Hitting your monthly forward limit does not prevent your Go service from minting new aliases via the API, nor does it block dashboard actions. Inbound mail received after reaching your forward cap is placed on a brief hold rather than bounced immediately.
If you are ready to automate your forwarding infrastructure directly from your Go backend, inspect the developer alias API documentation to issue your first API key.