JavaScript Mailto Link Implementation Guide
Complete guide to generating mailto links in JavaScript with modern frameworks and best practices.
Quick Start (Vanilla JavaScript)
function createMailto({ to, subject = '', body = '', cc = '', bcc = '' }) {
const params = [];
if (subject) params.push(`subject=${encodeURIComponent(subject)}`);
if (body) params.push(`body=${encodeURIComponent(body)}`);
if (cc) params.push(`cc=${encodeURIComponent(cc)}`);
if (bcc) params.push(`bcc=${encodeURIComponent(bcc)}`);
const query = params.length ? `?${params.join('&')}` : '';
return `mailto:${to}${query}`;
}
// Usage
const mailto = createMailto({
to: '[email protected]',
subject: 'Bug Report',
body: 'Description:\n- Issue: Login failed\n- Browser: Chrome'
});
console.log(mailto);
// mailto:[email protected]?subject=Bug%20Report&body=Description%3A%0A-%20Issue%3A%20Login%20failed%0A-%20Browser%3A%20Chrome
React Implementation
Custom Hook
import { useMemo } from 'react';
function useMailto({ to, subject = '', body = '', cc = '', bcc = '' }) {
return useMemo(() => {
const params = [];
if (subject) params.push(`subject=${encodeURIComponent(subject)}`);
if (body) params.push(`body=${encodeURIComponent(body)}`);
if (cc) params.push(`cc=${encodeURIComponent(cc)}`);
if (bcc) params.push(`bcc=${encodeURIComponent(bcc)}`);
const query = params.length ? `?${params.join('&')}` : '';
return `mailto:${to}${query}`;
}, [to, subject, body, cc, bcc]);
}
// Usage in component
function ContactButton() {
const mailto = useMailto({
to: '[email protected]',
subject: 'Website Inquiry',
body: 'Hi team,\n\nI have a question about...'
});
return (
<a href={mailto} className="btn btn-primary">
📧 Contact Us
</a>
);
}
Complete React Component
import { useState } from 'react';
function MailtoBuilder() {
const [fields, setFields] = useState({
to: '',
subject: '',
body: '',
cc: '',
bcc: ''
});
const mailto = useMailto(fields);
const charCount = mailto.length;
const isOverLimit = charCount > 1800;
const handleChange = (e) => {
setFields({
...fields,
[e.target.name]: e.target.value
});
};
return (
<div className="mailto-builder">
<input
type="email"
name="to"
placeholder="To: [email protected]"
value={fields.to}
onChange={handleChange}
required
/>
<input
type="text"
name="subject"
placeholder="Subject"
value={fields.subject}
onChange={handleChange}
/>
<textarea
name="body"
placeholder="Message body..."
value={fields.body}
onChange={handleChange}
rows={6}
/>
<div className={`char-count ${isOverLimit ? 'warning' : ''}`}>
{charCount} / 1800 characters
{isOverLimit && ' ⚠️ Too long for Outlook'}
</div>
<a
href={mailto}
className="btn btn-primary"
onClick={(e) => {
if (!fields.to) {
e.preventDefault();
alert('Please enter recipient email');
}
}}
>
Open Email Client
</a>
</div>
);
}
Vue.js Implementation
Composition API
<template>
<div class="mailto-form">
<input v-model="to" type="email" placeholder="To" required />
<input v-model="subject" placeholder="Subject" />
<textarea v-model="body" placeholder="Message" rows="6"></textarea>
<div class="preview">
<strong>Mailto URL:</strong>
<code>{{ mailto }}</code>
</div>
<a :href="mailto" class="btn">Send Email</a>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const to = ref('');
const subject = ref('');
const body = ref('');
const mailto = computed(() => {
const params = [];
if (subject.value) {
params.push(`subject=${encodeURIComponent(subject.value)}`);
}
if (body.value) {
params.push(`body=${encodeURIComponent(body.value)}`);
}
const query = params.length ? `?${params.join('&')}` : '';
return `mailto:${to.value}${query}`;
});
</script>
TypeScript Version
interface MailtoOptions {
to: string;
subject?: string;
body?: string;
cc?: string;
bcc?: string;
}
interface MailtoResult {
url: string;
length: number;
isValid: boolean;
warnings: string[];
}
function createMailto(options: MailtoOptions): MailtoResult {
const { to, subject = '', body = '', cc = '', bcc = '' } = options;
const params: string[] = [];
const warnings: string[] = [];
if (subject) params.push(`subject=${encodeURIComponent(subject)}`);
if (body) params.push(`body=${encodeURIComponent(body)}`);
if (cc) params.push(`cc=${encodeURIComponent(cc)}`);
if (bcc) params.push(`bcc=${encodeURIComponent(bcc)}`);
const query = params.length ? `?${params.join('&')}` : '';
const url = `mailto:${to}${query}`;
// Validation
if (url.length > 2000) {
warnings.push('URL exceeds 2000 chars (Outlook may truncate)');
}
if (url.length > 1800) {
warnings.push('URL approaching character limit');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailRegex.test(to);
if (!isValid) {
warnings.push('Invalid email address format');
}
return {
url,
length: url.length,
isValid,
warnings
};
}
// Usage with type safety
const result = createMailto({
to: '[email protected]',
subject: 'Hello',
body: 'Test message'
});
console.log(result);
// {
// url: 'mailto:[email protected]?subject=Hello&body=Test%20message',
// length: 58,
// isValid: true,
// warnings: []
// }
Node.js / Express Integration
const express = require('express');
const app = express();
app.use(express.json());
// API endpoint to generate mailto
app.post('/api/mailto', (req, res) => {
const { to, subject, body, cc, bcc } = req.body;
// Validation
if (!to) {
return res.status(400).json({ error: 'Recipient email required' });
}
const params = [];
if (subject) params.push(`subject=${encodeURIComponent(subject)}`);
if (body) params.push(`body=${encodeURIComponent(body)}`);
if (cc) params.push(`cc=${encodeURIComponent(cc)}`);
if (bcc) params.push(`bcc=${encodeURIComponent(bcc)}`);
const query = params.length ? `?${params.join('&')}` : '';
const mailto = `mailto:${to}${query}`;
// Check length
if (mailto.length > 2000) {
return res.status(400).json({
error: 'Mailto URL too long',
length: mailto.length,
max: 2000
});
}
res.json({
success: true,
mailto,
length: mailto.length
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Advanced Features
Template System
class MailtoTemplate {
static support({ product, issue, userId }) {
const body = `
Hi Support Team,
I'm experiencing an issue with ${product}.
Issue: ${issue}
User ID: ${userId}
Please help!
`.trim();
return createMailto({
to: '[email protected]',
subject: `Support: ${issue}`,
body
});
}
static feedback({ rating, comment }) {
const stars = '⭐'.repeat(rating);
const body = `
Rating: ${stars} (${rating}/5)
Feedback:
${comment}
`.trim();
return createMailto({
to: '[email protected]',
subject: 'Product Feedback',
body
});
}
}
// Usage
const supportMailto = MailtoTemplate.support({
product: 'Web App',
issue: 'Login Error',
userId: 'USER-123'
});
Builder Pattern
class MailtoBuilder {
constructor(to) {
this.to = to;
this.params = {};
}
setSubject(subject) {
this.params.subject = subject;
return this;
}
setBody(body) {
this.params.body = body;
return this;
}
addCC(cc) {
this.params.cc = cc;
return this;
}
addBCC(bcc) {
this.params.bcc = bcc;
return this;
}
build() {
return createMailto({ to: this.to, ...this.params });
}
getStats() {
const url = this.build();
return {
length: url.length,
maxLength: 2000,
remaining: 2000 - url.length,
percentage: (url.length / 2000 * 100).toFixed(1)
};
}
}
// Usage
const mailto = new MailtoBuilder('[email protected]')
.setSubject('Bug Report')
.setBody('Description here...')
.addCC('[email protected]')
.build();
console.log(mailto);
Analytics Tracking
// Track mailto clicks with Google Analytics 4
function trackMailtoClick(email, label) {
if (typeof gtag !== 'undefined') {
gtag('event', 'mailto_click', {
'event_category': 'contact',
'event_label': label,
'email_address': email
});
}
}
// Usage
document.querySelectorAll('a[href^="mailto:"]').forEach(link => {
link.addEventListener('click', () => {
const email = link.href.replace('mailto:', '').split('?')[0];
trackMailtoClick(email, window.location.pathname);
});
});
Testing
// Jest tests
describe('Mailto Generation', () => {
test('creates basic mailto', () => {
const result = createMailto({ to: '[email protected]' });
expect(result).toBe('mailto:[email protected]');
});
test('encodes subject', () => {
const result = createMailto({
to: '[email protected]',
subject: 'Hello World'
});
expect(result).toContain('subject=Hello%20World');
});
test('encodes line breaks', () => {
const result = createMailto({
to: '[email protected]',
body: 'Line 1\nLine 2'
});
expect(result).toContain('%0A');
});
test('handles special characters', () => {
const result = createMailto({
to: '[email protected]',
subject: 'Test & Demo'
});
expect(result).toContain('%26');
});
});
Common Pitfalls
// ❌ WRONG: Not encoding
const mailto = `mailto:${to}?subject=${subject}`;
// ✅ CORRECT: Proper encoding
const mailto = `mailto:${to}?subject=${encodeURIComponent(subject)}`;
// ❌ WRONG: Using escape() (deprecated)
const mailto = `mailto:${to}?subject=${escape(subject)}`;
// ✅ CORRECT: Use encodeURIComponent
const mailto = `mailto:${to}?subject=${encodeURIComponent(subject)}`;
// ❌ WRONG: Forgetting to join params with &
const mailto = `mailto:${to}?subject=${subject}?body=${body}`;
// ✅ CORRECT: Use & for additional params
const mailto = `mailto:${to}?subject=${subject}&body=${body}`;
Browser Compatibility
// Check if mailto is supported
function supportsMailto() {
return 'mailto:' in window.location;
}
// Fallback for unsupported browsers
function handleMailto(email) {
if (supportsMailto()) {
window.location.href = `mailto:${email}`;
} else {
// Copy to clipboard as fallback
navigator.clipboard.writeText(email);
alert(`Email copied: ${email}`);
}
}
Related Resources
📚 Mailto Guides
💻 Other Language Guides
- Python Guide - Flask, Django, FastAPI integration
- PHP Guide - Laravel, WordPress, pure PHP
- Ruby Guide - Rails, Sinatra, RSpec tests
📖 Deep Dives
Test your implementation with our free mailto link generator to ensure cross-browser compatibility.