First off, thank you for considering contributing to Sendable! 🎉
The following is a set of guidelines for contributing to this project. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
- Code of Conduct
- Getting Started
- Development Workflow
- Project Structure
- Coding Standards
- Commit Guidelines
- Pull Request Process
- Testing
- Documentation
This project and everyone participating in it is governed by our commitment to fostering an open and welcoming environment. Please be respectful and professional in all interactions.
- Be Respectful: Treat everyone with respect and kindness
- Be Collaborative: Work together towards the common goal
- Be Patient: Help others learn and grow
- Accept Feedback: Be open to constructive criticism
- Focus on What's Best: Prioritize the project and community
Before you begin, ensure you have:
- Node.js (v20.x or higher)
- pnpm (v9.x or higher)
- Git
- A Convex account (Sign up free)
- A code editor (VS Code recommended)
-
Fork the Repository
# Click the "Fork" button on GitHub -
Clone Your Fork
git clone https://github.com/hasnaintypes/sendable-ai.git cd sendable-ai -
Add Upstream Remote
git remote add upstream https://github.com/hasnaintypes/sendable-ai.git
-
Install Dependencies
pnpm install
-
Set Up Environment Variables
cp .env.example .env.local # Edit .env.local with your credentials -
Initialize Database
npx convex dev --once
-
Start Development Server
pnpm run dev
We follow a feature-branch workflow:
main (production)
↓
develop (staging)
↓
feature/your-feature-name (your work)
# Update your local main branch
git checkout main
git pull upstream main
# Create and switch to a new branch
git checkout -b feature/your-feature-name
# Or for bug fixes
git checkout -b fix/bug-description
# Or for documentation
git checkout -b docs/what-you-are-documentingfeature/- New features (e.g.,feature/lead-scoring)fix/- Bug fixes (e.g.,fix/email-validation)docs/- Documentation updates (e.g.,docs/api-reference)refactor/- Code refactoring (e.g.,refactor/auth-flow)test/- Adding tests (e.g.,test/campaign-creation)chore/- Maintenance tasks (e.g.,chore/update-dependencies)
# Fetch latest changes from upstream
git fetch upstream
# Rebase your branch onto upstream/main
git rebase upstream/main
# If there are conflicts, resolve them and continue
git add .
git rebase --continue
# Force push to your fork (only for your feature branches!)
git push origin feature/your-feature-name --forcesendable-ai/
├── convex/ # Backend (Convex)
│ ├── auth/ # Authentication mutations & queries
│ ├── betterAuth/ # Better Auth adapter & config
│ ├── emails/ # Email templates (React Email)
│ ├── leads/ # Lead management (future)
│ ├── campaigns/ # Campaign logic (future)
│ ├── _generated/ # Auto-generated files (don't edit)
│ └── schema.ts # Database schema definitions
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (auth)/ # Protected routes
│ │ ├── (unauth)/ # Public routes
│ │ └── api/ # API routes
│ ├── components/ # React components
│ │ ├── auth/ # Authentication components
│ │ ├── layout/ # Layout components
│ │ ├── ui/ # shadcn/ui components
│ │ └── providers/ # Context providers
│ └── lib/ # Shared utilities
└── public/ # Static assets
convex/schema.ts- Database schema (modify carefully)src/app/layout.tsx- Root layoutsrc/lib/auth/client.tsx- Client-side authconvex/auth/helpers.ts- Server-side auth
- Always use TypeScript - No plain JavaScript files
- Define types explicitly - Avoid
anyunless absolutely necessary - Use interfaces for objects - Prefer
interfaceovertypefor object shapes - Export types - Make types reusable across files
// Good
interface Lead {
id: string;
email: string;
company: string;
status: "new" | "contacted" | "replied";
}
// Bad
const lead: any = { ... };- Use functional components - No class components
- Use hooks - Leverage React hooks for state and effects
- One component per file - Keep files focused
- Export named components - Avoid default exports for components
// Good
export function LeadCard({ lead }: { lead: Lead }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<Card>
{/* Component content */}
</Card>
);
}
// Bad
export default ({ lead }) => <div>...</div>- Use clear function names - Describe what the function does
- Validate inputs - Always check arguments
- Use transactions - For multi-document operations
- Handle errors gracefully - Return meaningful error messages
// Good
export const createLead = mutation({
args: {
email: v.string(),
company: v.string(),
role: v.optional(v.string()),
},
handler: async (ctx, args) => {
// Validate email format
if (!args.email.includes("@")) {
throw new Error("Invalid email format");
}
// Check for duplicates
const existing = await ctx.db
.query("leads")
.withIndex("by_email", (q) => q.eq("email", args.email))
.first();
if (existing) {
throw new Error("Lead already exists");
}
return await ctx.db.insert("leads", {
...args,
createdAt: Date.now(),
status: "new",
});
},
});- Use Tailwind CSS - Utility-first approach
- Follow shadcn/ui patterns - For consistent component styling
- Mobile-first - Design for mobile, enhance for desktop
- Dark mode support - Use theme-aware classes
// Good
<div className="flex flex-col gap-4 p-6 md:flex-row md:gap-6 md:p-8">
<button className="bg-primary text-primary-foreground hover:bg-primary/90">
Click me
</button>
</div>
// Bad
<div style={{ display: "flex", padding: "24px" }}>
<button style={{ background: "#007bff" }}>Click me</button>
</div>- React components: PascalCase (e.g.,
LeadCard.tsx) - Utilities/hooks: camelCase (e.g.,
useLeadFilter.ts) - Convex functions: camelCase (e.g.,
mutations.ts,queries.ts) - Pages: lowercase with hyphens (e.g.,
forget-password/page.tsx)
We follow Conventional Commits for clear commit history.
<type>(<scope>): <subject>
<body>
<footer>
feat:- New featurefix:- Bug fixdocs:- Documentation changesstyle:- Code style changes (formatting, no logic change)refactor:- Code refactoringperf:- Performance improvementstest:- Adding or updating testschore:- Maintenance tasksci:- CI/CD changes
# Feature
git commit -m "feat(leads): add CSV import functionality"
# Bug fix
git commit -m "fix(auth): correct redirect after password reset"
# Documentation
git commit -m "docs(readme): update installation instructions"
# Refactor
git commit -m "refactor(campaigns): simplify email generation logic"
# Multiple paragraphs
git commit -m "feat(analytics): add campaign performance dashboard
- Add funnel visualization
- Implement reply rate tracking
- Add sentiment analysis chart
Closes #123"- Atomic commits - Each commit should be a single logical change
- Present tense - "Add feature" not "Added feature"
- Imperative mood - "Fix bug" not "Fixes bug"
- Be descriptive - Explain what and why, not just what
- Reference issues - Use "Closes #123" or "Fixes #456"
- Update your branch with the latest main
- Run all checks locally:
pnpm run lint pnpm run build # Run type checking npx tsc --noEmit - Test your changes thoroughly
- Update documentation if needed
- Add tests for new features
-
Push your branch to your fork
git push origin feature/your-feature-name
-
Open a PR on GitHub
- Use a clear, descriptive title
- Reference related issues
- Fill out the PR template completely
-
PR Title Format
feat(leads): add bulk delete functionality fix(auth): resolve email verification bug docs(contributing): improve setup instructions
When you open a PR, include:
## Description
Brief description of what this PR does.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Related Issues
Closes #123
## Changes Made
- Added X functionality
- Fixed Y bug
- Updated Z documentation
## Testing Done
- [ ] Tested locally
- [ ] Added unit tests
- [ ] Tested on multiple browsers
- [ ] Tested mobile responsiveness
## Screenshots (if applicable)
[Add screenshots here]
## Checklist
- [ ] My code follows the project's coding standards
- [ ] I have added tests that prove my fix/feature works
- [ ] I have updated the documentation
- [ ] My changes generate no new warnings
- [ ] I have checked my code and corrected any misspellings- Automated checks must pass (linting, type checking, build)
- At least one approval from a maintainer required
- Address feedback promptly and professionally
- Resolve conflicts if any arise
- Squash commits may be required before merge
-
Delete your branch (both local and remote)
git branch -d feature/your-feature-name git push origin --delete feature/your-feature-name
-
Update your main branch
git checkout main git pull upstream main
# Run all tests
pnpm test
# Watch mode
pnpm test -- --watch
# Coverage
pnpm test -- --coverage- Test user flows - Focus on user-facing functionality
- Test edge cases - Handle errors and unexpected inputs
- Keep tests simple - One concept per test
- Use descriptive names - Clearly state what is being tested
// Example test structure
describe("Lead Creation", () => {
it("should create a new lead with valid data", async () => {
// Arrange
const leadData = { email: "test@example.com", company: "ACME" };
// Act
const result = await createLead(leadData);
// Assert
expect(result).toBeDefined();
expect(result.email).toBe(leadData.email);
});
it("should reject invalid email format", async () => {
// Arrange
const leadData = { email: "invalid-email", company: "ACME" };
// Act & Assert
await expect(createLead(leadData)).rejects.toThrow("Invalid email");
});
});- Add JSDoc comments for complex functions
- Explain "why" not "what" - Code shows what, comments explain why
- Keep comments updated - Outdated comments are worse than none
/**
* Calculates lead score based on engagement metrics
*
* Uses a weighted algorithm that prioritizes recent activity:
* - Email opens: 1 point
* - Email clicks: 3 points
* - Replies: 5 points
* - Positive sentiment: 2x multiplier
*
* @param lead - The lead to score
* @returns Score between 0-100
*/
export function calculateLeadScore(lead: Lead): number {
// Implementation
}When updating docs:
- Keep README.md current - Reflect actual functionality
- Update CHANGELOG.md - Document notable changes
- Add inline examples - Show usage in comments
- Create guides - For complex features
If you have questions:
- Check existing Issues
- Search Discussions
- Open a new issue with the
questionlabel
Your contributions make Sendable better for everyone. We appreciate your time and effort!
Happy Coding!