https://api.reallyquickemails.com
Each client = one project. The domain, reply domain, limits and stats are per project, so each of your clients is an independent RQE project with its own API key. These endpoints create, configure and list those projects inside your partner organization.
Authentication
Partner endpoints use a dedicated partner key (distinct from project API keys):401. A projectId that doesn’t belong to your organization returns 404, never 403: from the outside, someone else’s project is indistinguishable from one that doesn’t exist.
Full onboarding in one call
This is the recommended path. A singlePOST creates the project, registers the sending domain with the complete DNS pack, and creates the sender on that domain.
1
Create the whole client
POST /v1/partner/projects with name, domain and sender. You get back project_id, slug, the client’s api_key, the DNS records and the public verification link.2
Forward the verification link
domain.verification_url is a public page that shows the records and verifies with one button. Your end client opens it without an RQE account: it’s the only thing you need to send them.3
Wait for the domain.verified webhook
Don’t poll. RQE re-verifies on its own and notifies you by webhook once DNS propagates. See Webhooks.
POST /v1/partner/projects
Creates a client project inside your partner organization. Theslug and api_key are generated automatically.
The only required field is name. domain and sender are optional and new: if you pass them, onboarding completes in this same call. If you don’t, the endpoint responds exactly as before.
Compatibility: if you already integrated the previous flow, you don’t need to change anything. The call with just
{ "name": "..." } is still valid and its response did not change. The new fields add to it, they don’t replace it.Request Body
Minimal example — project only
The original flow, unchanged. It creates the project and returns its API key; you configure the domain and the sender afterwards with the endpoints below.- cURL
- Python
201 Created
Example — full onboarding
Withdomain and sender, the same call registers the domain and creates the sender. The response adds the domain and sender keys to the three above.
- cURL
- Python
201 Created
sender shape — with id, sender_source, can_send and created_at — is the same in all four places a sender shows up: project creation, domain registration, sender creation and the project detail.
The DNS pack
domain.records carries the full pack, not just DKIM. It’s derived entirely in one call.
Per-record
status: not_set, propagating, mismatch or verified. The priority field appears only on the reply MX; the send one carries its priority inside value. Details for each record in Domains.
The
records name belongs only to the endpoints under /v1/partner. The ones under /domains/*, called with the client’s API key, still return the array as dns_records. Nothing changed there.warnings[]. And if the project already had one of the two, that step is skipped and reported in skipped[] — see Skipped steps.
Idempotency
Withexternal_key, retrying the call returns the same project instead of duplicating it. It’s the field that ties the customer in your system to the RQE project: always use it.
If the external_key belongs to a disabled client, the response is 409 PROJECT_DISABLED with the project_id in the body, so you know which one to reactivate.
Partial onboarding
This only applies if you passeddomain and sender. If the project is created but domain registration fails, the response comes back with partial: true and the detail in errors[]. The project and its api_key are valid: retry the domain with POST /v1/partner/projects/{projectId}/domains.
Every entry in errors[] and warnings[] is { step, error_code, error }. In errors[], step only takes the value domain: the sender is created inside domain registration, so there’s no separate step of its own that can fail. In warnings[], step takes reply_domain, tracking_domain or verification_link — the best-effort steps that don’t sink the onboarding.
We never drop a step silently. If you see neither
partial nor warnings in the response, everything went through.GET /v1/partner/projects
Lists the client projects in your organization.- cURL
200 OK
GET /v1/partner/projects/{projectId}
Detail of one client project. The response has four blocks:project, domains, senders and health.
This is the call to build your own client panel with: it says at a glance who can send well and who can’t.
- cURL
200 OK
The health block
It’s per project, not the shape of GET /domains/health: there’s no summary and no data here.
PATCH /v1/partner/projects/{projectId}
Updates the client project’s data. Only the fields you send are touched.Request Body
A
PATCH with no recognized field returns 400 EMPTY_PATCH.
- cURL
200 OK — the updated project, wrapped in project.
PATCH works on a disabled client, on purpose. That way you can fix its webhook_url before reactivating it, instead of bringing it back broken. The slug, by contrast, is stable for life: tracking links in already-sent emails depend on it, and changing name does not move it.DELETE /v1/partner/projects/{projectId}
Disables a client project (soft-disable). It is not deleted: links in already-sent emails depend on the project still existing. It stops sending and gets adisabled_at.
- cURL
200 OK
POST /v1/partner/projects/{projectId}/reactivate
Undoes the soft-disable: clearsdisabled_at and the project can send again. It’s idempotent — reactivating one that is already active returns 200, never 409.
- cURL
200 OK — it carries the full project, same shape as PATCH.
Client domains
The same domains as/domains/*, but with your partner key and the client’s projectId in the path: you don’t need each client’s api_key on hand.
POST /v1/partner/projects/{projectId}/domains
Registers the client’s sending domain, derives the full DNS pack and creates the public verification link.- cURL
201 Created — or 200 OK with reused: true if the domain was already registered. The pack comes at the top level, unwrapped.
Domain registration is idempotent. Repeating it on an already-registered domain returns
200 with reused: true and its existing records, instead of failing. Retrying an onboarding that got half-way is safe.Skipped steps
skipped[] is a sibling of warnings[] and says what wasn’t done and why. The difference matters: warnings[] is a step that was attempted and failed; skipped[] is a step that was deliberately not attempted, because doing it would have broken something already working.
skipped[] is only populated by this POST. GET /v1/partner/projects/{projectId}/domains/{domain} always returns skipped: [].
GET /v1/partner/projects/{projectId}/domains
Lists the client’s domains with their status.200 OK
GET /v1/partner/projects/{projectId}/domains/{domain}
Record-by-record status, with no external lookups. This is what you show in your panel while the client publishes DNS.200 OK
skipped always comes back empty here: only the onboarding POST fills it. A domain that doesn’t exist in the project returns 404 DOMAIN_NOT_FOUND.
POST /v1/partner/projects/{projectId}/domains/{domain}/verify
Forces verification of the sending domain: queries SES, updates the stored record status and answers whether the domain can send. Call it when the client tells you “it’s configured”. It covers identity, DKIM and MAIL FROM. It does not verify the reply domain or the tracking domain.200 OK
reply_domain and tracking_domain, but read-only: they are each one’s current state, not something this call verified.
No need to poll. Domains in
pending re-verify on their own — often on day one, more spaced out later — and when they turn verified RQE emits the domain.verified webhook to the project’s webhook_url. verify is for an immediate answer, not a replacement for the webhook.Client senders
GET /v1/partner/projects/{projectId}/senders
Lists the client’s senders and how each one was verified.200 OK
sender_source says where the sender came from: manual (created on a registered domain), magic_link (verified by email, no DKIM), auto-own-email or admin. A sender with domain_authenticated: false and real volume is the pattern that breaks deliverability.
POST /v1/partner/projects/{projectId}/senders
Creates a sender. By default it requires a verified domain: that’s the rule that keeps a client from being born sending unauthenticated.name and email are accepted loose in the body or nested under sender: both forms work.
- cURL
201 Created
path says which route the sender took: domain (on the verified domain) or magic_link. On the magic link route the response adds a warning noting that this sender has no DKIM.
If the address’s domain isn’t verified and you did not send the flag:
Response 422 Unprocessable Entity
verification_status comes back null when the domain isn’t even registered.
With allow_unauthenticated: true a verification email is sent to the address and the sender is created with sender_source: "magic_link", domain_authenticated: false and email_verified: false until the recipient clicks.
The verified-domain requirement belongs only to this endpoint.
POST /domains/verify-email and the rest of /domains/*, called with the client’s API key, work exactly as before: they don’t ask for the flag and don’t return DOMAIN_NOT_VERIFIED.POST /v1/partner/projects/{projectId}/api-key/rotate
Rotates the client project’s API key.- cURL
200 OK
GET /v1/partner/projects/{projectId}/stats
The client’s sending metrics: deliveries, bounces, opens and clicks.GET /v1/partner/whoami
Data about the organization tied to your partner key, with apartner block carrying your project quota. It’s how you know how many clients you have left before you hit MAX_PROJECTS_REACHED.
200 OK
max_projects set to null means no limit. The count is your active clients, not counting your root project.
Before you send volume
Register the client’s domain before the first large send. This isn’t a hygiene recommendation: it’s the difference between reaching the inbox and not reaching it. A sender verified by email only (magic link) goes out signed with the platform’s shared domain. The result, measured on real clients:- Microsoft rejects with
5.7.515. It seesSPF=Pass, DKIM=Pass, DMARC=Failbecause nothing is aligned with theFromdomain, and it cuts off volume senders right there. - Gmail answers
550-5.7.1 "likely unsolicited mail". Without its own DKIM, the client’s reputation is mixed with everyone else sending unauthenticated. - No reply domain and no tracking domain. Both hang off the verified domain.
200, the email “sends”, and the problem surfaces weeks later in the bounce rate.
1
Create the client with their domain
POST /v1/partner/projects with domain and sender. The DNS pack comes back in the response.2
Forward the verification_url
A public page with the records and a verify button. It’s the only thing your end client needs to open, with no RQE account. If their DNS provider supports Domain Connect, publishing is one click.
3
Wait for domain.verified
It arrives by webhook. Only then enable volume sending for that client.
4
Watch their health
GET /v1/partner/projects/{projectId} returns health. A client in critical with can_send: true is burning reputation silently.Errors
Every error response has the same shape:code and error_code always carry the same value. error_code is the canonical one; code is there for compatibility with existing integrations. Some errors add extra flat fields to the body, so you never have to parse the text.
Error codes
PROJECT_DISABLED only shows up on creation, when you reuse the external_key of a disabled client. It carries the project_id so you know which one to call /reactivate on. A PATCH on a disabled client does work, on purpose: that way you can fix its webhook before bringing it back.Next
- Partner quickstart — the two integration models and when each one fits.
- Domains — the same management with the client’s API key.
- Webhooks —
domain.verified,sender.verifiedand email events. - Public API — sending with the client’s key.