Send & Issue Credentials
One call sends a credential to an email, phone, or LearnCard profile. Sign it yourself or let LearnCard sign from a template.
The one-line version
npx @learncard/cli send # you sign; writes send.mjs
npx @learncard/cli send --template # LearnCard signs from a reusable template; writes send-from-template.mjsBoth prompt for the recipient (or take it as an argument: send you@yourdomain.com) and reuse the .env the Quickstart created. Everything below is what those scripts do, and the options send() takes beyond them.
send() in one picture
const result = await learnCard.invoke.send({
type: 'boost',
recipient: 'jane@example.com', // email, phone, profile ID, or DID β detected automatically
signedCredential, // β you signed it (or)
templateUri, // β LearnCard signs from a template you created
refresh: true, // β optional: issue it refreshable (email/phone uses deferred inbox signing)
});You choose who the recipient is and who signs. Everything else β delivery, the claim email, auto-delivery to existing accounts β is the same call. Add refresh: true and the recipient can receive in-place updates later; see Issue and Refresh a Managed Credential. Add idempotencyKey to make an SDK-signed or server-signed refreshable send safe to retry (direct REST callers providing signedCredential omit the key and retry the same signed credential and templateUri); see Retrying a refreshable send safely.
jane@example.com
Goes to the Universal Inbox; Jane gets a claim email. If that email is already verified on a LearnCard account, it's delivered straight there.
+15551234567
Same, by SMS. Phone delivery is limited to issuers on the trusted registry.
jane-doe (profile ID)
Delivered directly to that account.
did:key:z6Mkβ¦ / did:web:β¦
Delivered directly to that DID.
signedCredential
You did, with issueCredential() and your seed
Nothing
templateUri
LearnCard, with your signing authority, from a template you created once
setup-signing, then createBoost (or send --template)
template: { credential, name, category }
Same, creating the template in this call
setup-signing
Template URIs look like lc:network:<host>/trpc:boost:<id>. Always use the value returned by createBoost or send (result.uri) β never build one.
You need a network profile before you send β createServiceProfile for an organization or bot, createProfile for a person. The Quickstart and learncard init do this for you. Network Profiles.
Examples
Your proof is kept as-is all the way to the recipient. send() also saves the credential as a template (result.uri) so you can send the same badge again with templateUri.
Needs a signing authority β one command: npx @learncard/cli setup-signing. Templates can carry {{variables}} filled per send; see Issue at scale.
Include the LearnCard Boost context and BoostCredential type on templates the network signs; it stamps boostId on each issued credential. Why.
If Jane has consented to contractUri with write permission for this template's category, the credential is written under that consent and appears in her consent history. See Connect a User's LearnCard.
Options for email and phone recipients
Email and phone sends stay claimable for 30 days by default. Claims are single-use, and the network can't read the credential after it's claimed. For transcripts, CLRs, or other sensitive learner data, set expiresInDays to the shortest practical window β it controls how long the payload can be claimed, not the credential's own validity dates. Details in Universal Inbox.
Guardian approval
The guardian gets an approval email with a 6-digit code; the learner sees a pending notice, then a normal claim button once approved. guardianStatus moves to GUARDIAN_APPROVED or GUARDIAN_REJECTED. If the guardian goes on to create a LearnCard account, they become the learner's manager and every future credential to that learner is gated automatically β no guardianEmail needed, approval in-app without a code.
guardianEmail must differ from recipient (self-approval not allowed)
Use a different address for the guardian.
Stuck at AWAITING_GUARDIAN
The guardian hasn't acted. They can reopen the link from their email; the learner can't claim until then.
Learner can't claim after approval
Check guardianStatus β GUARDIAN_REJECTED means not claimable. Send again if it was a mistake.
What comes back
refresh is the one part you can't get anywhere else: refreshable credentials are stored encrypted to the recipient, so keep the receipt alongside your own record of the claims if you want to publish updates. It's metadata only β no credential content. Full walkthrough: Issue and Refresh a Managed Credential.
inbox.status
Means
claimUrl
PENDING
New recipient. A claim email/SMS went out (unless suppressDelivery); they claim at claimUrl.
yes
ISSUED
That email/phone is already verified on a LearnCard account β delivered directly. Nothing to claim.
no
Once someone claims by email, that address is verified on their account, so your next send to it comes back ISSUED.
Over HTTP
send() is also POST https://network.learncard.com/api/send with the same JSON body. Authenticate with Authorization: Bearer <API token> from an auth grant with boosts:write scope β npx @learncard/cli token, or Generate API Tokens.
Sign locally, send over HTTP
If you sign credentials yourself but want to deliver them from any language, create a token and POST the signed credential. This script creates the token and writes the request body to request.json:
What you should see
The response is JSON:
inbox.status is PENDING for a new recipient (inbox.claimUrl is where they claim it) or ISSUED if they already use LearnCard (auto-delivered, no claimUrl).
The token has one permission (boosts:write). Store it like a password. Revoke it any time.
Track what happened to a credential
Every send() returns an activityId. The network logs each step against it β CREATED when you sent, DELIVERED when it reached an existing account or the claim email went out, CLAIMED when the recipient accepted, and EXPIRED or FAILED if it didn't get there.
Or from the terminal: npx @learncard/cli status <activityId>. No server needed β poll this when you want to know if a credential was claimed. Details, filters, and stats: Credential Activity. To be told instead of asking, see Know When a Credential Is Claimed.
Who has a template
For a per-template view β everyone who holds a credential issued from it, and whether each one is active, revoked, or suspended:
This is also how you find the credentialUri to revoke.
Issue at scale with templates
Boosts support Mustache-style templating to inject dynamic values at issuance time.
Personalize with {{variables}}
Use {{variableName}} syntax in your credential template:
Provide templateData when sending to fill in the variables:
The resulting credential will have all placeholders replaced.
Issue from a spreadsheet
You can issue credentials in bulk by reading a CSV file.
Note that re-running send for the same recipient and template will re-send the credential (it is not idempotent). To avoid duplicates, use learnCard.invoke.getPaginatedBoostRecipients(boostUri) to reconcile who has already received it before sending.
Next steps
Know whether it was claimed β Know When a Credential Is Claimed
Take one back β Revoke or Update a Credential
Design your own credential β Building Verifiable Credentials
Lower-level inbox control (per-issuance signer, custom claim flows) β Universal Inbox API
Last updated
Was this helpful?