API reference
Compatibility with Resend
Understand supported operations and the differences to account for when migrating.
Operation coverage
Opensend’s API contract is compared with a fixed snapshot of Resend’s OpenAPI specification that lists 113 operations. Opensend serves all of them: 97 as served and 16 as partial. “Served” means no resource-specific wire gap was identified after the common differences below. It does not guarantee equivalence with the live Resend service.
| Status | Operations | Meaning |
|---|---|---|
| Served | 97 | Implemented with the shared self-hosted differences below |
| Partial | 16 | Implemented with the resource-specific limitations below |
The partial operations are the six domain operations, automation create and update, suppression list and retrieve, email metrics, template retrieval, contact import creation, webhook attempt listing, OAuth grant listing, and usage.
This reference has one page for each of the 113 public operations. Opensend’s contract has two more operations, which serve the SMTP gateway and are not for API clients. The counts compare a fixed Resend snapshot; they are not a claim to implement every feature added to Resend since that snapshot.
Read the full compatibility audit for the operation-by-operation comparison and its source evidence.
Common differences
Use your installation’s API origin, such as https://api.example.com, and an os_ API key or OAuth token. IDs are opaque Convex IDs rather than UUIDs. Full-access credentials are required except for email sending. OAuth grants use the same authentication, errors, rate limits, logging, and pagination as the other public resources. SMTP accepts API keys only.
Lists default to 20 rows and accept limits of 1–100. Cursors are resource IDs. Most lists accept either after or before; webhook event and attempt lists accept only after. Opensend always bounds list results, including endpoints for which Resend may return every row by default.
All keys share a limit of 10 requests per second per team. The default request limit is 1 MiB; single-email and SMTP email submissions accept up to 42 MiB of JSON, subject to your proxy’s limits. POST idempotency retains responses for 24 hours. JSON requests match exact body bytes; multipart imports match decoded form fields and CSV contents. API introduction explains errors and retry behavior.
Your AWS account, SES sandbox status, IAM policy, and installation capacity determine delivery limits. Sending requires a verified sender and ready SES tenancy. Self-hosted retention also applies: request logs and sent events last 30 days, webhook history lasts 90 days, and completed or failed contact imports last seven days.
Emails and metrics
Single sends accept base64 attachments or public HTTPS attachment URLs. Private addresses and HTTP URLs are rejected. There must be at least one to recipient and no more than 50 total recipients. Body content and headers share a 900,000-byte UTF-8 limit; encoded attachments have a 40 MiB limit. Batch sending accepts 1–100 messages without attachments, with strict or permissive validation. Transactional topic_id is supported; transactional sends do not automatically add unsubscribe headers. Render React content in your SDK before sending HTML.
The one partial operation here is retrieve metrics. It supports 18 metrics, period/domain dimensions, domain filters, and timezone-aware grouping. Aggregates follow email creation cohorts at 15-minute precision, within 31 domain-period spans and one year. Email/broadcast dimensions and filters, repeat opened/clicked counts, and unsubscribe metrics return 422 because those historical dimensions and events are not retained. Rates are percentages recomputed from counts.
Sent and received attachment downloads expire after one hour. Received mail has a 40 MiB raw MIME limit and 30-day retention. A received message that cannot be parsed retains its raw MIME with empty decoded data.
Domains
All six domain operations are partial because domains follow the AWS SES lifecycle. Creation and deletion provision AWS resources asynchronously. Creation accepts TLS and sending/receiving capabilities, but regional SES readiness still applies. Only provisioned domains can be updated; name and region cannot be changed through the update endpoint.
Domain statuses are pending, verified, partially_verified, or failed. DNS statuses are pending, verified, or temporary_failure. DNS records include DMARC and omit TrackingCAA/CAA records. Return-path MX records map to SPF. Verification is limited to once per domain per 10 seconds. Receiving needs an SES region that supports it. Tracking begins after CNAME verification and uses HTTP redirects; HTTPS needs operator-managed certificates and routing.
Templates
Template retrieval is partial for historical metadata. Variables have stable IDs and independent creation/update timestamps, and current_version_id changes when the draft changes. Older templates without that metadata fall back to template timestamps, so past variable lifetimes cannot be reconstructed.
Templates keep one mutable draft and one published snapshot. There is no historical version API. Explicit variables support string and number values, up to 50 definitions; HTML and text are each limited to 256 KiB. Reserved contact and unsubscribe variables cannot be redefined.
Contacts and imports
Contact import creation is partial. Upload multipart CSV with optional column mapping, conflict handling, segments, and topics. The default conflict behavior is upsert. Each request is limited to 1 MiB, 500 rows, 500 KB of parsed data, and 100 references each for segments and topics. Only string and number custom properties are supported. Imported contact changes do not emit contact webhooks.
Contacts share dashboard validation: up to 100 properties and 100 topics per team. Individual null property values clear the value; a whole properties: null is rejected. The four deprecated audience operations alias segment records. Prefer segments for new integrations.
Automations
Creation and update are partial. All eight step types are supported: trigger, send email, delay, wait for event, condition, contact update, contact delete, and add to segment. Definitions allow one trigger plus 100 execution steps, 64 KiB, and 12 branch levels. Cycles, joins, and arbitrary parallel branches are rejected.
Nested mixed condition groups, null comparisons, send subject overrides, structured template variables, and wait filter_rule return 422. A wait needs a timeout of at most 30 days before activation. Disable an automation in a separate request before changing its graph; graph updates require both steps and connections. Status is enabled or disabled. Stopping disables new starts while existing run snapshots finish.
Webhooks
GET /webhooks/{webhook_id} returns signing_secret, as creation and rotation do; the webhook list omits it. Rotation signs with the new and immediately preceding secret for 24 hours. Another rotation replaces the preceding key.
Attempt listing is partial for historical data. Installations upgraded from a version that kept only the latest result cannot recover earlier individual attempts. Responses are capped at 4 KiB and history expires after 90 days. Replays add one attempt to the original event history with the same svix-id and preserve its automatic retry schedule.
Suppressions
Listing and retrieval are partial for historical source_id data. New SES bounce/complaint entries retain the source email ID. Older automatic entries and manual suppressions return null. Addresses are normalized, and adding an existing address does not emit another suppression.added event. Removing a bounce or complaint also schedules tenant-scoped SES cleanup.
OAuth grants
Grant listing is partial for historical revocation data. Grants cover the installation, so resource is null. Historical or externally invalidated grants have no recorded revocation timestamp or reason. New API revocations record both and immediately invalidate all tokens for that grant. Listing uses the standard paginated envelope; revocation returns an oauth_grant object.
Other served resources
Broadcast recipient and clicked-link reports, API-key renaming, custom events, logs, contact properties, segments, and topics are available. Served operations still use Opensend’s limits and retention. Broadcast content is limited to 256 KiB each for HTML/text, preferences are checked again before delivery, and cancellation only works before recipient resolution starts. Custom events reserve the opensend: prefix, accept payloads up to 64 KiB, and are not forwarded to webhooks.
Email sharing, domain claims, and usage
Share email creates a public link for a sent or received email; the default and maximum lifetime is 48 hours. Domain claims transfer a domain that another team on the same installation registered, after a TXT ownership proof; pending claims expire after seven days, and the transferred domain gets fresh DKIM records.
Retrieve usage is partial. It returns every field Resend’s usage object has, but there is no billing plan: monthly email, contact, segment, broadcast, domain, and automation-run limits are null, AI credits are always zero, and the daily email limit is the installation’s SES quota shared by all teams.
A method in the SDK does not imply that Opensend implements its endpoint. Test the operations you need against your own installation and check the resource-specific notes before migrating.