Send WeChat Mini Program Subscribe Messages via Cloud Function
In one sentence: After the Mini Program frontend obtains user authorization via
wx.requestSubscribeMessageand stores the subscription record in the database, the server usescloud.openapi.subscribeMessage.sendat 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
templateIdis in hand - Not applicable: Service notifications (that is a WeChat Official Account capability with a different API)
- Not applicable: Using the
wx.cloudWeChat Cloud Development system (refer to its official documentation)
Prerequisites
| Dependency | Version |
|---|---|
| Node.js (Cloud Function runtime) | ≥ 16.13 |
wx-server-sdk | latest (for Cloud API calls via OpenAPI) |
@cloudbase/node-sdk | 3.18.1 |
@cloudbase/cli | latest |
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
templateIdand 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 formbV-cBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx- Field names in the template content, e.g.,
{{thing1.DATA}}{{time2.DATA}}{{amount3.DATA}}— thedatafield must strictly use these field names
Field types have restrictions:
| Field name prefix | Content length / format |
|---|---|
thing | ≤ 20 characters |
time | Format like "January 1, 2024 14:00" |
amount | Number + currency symbol, e.g., $100 |
phrase | ≤ 5 characters |
name | Name, ≤ 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.requestSubscribeMessagemust be called in the synchronous callback of a user tap event; it cannot be placed inonLoador after an asyncawait, 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
acceptdirectly, 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-sdkfrom add-auth. Onlywx-server-sdkhascloud.openapi, and it requires the Cloud Function and Mini Program to be associated via "WeChat Official Account Platform Association" templateIdin 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 mappingminiprogramStatecontrols the Mini Program version for deep links:formalfor production,trialfor experience version,developerfor development version; usedeveloperduring 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
- In the WeChat Official Account Platform developer settings, associate the current Mini Program
AppIDwith the CloudBase environment - In the Mini Program, tap to trigger
wx.requestSubscribeMessage; the system prompt appears → select "Allow" - CloudBase Console → Database → subscriptions: a new record with
_openidequal to the current user's openid should appear, withremainingQuotaequal to 1 - Call the
notifyOrderCloud Function once; the WeChat app should receive the template message - Check the
subscriptionsrecord again;remainingQuotashould now be 0 andlastUsedAtshould have a value
Common Errors
| Error code | Cause | Fix |
|---|---|---|
43101 | User has not subscribed to this template, or the previous one-time subscription quota has been consumed | Normal business path: the frontend must first call requestSubscribeMessage to obtain authorization before sending |
47003 | A field in data exceeds the length limit, or the field name does not match the template | Compare with the field names in the template (thing1, time2, etc.), pass strictly in { value: '...' } structure, and truncate long fields to within the limit |
40037 | templateId does not exist or has been deleted | Retrieve the latest templateId from the WeChat Official Account Platform |
41030 | page path does not exist | The 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 function | Using @cloudbase/node-sdk instead of wx-server-sdk | Switch 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
| Type | Trigger | Quota rule |
|---|---|---|
| One-time subscription | Any template; this is the default | Each user authorization = 1 server send; another send requires another authorization |
| Long-term subscription | Must apply for a "Public Service" category template; applicant is typically government / medical / transport / school | One user authorization allows repeated server sends within 30 days without consuming quota |
| Device subscription | IoT device Mini Programs | Similar 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.
Related Documentation
- Cloud API cloud.openapi Overview — authentication and association for Cloud API calls
- WeChat Subscribe Messages / subscribeMessage.send — full API parameters and error codes
- requestSubscribeMessage — frontend authorization prompt
- add-auth-wechat-miniprogram — prerequisite: login integration
- add-database-wechat-miniprogram — prerequisite: database read/write
Next Steps
- Alert on send failures: connect-wecom-webhook-cloud-function
- Switch to scheduled batch push: schedule-cloud-function-cron-job
- Template segmentation in multi-tenant scenarios: secure-database-multi-tenant-rules