Disable Paystack subscription emails by setting send_invoices: false and send_sms: false when creating or updating a plan. Then build your own email notifications triggered by Paystack webhook events (charge.success, invoice.payment_failed, subscription.create, subscription.disable). This gives you full control over branding, content, and timing.
Paystack sends email notifications to customers whenever a subscription event happens: charge succeeded, charge failed, subscription created, subscription cancelled. These emails are functional but generic. They use Paystack's branding, not yours.
If you also send your own emails for these events (which you should, for a professional product), the customer receives two emails for every event: one from Paystack and one from you. This is confusing, unprofessional, and erodes trust.
The solution: disable Paystack's emails and send your own. You get full control over branding, content, language, and timing. The customer gets one clear notification per event.
This article is part of the subscriptions and recurring billing guide.
Paystack email settings live on the Plan object. When you create a plan, set send_invoices and send_sms to false:
var axios = require('axios');
async function createPlanWithoutEmails() {
var response = await axios.post(
'https://api.paystack.co/plan',
{
name: 'Pro Monthly',
amount: 500000,
interval: 'monthly',
currency: 'NGN',
send_invoices: false,
send_sms: false,
},
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
}
);
var plan = response.data.data;
console.log('Plan created with emails disabled:', plan.plan_code);
return plan;
}
For existing plans, you can update the email settings:
async function disableEmailsOnExistingPlan(planCode) {
var response = await axios.put(
'https://api.paystack.co/plan/' + planCode,
{
send_invoices: false,
send_sms: false,
},
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
}
);
console.log('Emails disabled for plan:', planCode);
return response.data.data;
}
Important: These settings affect all subscriptions on this plan. If you disable emails on a plan, no customer subscribed to that plan receives Paystack notifications. Make sure your own email system is working before disabling.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
With Paystack emails disabled, you send your own based on webhook events. Here is the mapping:
app.post('/webhooks/paystack', function(req, res) {
var event = req.body;
switch (event.event) {
case 'subscription.create':
sendWelcomeEmail(event.data);
break;
case 'charge.success':
// Only send receipt for subscription charges, not all charges
if (event.data.plan && event.data.plan.plan_code) {
sendSubscriptionReceiptEmail(event.data);
}
break;
case 'invoice.payment_failed':
sendPaymentFailedEmail(event.data);
break;
case 'subscription.not_renew':
sendCancellationConfirmEmail(event.data);
break;
case 'subscription.disable':
sendSubscriptionEndedEmail(event.data);
break;
}
res.sendStatus(200);
});
async function sendSubscriptionReceiptEmail(chargeData) {
var customer = chargeData.customer;
var amount = chargeData.amount / 100; // Convert from kobo
var currency = chargeData.currency;
var reference = chargeData.reference;
var planName = chargeData.plan ? chargeData.plan.name : 'Your subscription';
await emailService.send({
to: customer.email,
subject: 'Payment receipt for ' + planName,
template: 'subscription_receipt',
data: {
customerName: customer.first_name || 'there',
planName: planName,
amount: currency + ' ' + amount.toLocaleString(),
reference: reference,
date: new Date().toLocaleDateString(),
manageUrl: 'https://yoursite.com/billing',
},
});
}
async function sendPaymentFailedEmail(invoiceData) {
var customer = invoiceData.customer;
var cardUpdateUrl = await generateCardUpdateUrl(customer.email);
await emailService.send({
to: customer.email,
subject: 'Action needed: your payment did not go through',
template: 'payment_failed',
data: {
customerName: customer.first_name || 'there',
cardUpdateUrl: cardUpdateUrl,
},
});
}
Build these templates to cover the full subscription lifecycle:
1. Subscription Welcome
Triggered by: subscription.create
Content: Thank the customer, confirm the plan and price, state the next billing date, link to billing settings.
2. Payment Receipt
Triggered by: charge.success (for subscription charges)
Content: Confirm the payment amount, show the card charged (last4), state the billing period covered, include the transaction reference, link to download invoice.
3. Payment Failed
Triggered by: invoice.payment_failed
Content: Notify that payment failed, show which card was attempted, include a prominent card update button, explain what happens if not resolved.
4. Upcoming Renewal
Triggered by: invoice.create (optional, some products skip this)
Content: Notify that a charge will happen soon, show the amount, link to manage subscription.
5. Cancellation Confirmed
Triggered by: subscription.not_renew
Content: Confirm the cancellation, state when access ends (end of current period), offer to resubscribe, include a feedback link.
6. Subscription Ended
Triggered by: subscription.disable
Content: Notify that the subscription has ended, explain how to reactivate, assure data is preserved.
Each email should include your logo, follow your brand colors, and link to your billing page. The tone should match your product's voice.
Even after disabling Paystack emails, edge cases can cause duplicates or missed emails.
Webhook deduplication
Paystack can send the same webhook event multiple times. If your email handler fires on every webhook, the customer gets multiple copies of the same receipt. Deduplicate by tracking which events you have already processed:
async function processWebhookWithDedup(event) {
var eventId = event.data.reference || event.data.subscription_code;
var eventType = event.event;
var deduplicationKey = eventType + '_' + eventId;
// Check if already processed
var existing = await db.query(
'SELECT id FROM processed_webhook_events WHERE dedup_key = $1',
[deduplicationKey]
);
if (existing.rows.length > 0) {
console.log('Duplicate webhook skipped: ' + deduplicationKey);
return;
}
// Record before processing
await db.query(
'INSERT INTO processed_webhook_events (dedup_key, event_type, processed_at) VALUES ($1, $2, NOW())',
[deduplicationKey, eventType]
);
// Process the event (send email, update database, etc.)
await handleWebhookEvent(event);
}
Testing before going live
Before disabling Paystack emails on your live plans:
If you already have customers on plans with Paystack emails enabled, transition carefully:
If you have multiple plans, you can disable Paystack emails one plan at a time to reduce risk.
If you use charge_authorization instead of the Subscriptions API, Paystack sends a payment receipt email to the customer by default for each successful charge. This is separate from the subscription email settings.
For manual charges, Paystack's default receipts may be acceptable since they are transaction-level, not subscription-level. But if you want full control, send your own receipt after each charge_authorization call and coordinate with Paystack support about disabling default transaction receipts.
async function chargeAndNotify(userId, amount, authCode, purpose) {
var user = await getUser(userId);
var reference = 'BILL_' + userId + '_' + Date.now();
var result = await chargeAuthorization(user.email, amount, authCode, reference);
if (result.status === 'success') {
// Send your own receipt
await emailService.send({
to: user.email,
subject: 'Payment receipt',
template: 'manual_charge_receipt',
data: {
customerName: user.first_name,
amount: (amount / 100).toLocaleString(),
purpose: purpose,
reference: reference,
date: new Date().toLocaleDateString(),
},
});
} else {
// Send failure notification
var cardUpdateUrl = await generateCardUpdateUrl(user.email, userId);
await emailService.send({
to: user.email,
subject: 'Payment did not go through',
template: 'charge_failed',
data: {
customerName: user.first_name,
cardUpdateUrl: cardUpdateUrl,
reason: result.gateway_response,
},
});
}
return result;
}
The goal is always the same: the customer receives exactly one notification per billing event, branded with your logo and voice, with clear information about what happened and what to do next.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course