Hybrid Pricing Implementation Plan
Generated: 2025-07-23 21:00 UTC
Status: Implementation Plan
Region: us-east4
Verified: Based on project architecture
Overview
This document outlines the implementation plan for a hybrid pricing approach where:
- Free Tier: Direct signup with email (no Stripe involvement)
- Pro Tier: $10/month via Stripe Payment Links
This approach solves Stripe's $0.50 minimum payment limitation while maintaining a truly free tier.
Prerequisites
Before implementing the hybrid approach, ensure these components are ready:
1. Gmail API Setup COMPLETED
Gmail API is configured and ready to send welcome emails with API keys.
Status:
- Gmail API enabled
- Service account created:
gmail-sender@convert-to-markdown-us-east4.iam.gserviceaccount.com
- Service account key saved:
deploy-gcp/keys/gmail-service-account.json
- Domain-wide delegation configured in Google Workspace
- Client ID
116940581280353324169
authorized with scopehttps://www.googleapis.com/auth/gmail.send
Email Sender: Set GMAIL_SENDER_EMAIL
environment variable to the email address authorized in your Google Workspace (e.g., lindsay@knowcode.tech
)
2. Firestore Database Required
Must have Firestore configured for storing user subscriptions.
Check:
# Verify Firestore is enabled
gcloud firestore databases list --project=YOUR_PROJECT_ID
3. Stripe Configuration Required for Pro Tier
- Stripe account with products/prices created
- Payment link for $10/month Pro subscription
- Webhook endpoint configured
- Customer portal enabled
Already completed from deployment:
- Pro tier payment link:
https://buy.stripe.com/bJe4gy1yQ9SJ2Mg1lNbfO07
- Customer portal:
https://billing.stripe.com/p/login/eVqfZgelCfd3cmQd4vbfO00
4. Project Configuration
- Region: us-east4 (all functions must deploy here)
- Project ID: Your GCP project ID
- Domain: convert-to-markdown.knowcode.tech
5. Email Templates
Prepare welcome email templates for both tiers (see implementation below).
Architecture Overview
Implementation Steps
Step 1: Create Free Tier Signup Function
File: functions/free-tier-signup/index.js
const { Firestore } = require('@google-cloud/firestore');
const crypto = require('crypto');
const { sendWelcomeEmailFree } = require('../lib/email-gmail-api');
const db = new Firestore();
// API key generation functions
function generateApiKey() {
const randomBytes = crypto.randomBytes(32).toString('hex');
return `ctm_live_${randomBytes}`;
}
function hashApiKey(apiKey) {
return crypto.createHash('sha256').update(apiKey).digest('hex');
}
// Email validation
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
exports.freeTierSignup = async (req, res) => {
// CORS headers for browser requests
res.set('Access-Control-Allow-Origin', 'https://convert-to-markdown.knowcode.tech');
res.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
// Only allow POST
if (req.method !== 'POST') {
res.status(405).json({ error: 'Method not allowed' });
return;
}
const { email } = req.body;
// Validate email
if (!email || !isValidEmail(email)) {
return res.status(400).json({ error: 'Please provide a valid email address' });
}
try {
// Check if user already exists
const existingUsers = await db.collection('subscriptions')
.where('email', '==', email.toLowerCase())
.limit(1)
.get();
if (!existingUsers.empty) {
return res.status(400).json({
error: 'This email is already registered. Check your inbox for your API key.'
});
}
// Rate limiting check (optional)
// Could implement IP-based or email-based rate limiting here
// Generate API key
const apiKey = generateApiKey();
const hashedApiKey = hashApiKey(apiKey);
// Create unique user ID for free tier
const userId = 'free_' + crypto.randomBytes(16).toString('hex');
// Create user record
const userDoc = {
email: email.toLowerCase(),
plan: 'free',
status: 'active',
apiKeyHash: hashedApiKey,
monthlyUsage: 0,
createdAt: new Date(),
updatedAt: new Date(),
source: 'web_signup',
features: {
maxConversions: 50,
maxFileSize: 5242880, // 5MB in bytes
priority: false,
dashboard: false
}
};
// Save to Firestore
await db.collection('subscriptions').doc(userId).set(userDoc);
// Send welcome email
await sendWelcomeEmailFree(email, apiKey);
// Log for monitoring
console.log(`New free tier signup: ${email}`);
res.json({
success: true,
message: 'Check your email for your API key!'
});
} catch (error) {
console.error('Signup error:', error);
res.status(500).json({
error: 'An error occurred during signup. Please try again.'
});
}
};
// Welcome email for free tier
async function sendWelcomeEmailFree(email, apiKey) {
const subject = 'Welcome to Convert To Markdown - Your API Key Inside! 🎉';
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #667eea; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
.content { background: #f8f9fa; padding: 30px; border-radius: 0 0 8px 8px; }
.api-key { background: #1e293b; color: #34d399; padding: 15px; border-radius: 6px; font-family: monospace; font-size: 14px; word-break: break-all; }
.warning { background: #fef3c7; border: 1px solid #f59e0b; padding: 15px; border-radius: 6px; margin: 20px 0; }
.features { background: white; padding: 20px; border-radius: 6px; margin: 20px 0; }
.cta { background: #667eea; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block; margin-top: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Welcome to Convert To Markdown!</h1>
<p>Your free account is now active</p>
</div>
<div class="content">
<h2>Here's your API key:</h2>
<div class="api-key">${apiKey}</div>
<div class="warning">
<strong>⚠️ Important:</strong> Save this API key securely. For security reasons, we don't store it and can't retrieve it for you. If you lose it, you'll need to create a new account.
</div>
<div class="features">
<h3>Your Free Plan includes:</h3>
<ul>
<li>✅ 50 conversions per month</li>
<li>✅ 5MB max file size</li>
<li>✅ All file formats (Excel, Word, PDF, PowerPoint)</li>
<li>✅ RESTful API access</li>
<li>✅ Basic email support</li>
</ul>
</div>
<h3>Quick Start:</h3>
<p>Try your first conversion with this curl command:</p>
<div style="background: #f1f5f9; padding: 15px; border-radius: 6px; font-family: monospace; font-size: 12px; overflow-x: auto;">
curl -X POST https://convert-to-markdown.knowcode.tech/xlsx-converter \\<br>
-H "x-api-key: ${apiKey}" \\<br>
-F "file=@your-file.xlsx"
</div>
<p>Need more conversions? Upgrade to Pro for just $10/month and get:</p>
<ul>
<li>🚀 10,000 conversions per month (200x more!)</li>
<li>📁 50MB file size limit</li>
<li>📊 Usage dashboard</li>
<li>⚡ Priority processing</li>
</ul>
<center>
<a href="https://convert-to-markdown.knowcode.tech/pricing" class="cta">View API Documentation</a>
</center>
<p style="margin-top: 30px; color: #666;">
Happy converting!<br>
The Convert To Markdown Team
</p>
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e2e8f0;">
<p style="font-size: 12px; color: #666; text-align: center;">
You received this email because you signed up for Convert To Markdown.<br>
If you didn't sign up, please ignore this email.
</p>
</div>
</div>
</body>
</html>
`;
const textContent = `
Welcome to Convert To Markdown!
Your API Key:
${apiKey}
⚠️ IMPORTANT: Save this key securely - we can't retrieve it for you!
Your Free Plan includes:
- 50 conversions per month
- 5MB max file size
- All file formats supported
- RESTful API access
Quick Start:
curl -X POST https://convert-to-markdown.knowcode.tech/xlsx-converter \\
-H "x-api-key: ${apiKey}" \\
-F "file=@your-file.xlsx"
Need more? Upgrade to Pro for $10/month:
https://convert-to-markdown.knowcode.tech/pricing
Happy converting!
The Convert To Markdown Team
`;
await sendEmail({
to: email,
subject: subject,
text: textContent,
html: htmlContent
});
}
File: functions/free-tier-signup/package.json
{
"name": "free-tier-signup",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@google-cloud/firestore": "^7.0.0",
"googleapis": "^105.0.0"
}
}
Step 2: Update Pricing Page
File: docs/pricing.md
Replace the Free Tier card button section with:
<!-- Replace the button/link with this form -->
<form id="free-tier-signup" style="margin-top: 20px;" onsubmit="return handleFreeSignup(event)">
<input type="email"
id="free-email-input"
name="email"
placeholder="Enter your email address"
required
style="width: 100%; padding: 12px; margin-bottom: 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 16px;">
<button type="submit"
id="free-submit-btn"
style="width: 100%; padding: 14px; background: #64748b; color: white; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 16px; transition: background 0.2s;">
Get Your Free API Key →
</button>
</form>
<p style="text-align: center; margin-top: 10px; font-size: 0.875rem; color: #666;">
No credit card required • Instant access
</p>
<div id="free-signup-message" style="display: none; margin-top: 15px; padding: 12px; border-radius: 6px; text-align: center;"></div>
<script>
async function handleFreeSignup(event) {
event.preventDefault();
const form = event.target;
const email = form.email.value;
const button = document.getElementById('free-submit-btn');
const messageDiv = document.getElementById('free-signup-message');
const originalButtonText = button.textContent;
// Reset any previous messages
messageDiv.style.display = 'none';
// Disable form during submission
button.disabled = true;
button.textContent = 'Creating your account...';
button.style.background = '#94a3b8';
try {
const response = await fetch('https://us-east4-YOUR_PROJECT_ID.cloudfunctions.net/free-tier-signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: email })
});
const result = await response.json();
if (response.ok) {
// Success
messageDiv.style.display = 'block';
messageDiv.style.background = '#dcfce7';
messageDiv.style.color = '#166534';
messageDiv.style.border = '1px solid #86efac';
messageDiv.innerHTML = '✅ <strong>Success!</strong> Check your email for your API key.';
// Hide the form
form.style.display = 'none';
// Track conversion if analytics is set up
if (typeof gtag !== 'undefined') {
gtag('event', 'sign_up', {
method: 'email',
value: 'free_tier'
});
}
} else {
// Error
messageDiv.style.display = 'block';
messageDiv.style.background = '#fee2e2';
messageDiv.style.color = '#991b1b';
messageDiv.style.border = '1px solid #fca5a5';
messageDiv.textContent = result.error || 'Signup failed. Please try again.';
// Re-enable form
button.disabled = false;
button.textContent = originalButtonText;
button.style.background = '#64748b';
}
} catch (error) {
// Network or other error
console.error('Signup error:', error);
messageDiv.style.display = 'block';
messageDiv.style.background = '#fee2e2';
messageDiv.style.color = '#991b1b';
messageDiv.style.border = '1px solid #fca5a5';
messageDiv.textContent = 'Network error. Please check your connection and try again.';
// Re-enable form
button.disabled = false;
button.textContent = originalButtonText;
button.style.background = '#64748b';
}
return false;
}
</script>
Step 3: Deploy Free Tier Signup Function
# Deploy the function
gcloud functions deploy free-tier-signup \
--gen2 \
--runtime=nodejs20 \
--region=us-east4 \
--source=functions/free-tier-signup \
--entry-point=freeTierSignup \
--trigger-http \
--allow-unauthenticated \
--set-env-vars="PROJECT_ID=YOUR_PROJECT_ID" \
--memory=256MB \
--timeout=30s \
--max-instances=10
Step 4: Use Gmail API Email Library
The Gmail API email library is already created at functions/lib/email-gmail-api.js
and includes:
- Gmail API integration using domain-wide delegation
- Service account authentication
- Welcome email templates for both Free and Pro tiers
- Error handling and logging
Key functions:
sendEmail({ to, subject, text, html })
- Generic email sendingsendWelcomeEmailFree(email, apiKey)
- Free tier welcome emailsendWelcomeEmailPro(email, apiKey)
- Pro tier welcome email
Step 5: Update Webhook Handler for Pro Tier
The existing Stripe webhook handler should already handle Pro subscriptions. Ensure it uses the same Firestore schema as the free tier signup.
Step 6: Testing Checklist
Free Tier Signup:
- Email validation works
- Duplicate email check works
- API key generation works
- Email delivery works
- Firestore record created
- UI shows success message
- Form is hidden after success
Pro Tier Signup:
- Stripe payment link works
- Webhook receives event
- API key generated
- Email sent
- Firestore record created
API Key Validation:
- Free tier keys work
- Pro tier keys work
- Usage limits enforced
- Monthly reset works
Step 7: Monitoring
Set up monitoring for:
- Free tier signup success/failure rate
- Email delivery success
- API key validation performance
- Conversion rate (visits → signups)
Security Considerations
- Rate Limiting: Implement rate limiting on the free tier signup endpoint
- Email Validation: Consider adding email verification step
- CAPTCHA: Add reCAPTCHA if spam becomes an issue
- CORS: Restrict to your domain only
- API Key Security: Never log or expose raw API keys
Rollback Plan
If issues arise:
- Remove inline form from pricing page
- Revert to a single pricing approach
- Keep existing Pro tier Stripe flow
- All existing API keys continue working
Timeline
- Day 1: Deploy free tier signup function
- Day 2: Update pricing page with inline form
- Day 3: Test and monitor
- Day 4: Full rollout
Success Metrics
- Free tier signup conversion rate > 5%
- Email delivery success rate > 99%
- API key validation latency < 100ms
- User satisfaction (support tickets)
Next Steps:
- Verify all prerequisites are met
- Deploy the free tier signup function
- Update the pricing page
- Test end-to-end flow
- Monitor initial signups