Every app eventually needs to send email: a sign-up confirmation, a password reset, a receipt. The usual answer is SendGrid, Mailgun or Resend, and they're good services. But if you're building in Ghana, you hit the same wall I did: they're priced in dollars and want an international card.
So I built Sankofa, an email API that's prepaid in cedis. You top up with MTN MoMo, Telecel Cash or AirtelTigo Money at GHS 5 per 1,000 emails, with no subscription.
This post walks through sending email from a Node.js app with it, from your first test email to bulk sends and delivery webhooks.
1. Get an API key
Create an account, then open API Keys in the dashboard and create one. It starts with esk_ and is shown once, so put it straight into your environment:
export SANKOFA_API_KEY=esk_your_api_key
Keep it on your server. Never put it in frontend code, where anyone can read it.
2. Send your first email (no DNS needed)
Normally you can't send email until you've verified a domain. To get you going sooner, every account can use a shared test sender, hello@test.trysankofa.com, which can only send to the email address you signed up with:
const response = await fetch("https://api.trysankofa.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SANKOFA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "hello@test.trysankofa.com",
to: "you@example.com", // the address you signed up with
subject: "Hello from my app",
text: "It works!",
}),
});
const body = await response.json();
if (!response.ok) throw new Error(`${body.error}: ${body.message}`);
console.log("Sent:", body.messageId);
That uses the fetch built into Node.js 18 and later, so there's nothing to install. Check your inbox, and your spam folder the first time.
3. Send from your own domain
When you're ready to email your users, verify your domain on the Domains page. Sankofa gives you three DNS records to add. Once they're in, you can send from any address on that domain:
await fetch("https://api.trysankofa.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SANKOFA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Acme <orders@acme.com.gh>",
to: "customer@example.com",
subject: "Your order has shipped",
html: "<p>Your order is on its way.</p>",
text: "Your order is on its way.",
}),
});
Each recipient uses one credit. If a send fails, the credit comes back automatically.
4. Templates, and sending to many people
For emails you send repeatedly, save a template with {{placeholders}}:
await fetch("https://api.trysankofa.com/v1/templates", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SANKOFA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "order-shipped",
subject: "Your order {{order}} has shipped",
text: "Hi {{name}}, your order {{order}} is on its way.",
}),
});
Then send it to up to 500 people in one request. Each person gets their own copy with their own values filled in:
const result = await fetch("https://api.trysankofa.com/v1/emails/bulk", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SANKOFA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Acme <orders@acme.com.gh>",
templateName: "order-shipped",
recipients: [
{ to: "ama@example.com", templateData: { name: "Ama", order: "A-1042" } },
{ to: "kofi@example.com", templateData: { name: "Kofi", order: "A-1043" } },
],
}),
}).then((r) => r.json());
for (const r of result.results) console.log(r.to, r.status, r.error ?? "");
If one recipient fails (say, an address that bounced before), the rest still go out, and you're not charged for the failure.
5. Know when emails are delivered
Register a webhook on the Webhooks page, and Sankofa will POST delivery events to your server: delivered, bounced, opened, clicked and so on. Each request is signed, so check the signature before trusting it:
import crypto from "node:crypto";
export function isFromSankofa(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // reject old requests
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1, "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}
Pass it the raw request body (before JSON parsing) and the X-Webhook-Signature header.
Bonus: let your AI assistant send email
Sankofa has an MCP server, so tools like Claude Code and Cursor can send email and check delivery for you. Create a separate API key for the agent and give it a daily limit, so it can never send more than you allow:
claude mcp add --transport http sankofa https://api.trysankofa.com/mcp \
--header "Authorization: Bearer esk_your_agent_key"
Then you can ask, "email the team that the deploy is done", and it will.
Wrapping up
That covers most of what an app needs: test sends, your own domain, templates, bulk sends and delivery tracking. The docs cover the rest, including attachments and receiving replies.
It's early days, so I'd really value feedback, especially from anyone building in Ghana or elsewhere in West Africa. What do you use for email today, and what would make you switch?
The first 20 developers get 200 free emails to try it.
Top comments (1)
Pricing in cedis with MoMo top-ups makes a lot of sense. One check that would protect your senders' reputation: refuse a recipient before sending when its domain doesn't exist or publishes a Null MX (a single MX record of 0 "."), since that mail can never land and only comes back later as a bounce. I'd treat a DNS timeout as couldn't-tell and send anyway, so a slow resolver doesn't block someone's sign-up email.