Send your first email
Roughly ten minutes from "approved tenant" to a delivered receipt. This guide assumes your access request has been approved and you can sign in at app.qwicmail.com.
Not approved yet? qwicmail uses closed onboarding — every tenant is manually verified before sending is enabled. If you haven't submitted a request, start at /request. Approval usually lands within one business day.
1. Install the SDK
Pick the SDK for your language. Every example on this page also has a
raw curl tab, so you can integrate from any HTTP client if
we don't ship an official SDK for your stack yet.
# No install needed.
export QWICMAIL_API_KEY="qm_live_xxxxxxxxxxxx"npm install @qwicmail/sdk
# or: pnpm add @qwicmail/sdk | yarn add @qwicmail/sdkpip install qwicmailgo get gitlab.com/teqpace/qwicmail-gogem install qwicmailcomposer require qwicmail/qwicmail-php<!-- Maven -->
<dependency>
<groupId>com.qwicmail</groupId>
<artifactId>qwicmail-java</artifactId>
<version>0.1.0</version>
</dependency>
// Gradle (Kotlin DSL)
implementation("com.qwicmail:qwicmail-java:0.1.0")2. Mint an API key
In the customer portal, open Settings → API keys → New key.
Give the key a name (we recommend the environment it's for —
production, staging) and grant the
emails:send scope. Two other scopes exist
(domains:write, webhooks:write) which you only
need if you intend to drive those resources from your code rather than
from the portal.
The plaintext key (format qm_live_…) is shown exactly
once. Copy it into your secret manager before closing the dialog —
there is no way to recover it afterwards. If lost, revoke it and mint
a fresh one.
3. Add and verify a sending domain
You can only send mail from a domain that's been verified for your tenant. The simplest path is the portal:
- Open Sending → Domains → Add domain.
- Enter the domain (e.g.
mail.example.com). - Publish the three DNS records the portal prints — one DKIM (required), one SPF (recommended), one DMARC (recommended in monitoring mode).
- Click Verify. Verification queries your authoritative nameservers directly and either marks the domain verified or returns the specific failure reason.
The same flow is available over the API — see the domains reference. Whichever path you use, DKIM is the only record verification depends on; SPF and DMARC are advisory but strongly recommended (and required by Gmail / Yahoo bulk sender rules above 5,000 messages/day).
4. Send
Once the domain is verified, every API key with emails:send can submit mail from any address on it:
curl https://api.qwicmail.com/emails \
-H "Authorization: Bearer $QWICMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "alerts@mail.example.com",
"to": "customer@example.org",
"subject": "Your receipt",
"html": "<p>Thanks for your order.</p>"
}'import { Qwicmail } from "@qwicmail/sdk";
const qm = new Qwicmail({ apiKey: process.env.QWICMAIL_API_KEY! });
const res = await qm.emails.send({
from: "alerts@mail.example.com",
to: "customer@example.org",
subject: "Your receipt",
html: "<p>Thanks for your order.</p>",
});
console.log(res.message_ids);import os
from qwicmail import Qwicmail
qm = Qwicmail(api_key=os.environ["QWICMAIL_API_KEY"])
res = qm.emails.send(
from_="alerts@mail.example.com",
to="customer@example.org",
subject="Your receipt",
html="<p>Thanks for your order.</p>",
)
print(res.message_ids)package main
import (
"context"
"fmt"
"os"
"gitlab.com/teqpace/qwicmail-go"
)
func main() {
qm := qwicmail.New(os.Getenv("QWICMAIL_API_KEY"))
res, err := qm.Emails.Send(context.Background(), &qwicmail.SendRequest{
From: "alerts@mail.example.com",
To: []string{"customer@example.org"},
Subject: "Your receipt",
HTML: "<p>Thanks for your order.</p>",
})
if err != nil {
panic(err)
}
fmt.Println(res.MessageIDs)
}require "qwicmail"
qm = Qwicmail::Client.new(api_key: ENV.fetch("QWICMAIL_API_KEY"))
res = qm.emails.send(
from: "alerts@mail.example.com",
to: "customer@example.org",
subject: "Your receipt",
html: "<p>Thanks for your order.</p>",
)
puts res.message_ids<?php
require __DIR__ . "/vendor/autoload.php";
use Qwicmail\Client;
$qm = new Client(getenv("QWICMAIL_API_KEY"));
$res = $qm->emails->send([
"from" => "alerts@mail.example.com",
"to" => "customer@example.org",
"subject" => "Your receipt",
"html" => "<p>Thanks for your order.</p>",
]);
print_r($res->messageIds);import com.qwicmail.Qwicmail;
import com.qwicmail.SendRequest;
import com.qwicmail.SendResponse;
var qm = new Qwicmail(System.getenv("QWICMAIL_API_KEY"));
SendResponse res = qm.emails().send(SendRequest.builder()
.from("alerts@mail.example.com")
.to("customer@example.org")
.subject("Your receipt")
.html("<p>Thanks for your order.</p>")
.build());
System.out.println(res.messageIds());A successful submission returns 202 Accepted with the IDs the platform assigned:
{
"id": "1f0c2c1a-...",
"message_ids": ["8a3f1f5b-..."],
"rejected": [],
"replayed": false
}
Each message_id traces the email end-to-end. The same value
is set as the SMTP Message-ID header on the outgoing wire,
and appears in every webhook event the message produces. If you ever
need to ask support about a single message, that's the value to quote.
What if a recipient is on the suppression list?
Recipients on your tenant suppression list (or the platform-global
hard-bounce list) never reach the queue. They show up in the
rejected array with a reason such as
hard_bounce or complaint — the rest of the
recipients in the call are accepted normally. If every recipient is
suppressed, the response is 422 Unprocessable Entity with
error: "all_recipients_suppressed".
5. (Optional) Listen for delivery events
Once your sending is in production, register an endpoint to receive delivery, bounce, complaint, and open events as HTTP POSTs. The payloads, retry schedule, and signature-verification recipes are documented under Webhooks.
6. Go live
Two things to know before you switch production traffic to qwicmail:
- Tier escalation is automatic. New tenants start with a conservative 24-hour sending cap. The cap relaxes on day 14 and is removed entirely on day 30, provided your bounce and complaint rates stay clean. No paperwork — just send sensibly.
- Auto-throttle protects everyone. If 24-hour bounce or complaint rates cross our thresholds, the platform throttles your tenant and surfaces the reason in the portal. Throttle lifts automatically after 7 days of clean sending.
For the practical deliverability checklist (DNS, warm-up posture, list hygiene) see Deliverability.