Send magic link email to customer for portal access
Sends an automated email to a customer containing a secure magic link for accessing their subscription management portal. The email is sent using the shop’s configured email template and includes a time-limited authentication token.
What This Endpoint Does:
- Validates customer exists and has subscriptions
- Generates secure portal access token
- Retrieves shop’s email template configuration
- Sends personalized email with magic link
- Logs activity for audit trail
Magic Link Functionality:
What is a Magic Link? A magic link is a special URL containing an encrypted authentication token that allows customers to access their portal without entering a password. Clicking the link automatically logs them in.
Link Contents:
- Shop’s portal URL
- Encrypted customer token (2-hour expiration)
- Direct access to subscription management
- No password required
Security:
- Token expires in 2 hours
- Single customer authentication
- Cannot be used by others
- Logged for security audit
Request Parameters:
email (required):
- Customer’s email address
- Must exactly match email in Shopify
- Customer must have at least one subscription
- Case-sensitive in some systems
Email Template Configuration:
Template Requirements:
- Email template must be configured in Appstle settings
- Template type: SUBSCRIPTION_MANAGEMENT_LINK
- Template must not be disabled
- Template includes shop branding and customization
Email Content:
- Personalized greeting with customer name
- Clickable magic link button/link
- Expiration notice (link valid 2 hours)
- Shop branding and footer
- Optional custom messaging
Template Variables:
{customer_name}: Customer’s display name{magic_link}: Portal URL with token{shop_name}: Store name{expiration_time}: Token expiry time
Use Cases:
1. Customer Self-Service:
- “Forgot password” alternative
- Quick portal access without account setup
- Passwordless authentication flow
- Reduce friction for customers
2. Subscription Management Prompts:
- “Manage your subscription” emails
- Pre-billing reminders with management link
- Post-purchase subscription setup
- Re-engagement campaigns
3. Customer Support:
- Send portal access to customers
- Enable self-service during support interactions
- Provide instant portal access
- Reduce support workload
4. Automated Workflows:
- Payment failure recovery emails
- Subscription expiration notices
- Pause/skip reminders
- Renewal notifications
5. Marketing Campaigns:
- Subscription feature announcements
- New product availability
- Loyalty program invitations
- Referral program links
Process Flow:
1. API receives email parameter
2. Searches subscription database for customer email
3. If not found → Returns 400 error
4. If found → Retrieves customer subscriptions
5. Sorts subscriptions by status (active first)
6. Checks email template configuration
7. If disabled → Returns 400 error with instructions
8. If enabled → Generates magic link token
9. Prepares email with template
10. Sends email via configured provider
11. Logs activity (source: MERCHANT_EXTERNAL_API)
12. Returns success message
Response Format:
"Email triggered successfully."
Simple string response confirming email queued for delivery.
Important Considerations:
Customer Validation:
- Email MUST exist in subscription database
- Customer MUST have at least one subscription
- Customers without subscriptions cannot receive link
- Returns error if customer not found
Email Template Disabled:
- If template disabled in settings: Returns error
- Error message guides merchant to enable template
- Path: “More -> Notification Settings”
- Template must be explicitly enabled
Email Delivery:
- Email sent asynchronously
- Success response doesn’t guarantee delivery
- Check email logs for delivery confirmation
- Respects shop’s email provider settings
Multiple Subscriptions:
- If customer has multiple subscriptions: Sorts by status
- Active subscriptions shown first
- Link provides access to ALL customer subscriptions
- Portal displays all contracts
Activity Logging:
- All magic link emails logged
- Source: MERCHANT_EXTERNAL_API
- Includes timestamp and customer
- Viewable in activity logs
Integration Examples:
Webhook Trigger - Payment Failed:
async function handlePaymentFailure(webhook) {
const customerEmail = webhook.customer.email;
// Send magic link to customer
await fetch(
`/api/external/v2/subscription-contracts-email-magic-link?email=${customerEmail}`,
{ headers: { 'X-API-Key': process.env.APPSTLE_API_KEY } }
);
console.log(`Magic link sent to ${customerEmail} for payment update`);
}
Customer Support Button:
async function sendPortalAccess(customerEmail) {
try {
const response = await fetch(
`/api/external/v2/subscription-contracts-email-magic-link?email=${encodeURIComponent(customerEmail)}`,
{
headers: { 'X-API-Key': apiKey },
method: 'GET'
}
);
if (response.ok) {
alert('Portal access email sent to customer!');
}
} catch (error) {
console.error('Failed to send magic link:', error);
}
}
Best Practices:
- Validate Email: Check email format before calling API
- Rate Limiting: Don’t spam customers - limit frequency
- Error Handling: Handle customer not found gracefully
- User Feedback: Confirm email sent to user
- Test Template: Ensure email template configured and working
- Monitor Logs: Check activity logs for delivery issues
Common Errors:
“Customer Email does not exist”:
- Email not found in subscription database
- Customer has no subscriptions
- Email may be misspelled
“Email template not found”:
- SUBSCRIPTION_MANAGEMENT_LINK template not configured
- Contact Appstle support to set up template
“Email is currently disabled”:
- Template disabled in Notification Settings
- Navigate to More -> Notification Settings
- Enable “Subscription Management Link” email
Authentication: Requires valid X-API-Key header
curl --request GET \
--url https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-linkimport requests
url = "https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link';
const options = {method: 'GET'};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body"Email triggered successfully."curl --request GET \
--url https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-linkimport requests
url = "https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link';
const options = {method: 'GET'};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://subscription-admin.appstle.com/api/external/v2/subscription-contracts-email-magic-link")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body"Email triggered successfully."