USSD-Triggered Emails
Send transactional emails instantly when users complete a USSD transaction — mobile money receipts, airtime confirmations, and banking alerts.
What is USSD?
USSD (Unstructured Supplementary Service Data) is the session-based protocol behind
short codes like *737#, *901#, and *822# used for
mobile money, airtime top-up, and bank account management across Africa.
Unlike apps or the web, USSD works on any phone — smartphone or feature phone —
with no internet required.
When a user completes a USSD transaction, your backend receives a callback from the USSD gateway. That's the perfect moment to trigger a confirmation email — while the user is still looking at their screen.
The /v1/emails/ussd endpoint
The POST /v1/emails/ussd endpoint is purpose-built for USSD callbacks:
- Returns a simplified
{ queued: true, ref: emailId }response optimised for USSD gateway response handlers - Accepts a
phoneNumberfield to look up the recipient by phone (if you've stored contacts with phone numbers) - Falls back to direct SES delivery if Redis is temporarily unavailable — so the email is never dropped, even during connectivity issues
- Accepts a
metadata.ussdobject for session tracking and audit logs
Example: MTN Mobile Money receipt
// Your USSD callback handler (Express)
app.post("/ussd/callback", async (req, res) => {
const { sessionId, phoneNumber, amount, transactionRef } = req.body;
// Respond to the gateway immediately (must be within ~5 seconds)
res.json({ message: "END Your transaction is complete. Receipt sent." });
// Send receipt email in the background
fetch("https://api.quolle.com/v1/emails/ussd", {
method: "POST",
headers: {
"Authorization": "Bearer qle_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "receipts@mail.yourbank.com",
phoneNumber, // looked up against your contact list
subject: "Transaction Receipt",
html: `
<h2>Transaction Successful</h2>
<p>Amount: <strong>₦${amount.toLocaleString()}</strong></p>
<p>Ref: ${transactionRef}</p>
<p>Date: ${new Date().toLocaleString("en-NG")}</p>
`,
metadata: {
ussd: {
sessionId,
shortCode: "*737#",
phoneNumber,
transactionRef,
},
},
}),
}).catch(console.error);
});
import requests
import threading
def send_ussd_receipt(session_id, phone_number, amount, transaction_ref):
requests.post(
"https://api.quolle.com/v1/emails/ussd",
headers={"Authorization": f"Bearer {AVIANISE_KEY}"},
json={
"from": "receipts@mail.yourbank.com",
"phoneNumber": phone_number,
"subject": "Transaction Receipt",
"html": f"<p>₦{amount:,.0f} sent. Ref: {transaction_ref}</p>",
"metadata": {
"ussd": {
"sessionId": session_id,
"shortCode": "*901#",
"phoneNumber": phone_number,
"transactionRef": transaction_ref,
}
},
},
)
@app.route("/ussd/callback", methods=["POST"])
def ussd_callback():
data = request.json
# Fire-and-forget receipt email
threading.Thread(
target=send_ussd_receipt,
args=(data["sessionId"], data["phoneNumber"],
data["amount"], data["transactionRef"])
).start()
return {"message": "END Transaction complete. Receipt sent."}
<?php
Route::post('/ussd/callback', function (Request $request) {
// Respond to gateway first
$response = response()->json([
'message' => 'END Transaction complete. Receipt sent.'
]);
// Send receipt in background via queue
SendUssdReceipt::dispatch(
$request->sessionId,
$request->phoneNumber,
$request->amount,
$request->transactionRef
);
return $response;
});
// In App\Jobs\SendUssdReceipt:
Http::withToken(config('avianise.api_key'))
->post('https://api.quolle.com/v1/emails/ussd', [
'from' => 'receipts@mail.yourbank.com',
'phoneNumber' => $this->phoneNumber,
'subject' => 'Transaction Receipt',
'html' => "<p>₦{$this->amount} sent.</p>",
'metadata' => [
'ussd' => [
'sessionId' => $this->sessionId,
'shortCode' => '*737#',
'phoneNumber' => $this->phoneNumber,
'transactionRef' => $this->transactionRef,
],
],
]);
Using a recipient email address
If you already know the recipient's email, pass to instead of
phoneNumber:
{
"from": "alerts@mail.yourfintech.com",
"to": "customer@example.com",
"subject": "Airtime purchase confirmed",
"html": "<p>₦500 airtime added to +234801234…</p>",
"metadata": {
"ussd": {
"sessionId": "sess_abc123",
"shortCode": "*822#",
"phoneNumber": "+2348012345678"
}
}
}
Linking phone numbers to contacts
To use phoneNumber lookup, store your customers in a Contact List
with their phone numbers:
await fetch("https://api.quolle.com/v1/contacts/your-list-id", {
method: "POST",
headers: { "Authorization": "Bearer qle_your_api_key", "Content-Type": "application/json" },
body: JSON.stringify({
email: "customer@example.com",
firstName: "Amaka",
phone: "+2348012345678", // used for USSD lookups
}),
});
Response format
The /v1/emails/ussd endpoint returns a minimal response
optimised for quick processing in gateway handlers:
{
"queued": true,
"ref": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
USSD email tracking
Emails sent via the USSD endpoint are tagged with a USSD badge in your Logs dashboard. Filter by source to see all USSD-triggered emails and their delivery status.
await the
email send before responding.