Skip to main content

Send WeChat Mini Program Subscribe Messages via Cloud Function

In one sentence: After the Mini Program frontend obtains user authorization via wx.requestSubscribeMessage and stores the subscription record in the database, the server uses cloud.openapi.subscribeMessage.send at the right moment to send a template message to a specific user, completing business notifications like "order shipped" and "comment replied."

Estimated time: 40 minutes | Difficulty: Advanced

Applicable Scenarios

  • Applicable: WeChat Mini Program + standalone CloudBase environment, needing to send template messages to logged-in users
  • Applicable: At least one Subscribe Message template has been applied for on the WeChat Official Account Platform and the templateId is in hand
  • Not applicable: Service notifications (that is a WeChat Official Account capability with a different API)
  • Not applicable: Using the wx.cloud WeChat Cloud Development system (refer to its official documentation)

Prerequisites

DependencyVersion
Node.js (Cloud Function runtime)≥ 16.13
wx-server-sdklatest (for Cloud API calls via OpenAPI)
@cloudbase/node-sdk3.18.1
@cloudbase/clilatest

Also required:

  • Already completed add-auth-wechat-miniprogram, with a usable login state in the Mini Program
  • Already completed add-database-wechat-miniprogram, familiar with reading and writing the Cloud Database
  • At least one template applied for in WeChat Official Account Platform → Features → Subscribe Messages, with the templateId and field names noted (e.g., thing1, time2, amount3)
  • In CloudBase Console → Environment Settings → WeChat Official Account Platform Association, associate the current Mini Program AppID with the current env. This step is required for Cloud API calls to work

"Cloud API call" means a Cloud Function calls a WeChat Open API directly without the business code managing access tokens. CloudBase uses the associated Mini Program identity when calling the WeChat side, so this step must be done first.

Step 1: Confirm Template Field Mapping

Go to WeChat Official Account Platform → Subscribe Messages → My Templates, open your template, and note:

  • templateId, in the form bV-cBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • Field names in the template content, e.g., {{thing1.DATA}} {{time2.DATA}} {{amount3.DATA}} — the data field must strictly use these field names

Field types have restrictions:

Field name prefixContent length / format
thing≤ 20 characters
timeFormat like "January 1, 2024 14:00"
amountNumber + currency symbol, e.g., $100
phrase≤ 5 characters
nameName, ≤ 10 characters

Exceeding the length limit is rejected by the WeChat side with error code 47003.

Step 2: Mini Program Frontend Shows Authorization Prompt and Stores to Database

miniprogram/pages/order/order.js:

import { db } from '../../libs/cloudbase';

const SUBSCRIBE_TPL = 'bV-cBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

Page({
async onSubmitOrder() {
// 1. Show authorization prompt (must be called in a user tap event callback)
const res = await new Promise((resolve, reject) => {
wx.requestSubscribeMessage({
tmplIds: [SUBSCRIBE_TPL],
success: resolve,
fail: reject,
});
});

if (res[SUBSCRIBE_TPL] !== 'accept') {
// User declined or did not authorize this time; business can continue, but no Subscribe Message will be sent
return;
}

// 2. Store this authorization in the database, recording available quota (one-time subscription: each authorization = 1 send quota)
await db.collection('subscriptions').add({
templateId: SUBSCRIBE_TPL,
remainingQuota: 1,
acceptedAt: db.serverDate(),
// _openid is automatically written by the SDK
});

// 3. Subsequent business flow (place order, payment, ...)
},
});

Four easy-to-miss points:

  • wx.requestSubscribeMessage must be called in the synchronous callback of a user tap event; it cannot be placed in onLoad or after an async await, otherwise it will be blocked with "request intercepted"
  • After the user checks "always keep this choice" in the system prompt, subsequent calls will not show the prompt and will return accept directly, transparent to the business
  • A one-time Subscribe Message allows only one send per authorization, so the frontend must immediately store a +1 quota after authorizing, and the server must decrement by 1 on send
  • Long-term subscriptions (government/medical/transport categories) have a different quota rule: one authorization allows the server to send repeatedly within 30 days, suitable for public services

Step 3: Cloud Function Sends the Message

Create a new Cloud Function cloudfunctions/notifyOrder/index.js:

const cloud = require('wx-server-sdk');

cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV,
});

const db = cloud.database();
const _ = db.command;

exports.main = async (event) => {
const { openid, templateId, page, data } = event;

// 1. Retrieve one unconsumed quota from the subscriptions collection
const queryRes = await db
.collection('subscriptions')
.where({
_openid: openid,
templateId,
remainingQuota: _.gt(0),
})
.orderBy('acceptedAt', 'asc')
.limit(1)
.get();

if (queryRes.data.length === 0) {
return { ok: false, error: 'NO_QUOTA', message: 'User has no available subscription authorization' };
}

const quotaDoc = queryRes.data[0];

// 2. Call the Cloud API to send the message
try {
const sendRes = await cloud.openapi.subscribeMessage.send({
touser: openid,
templateId,
page: page || 'pages/index/index',
data,
miniprogramState: 'formal', // formal / trial / developer
lang: 'zh_CN',
});

// 3. Decrement quota only on successful send
await db.collection('subscriptions').doc(quotaDoc._id).update({
remainingQuota: _.inc(-1),
lastUsedAt: db.serverDate(),
});

return { ok: true, sendRes };
} catch (err) {
return {
ok: false,
error: 'SEND_FAILED',
errcode: err.errCode,
errmsg: err.errMsg,
};
}
};

Notes:

  • This Cloud Function uses wx-server-sdk, not the @cloudbase/node-sdk from add-auth. Only wx-server-sdk has cloud.openapi, and it requires the Cloud Function and Mini Program to be associated via "WeChat Official Account Platform Association"
  • templateId in the call parameters is camelCase (templateId), not snake_case (template_id). The WeChat REST API uses snake_case; the Cloud API layer performs one camelCase mapping
  • miniprogramState controls the Mini Program version for deep links: formal for production, trial for experience version, developer for development version; use developer during integration testing
  • Do not decrement quota on delivery failure. Wrap in try/catch, and only call inc(-1) in the successful send branch

Step 4: Frontend Calls the Cloud Function

Back in the Mini Program, call when business events occur:

import app from '../../libs/cloudbase';

async function notifyOrderShipped(orderId) {
const callable = app.callFunction({
name: 'notifyOrder',
data: {
openid: app.auth().currentUser.customUserId, // this is the openid
templateId: 'bV-cBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
page: `pages/order/detail?id=${orderId}`,
data: {
thing1: { value: 'Order has shipped' },
time2: { value: 'May 1, 2024 14:00' },
amount3: { value: '$199' },
},
},
});

const res = await callable;
console.log('[notifyOrder]', res.result);
}

Verification

  1. In the WeChat Official Account Platform developer settings, associate the current Mini Program AppID with the CloudBase environment
  2. In the Mini Program, tap to trigger wx.requestSubscribeMessage; the system prompt appears → select "Allow"
  3. CloudBase Console → Database → subscriptions: a new record with _openid equal to the current user's openid should appear, with remainingQuota equal to 1
  4. Call the notifyOrder Cloud Function once; the WeChat app should receive the template message
  5. Check the subscriptions record again; remainingQuota should now be 0 and lastUsedAt should have a value

Common Errors

Error codeCauseFix
43101User has not subscribed to this template, or the previous one-time subscription quota has been consumedNormal business path: the frontend must first call requestSubscribeMessage to obtain authorization before sending
47003A field in data exceeds the length limit, or the field name does not match the templateCompare with the field names in the template (thing1, time2, etc.), pass strictly in { value: '...' } structure, and truncate long fields to within the limit
40037templateId does not exist or has been deletedRetrieve the latest templateId from the WeChat Official Account Platform
41030page path does not existThe path must be a page that exists in the published version; during integration testing, set miniprogramState to developer to use the development version
Cloud Function reports cloud.openapi is not a functionUsing @cloudbase/node-sdk instead of wx-server-sdkSwitch to wx-server-sdk, and make sure the Mini Program and current env are associated in the Console

For error code definitions, see error-code. For WeChat-side error codes, see the WeChat Open Documentation.

One-time vs. Long-term Subscription

TypeTriggerQuota rule
One-time subscriptionAny template; this is the defaultEach user authorization = 1 server send; another send requires another authorization
Long-term subscriptionMust apply for a "Public Service" category template; applicant is typically government / medical / transport / schoolOne user authorization allows repeated server sends within 30 days without consuming quota
Device subscriptionIoT device Mini ProgramsSimilar to long-term subscription, but bound to a device

Commercial Mini Programs can almost certainly only use one-time subscriptions, so the business strategy is to "add more authorization entry points" to increase the number of authorizations per session.

Next Steps