Get started
Create a tenant to get your first API key, or load an existing tenant you already have a key for.
How this works
What's a "tenant"?
This platform is multi-tenant — one shared system, but many separate customers using it at once, each with their own domains, campaigns, and unsubscribe lists that are completely invisible to everyone else. A "tenant" is just the technical name for one of those customers. If you're a business signing up, you (or your whole company) are one tenant.
What's an API key for?
It's how the system knows a request is really coming from you. Think of it like a password, except it's long and random instead of something you'd type from memory, and it's meant to be pasted into scripts/tools rather than into a login form. Every request you make (except creating a tenant in the first place) needs to include your key in an X-API-Key header, or it gets rejected.
Why is it shown only once?
The server never stores your key in a form it could show back to you — it only stores a one-way scrambled version (a "hash"). That way, even if someone broke into the database, they couldn't read out usable keys. The tradeoff: if you lose your key, there's no "forgot password" recovery. You'd need to generate a new one (which itself requires already having a working key — see the API keys section).
Why is creating a tenant the only step that needs no key?
Every other action needs a key, but you can't have a key before your very first tenant exists — so this one step is deliberately open to anyone, and it hands you your tenantId and first key together, atomically, in the same response.
Why does it sometimes ask for an "invite code"?
Being the one unauthenticated route cuts both ways — anyone who finds this page could otherwise mint themselves a free tenant. Some deployments of this platform require a shared invite code to create a new tenant, as a cheap way to keep that door closed to strangers. It only appears if the one you're using actually has this turned on; if you never see it, this deployment doesn't require one.
Create a new tenant
This is a one-time bootstrap step. It mints your first API key in the same response — there's no other way to get one, and no way to look it up again afterward.
Load an existing tenant
Already have a tenant ID and API key? Load them here to use the rest of this console.
Sending domains
Register a domain with DKIM before sending from it. First registration on the platform wins — a domain someone else already verified can't be re-registered by a different tenant.
How this works
Why do I need to prove I own a domain before sending from it?
Without proof, anyone could send email pretending to be "you@yourcompany.com" — that's literally what most email spoofing/phishing is. Mailbox providers (Gmail, Outlook, etc.) look for cryptographic proof that a message really came from the domain it claims to, and refuse or spam-flag anything that can't prove it.
What is DKIM, actually?
DKIM (DomainKeys Identified Mail) is that proof. When you register a domain here, OCI Email Delivery generates a private signing key it keeps to itself, and a matching public key it publishes as a DNS record. Every email you send gets invisibly signed with the private key; the receiving mail server looks up the public key in DNS and checks the signature matches. If it does, the receiver knows the message genuinely passed through Email Delivery on your behalf and hasn't been tampered with in transit.
What's a CNAME record?
A CNAME record is just a DNS entry that says "this name is an alias for that name" — like a forwarding address, but for domain names instead of mail. Email Delivery generates one DKIM key and hands back its DNS name/value directly, so there's just one CNAME record to add here (some providers, including this platform's own AWS original, use 3 for signing-key rotation — Email Delivery's model is simpler).
Where do I add this record?
At your domain registrar or DNS host (GoDaddy, Namecheap, Hostinger, Cloudflare, Route 53 — wherever you manage this domain's DNS). Look for a "DNS management" or "DNS records" page, and add it exactly as shown, using the "CNAME" record type.
Why doesn't "Verify" work immediately after I add the record?
DNS changes take time to spread across the internet's many DNS servers, but in practice that's not the long pole here: confirmed by directly checking DNS the whole time during this platform's own testing, the CNAME was live and correct within minutes — DKIM verification itself still took about 50-55 minutes end to end, purely because that's how often Email Delivery's own verification job runs, not a DNS propagation delay. A "not verified yet" result right after adding the record is completely normal — wait a while and check again. (SPF, below, verifies faster in practice — around 20-30 minutes.)
What are the SPF and DMARC records it also recommends?
These are two more DNS-based, email-authentication standards, layered on top of DKIM as extra trust signals. SPF's absence doesn't block sending in practice, but DKIM's does — Email Delivery's send call hard-requires DKIM to be verified, confirmed by a real rejection when attempting to send before it was. SPF is a list of servers allowed to send mail for your domain. DMARC tells receivers what to do if a message fails both SPF and DKIM (starting at "just watch and report," never "reject," so it can never accidentally block your own legitimate mail). Both meaningfully improve inbox placement over time.
My mail is authenticated but still goes to spam — why?
Because authentication is the prerequisite for inbox placement, not a guarantee of it. A brand-new sending domain has no reputation, and mailbox providers put unknown senders in spam until real recipients engage (open, reply, mark "Not spam"). This is normal and expected — it's earned away over 2–4 weeks of small, steady sends to people who genuinely want the mail, not fixed by a settings change. Register your domain at Google Postmaster Tools right after verifying it here (one extra TXT record): it's Google's own free dashboard of your domain's reputation and spam rate, and its data only starts accruing from the day you register — so early registration means your warm-up progress is actually measurable.
What does "Unregister" actually do?
It only removes this platform's own record that your tenant owns this domain — it does not touch the real DKIM signing key, Sender, or DNS records over at Email Delivery or your DNS provider. That's deliberate: it means re-registering the same domain later (by you or, once unregistered, a different tenant) reuses the same already-verified DKIM CNAME instead of generating a new one and making you re-verify from scratch. It also means unregistering here is not enough to fully "clean up" a domain you're done with — remove the CNAME/SPF/DMARC records yourself at your DNS provider too if you want it fully disentangled from Rissolv Mail.
Register a domain
Registers with Email Delivery (DKIM) and returns the CNAME record (plus recommended SPF/DMARC records) to add at your DNS provider.
Your domains
Loaded live from the server (GET /tenants/{tenantId}/domains) -- this is every domain your tenant has registered, on any device.
Campaigns
Send to a JSON list of addresses, or upload a CSV file — both go through the identical pipeline.
How this works
What actually happens when you click "Send campaign"?
Several OCI services hand off to each other in sequence, each doing one specific job:
1. Object Storage — your subject/body/recipients get written as two files (content.json + recipients.csv). Writing the recipients file is what triggers everything below, the same mechanism whether you paste addresses or upload a CSV.
2. A Function (ingest_campaign) notices the new file, checks each address against your suppression list, and enqueues one message per recipient onto a queue — in chunks of 2,000 at a time for large campaigns, self-invoking to pick up where it left off.
3. OCI Queue — a buffer between "figure out who to email" and "actually send." It exists so a large campaign doesn't have to happen instantly and all at once; it decouples how fast recipients can be queued from how fast Email Delivery will let you actually send.
4. Connector Hub picks messages off that queue and invokes another Function (send_email_worker) per recipient, which builds the actual email (with your unsubscribe link, physical address footer, etc.) and hands it to Email Delivery to send.
5. Email Delivery delivers it. Unlike some email platforms, there's no real-time bounce/complaint push event here — a separate scheduled Function (bounce_poller) polls Email Delivery's suppression list every so often and reflects new bounces/complaints back into this platform's own database, which is what GET /campaigns/{id} and this console's "Campaign status" card actually read from.
The reason for this many moving pieces: at real scale (thousands+ recipients), you can't just loop through a list and send one-by-one in a single request — it would time out long before finishing. Splitting the work into small, independent, retryable steps is what lets this same pipeline handle a handful of recipients or many thousands without changing anything. If a single ingestion chunk ever runs long enough to hit the ingest Function's own timeout, a separate scheduled Function (campaign_sweeper) checks every hour for a campaign that's stopped making forward progress and automatically resumes it from where it left off — you shouldn't need to cancel and retry a large campaign just because its status briefly looks frozen.
What does "Opened"/"Clicked" actually measure?
Both are counted the same indirect way most email platforms do it: "Opened" means a tiny, invisible tracking image embedded in the email was fetched — but some mail providers (notably Gmail) pre-fetch that image automatically before anyone actually reads the message, so treat it as a rough signal, not a confirmed read. "Clicked" requires an actual link click and is the more reliable of the two. Neither appears at all until at least one recipient has triggered it.
What does "Cancel campaign" do, and is it instant?
It stops any recipient who hasn't been sent to yet — but not instantly: some number of sends already queued at the moment you cancel may still go out, since they're already past the point this platform can intercept them. Check status again right after cancelling to see the final, settled counts rather than assuming the numbers on screen the moment you click are final.
Why so many recipient checks (typo, disposable, role-based, unreachable)?
Each one catches a different real-world mistake, and each is either a hard rule or just a heads-up, never both confused for the other:
Invalid format (no @, no domain) — dropped outright, since it can never be delivered.
Unreachable domain (no mail server at all, e.g. a typo like gmial.com with no DNS mail records) — dropped, since it would just bounce.
Possible typo (a domain one letter off from gmail.com, yahoo.com, etc.) — still sent, just flagged, because it might genuinely be someone's real, differently-spelled domain.
Disposable address (Mailinator, 10-minute-mail, etc.) — still sent, just flagged, since the mailbox is usually real right now, just likely to be abandoned soon.
Role-based address (support@, admin@) — still sent, just flagged, since it's perfectly deliverable and sometimes exactly who you mean to reach — just statistically more likely to complain or ignore marketing mail than a named person.
Why do I have to enter a physical address and a "consent basis"?
Both are legal requirements for commercial email, not this platform's own preference. CAN-SPAM (the US law governing commercial email) requires every marketing email to include a real physical postal address — this platform adds it as a small footer automatically so you never forget. The consent basis (e.g. "existing customer relationship") is stored permanently as your own record of why you believed you were allowed to email this list, in case a complaint or legal inquiry ever asks — it's never shown to recipients, purely an audit trail for you.
What's an idempotency key?
A safety net against double-sends. If your internet connection drops right as you click "Send" and you're not sure it went through, resubmitting with the same idempotency key returns the original campaign instead of sending everything a second time. Leave it blank for a normal, one-off send.
What's a webhook URL for, in plain terms?
Think of it like a delivery notification, not a technical thing. Without one, the only way to know a campaign has finished sending to everyone is to keep coming back and clicking "Check status" — like calling a restaurant every few minutes to ask if your order's ready. A webhook flips that around: instead of you checking on it, it tells you the moment it's done, the same way a delivery app texts you instead of making you refresh a tracking page.
Mechanically: you give it one URL — but that has to be something on your own system (a server, a script, a no-code automation tool), not something this platform provides. The moment every recipient has been handled (sent, bounced, blocked, whatever the outcome), this platform sends ONE request to that URL with the final tally, and whatever's listening there can then do something automatically — log it, alert your team in Slack, mark a task done — with no human needing to come back and check manually.
The one thing that trips people up: this is a machine talking to a machine, not an email or text to a person. If you're not a developer and don't use an automation tool, this field isn't useful to you yet — a no-code tool like Zapier's "Catch Hook" or Make.com's "Webhook" module (both free to start, zero coding) is the easiest way to get a working URL if you want to try it; otherwise just leave this blank and keep using "Check status," exactly like before.
What's Reply-To for?
Lets you send from one address (like no-reply@) while replies land in a different inbox a real person actually reads (like support@) — useful since marketing sends often shouldn't come from an inbox anyone's watching.
Why was my content rejected with "matches known spam-risk patterns"?
Your subject/body matched one of a specific, narrow set of checks — never a general "does this sound spammy" judgment call, and never a hard block. It's rejected only until you resubmit the exact same content with the "Send anyway" option (or confirmContentWarnings: true via the API), which sends it exactly as written. What's actually checked: unedited template placeholder text (e.g. "Welcome to Our Community", "Lorem ipsum"); an unrendered personalization tag like {{name}} or *|FNAME|* — this platform never substitutes those, so literal tag text reaching a real send is always a mistake, not intentional content; a link or image pointing at a placeholder domain (example.com and similar); a link through a URL-shortening service (bit.ly and similar) or straight to a raw IP address, both of which mail providers treat as phishing signals; a link to a punycode/internationalized domain; multiple exclamation marks or repeated currency symbols ($$$) in the subject; an all-uppercase subject line; or a subject starting with "Re:"/"Fwd:" on a brand-new campaign (a known tactic to fake a reply thread and boost opens). Every one of these is cheap and specific rather than a fuzzy AI judgment — false positives (e.g. a business genuinely named "Your Company LLC") are expected occasionally, which is exactly why this only ever warns, never blocks outright.
New campaign
Campaign status
Loaded live from the server (GET /tenants/{tenantId}/campaigns) -- your 5 most recent campaigns, on any device.
Suppressions
Addresses this tenant will never email again — from a hard bounce, a soft bounce, a complaint, or an unsubscribe click.
How this works
Why does an address end up here?
Four ways, all permanent and all immediate — there's no "3 strikes" grace period for any of them: a hard bounce (the mailbox doesn't exist), a soft bounce (temporary delivery failure — e.g. a full mailbox — but treated as suppressed on the very first occurrence, not after repeated tries), a complaint (the recipient hit "mark as spam"), or the recipient clicking the unsubscribe link in one of your emails. Every future campaign checks this list first and silently skips anyone on it — there's no way to accidentally email someone who's been suppressed.
Can I remove someone from this list?
Not through this console, and not through the API either — suppression is permanent by design. It's the mechanism that keeps this platform's (and your) sending reputation healthy with mailbox providers like Gmail, so it's deliberately not something a UI button can undo.
Why do some addresses I know are suppressed not show up here?
This list only shows addresses suppressed on or after this feature's release (2026-07-07) — anything suppressed earlier doesn't carry the internal field this lookup needs, so it can't be found. It's still fully suppressed and still being enforced on every send; it just won't appear in this particular list.
What does "Erase recipient data" below actually delete?
Only the historical record of past sends to that address (which campaigns it was sent in, when, whether it bounced) — for GDPR/CCPA-style "right to be forgotten" requests. It deliberately does not touch this suppression list: if an address already bounced, complained, or unsubscribed, it stays suppressed forever even after its send history is erased. Erasure and suppression solve two different problems — one is about your data retention, the other is about never emailing someone again — and this platform keeps them fully independent on purpose.
Suppressed addresses
Loaded live from the server (GET /tenants/{tenantId}/suppressions), most recently suppressed first.
Erase recipient data (GDPR / CCPA)
Deletes this address's send history (which campaigns it was sent, when, whether it bounced) across ALL your campaigns. This does not un-suppress it -- if it already bounced, complained, or unsubscribed, it stays suppressed forever; erasure only removes the historical record of past sends.
API keys
Issue an additional key for rotation, or a separately-named key for e.g. a CI pipeline. Not how you get your first key — that comes from creating a tenant.
How this works
Why would I want more than one key?
Named, separate keys let you tell things apart later — e.g. one key for a CI/deployment pipeline, one for a teammate, one for this console — and revoke just one of them if it's ever exposed, without breaking everything else that uses a different key.
Why do I need an existing key just to make a new one?
Otherwise, anyone who ever saw your tenant ID (which isn't a secret — it's in every URL and response) could mint themselves a brand-new, fully working key for your tenant with zero proof they're actually you. Requiring a real, valid key first closes that door.
What if I lose every key I have?
There's currently no self-service "forgot my key" recovery, by design (see "Get started" for why keys can't just be looked up again). Recovery would need direct operator intervention on this platform's own infrastructure — worth keeping at least one key somewhere safe (a password manager, a secrets vault) rather than only in your browser.
What happens when I revoke a key?
It stops working immediately for any new request — permanent, with no undo. You can't revoke your tenant's only remaining key (that guard exists specifically so you can never lock yourself out entirely); issue a replacement key first if you need to retire your last one.
Issue an additional key
Your API keys
Loaded live from the server (GET /tenants/{tenantId}/api-keys) -- names and creation dates only, never the keys themselves (only a hash of each is ever stored). Revoking is permanent and immediate.
No recovery if every key is lost
Only a hash of each key is ever stored server-side — a raw key is shown exactly once, here, and can't be retrieved again. If every key for a tenant is lost, there's currently no self-service recovery path.