PushMail.dev

SDKs & Libraries

PushMail is a standard REST API — you don't need an SDK. But these wrappers make it even easier.

No SDK needed

PushMail is designed to be simple enough to use with any HTTP client. Every endpoint accepts and returns JSON.

Node.js (fetch)
async function addContact(email: string, firstName?: string) {
  const res = await fetch("https://pushmail.dev/api/v1/contacts", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PUSHMAIL_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      siteId: 1,
      email,
      firstName,
      tags: ["signup"],
    }),
  });

  if (!res.ok) throw new Error(`PushMail error: ${res.status}`);
  return res.json();
}
Python (requests)
import requests

PUSHMAIL_KEY = "pm_live_YOUR_KEY"

def add_contact(email: str, first_name: str = None):
    res = requests.post(
        "https://pushmail.dev/api/v1/contacts",
        headers={"Authorization": f"Bearer {PUSHMAIL_KEY}"},
        json={
            "siteId": 1,
            "email": email,
            "firstName": first_name,
            "tags": ["signup"],
        },
    )
    res.raise_for_status()
    return res.json()
Go
func addContact(email, firstName string) error {
    body, _ := json.Marshal(map[string]interface{}{
        "siteId":    1,
        "email":     email,
        "firstName": firstName,
        "tags":      []string{"signup"},
    })

    req, _ := http.NewRequest("POST",
        "https://pushmail.dev/api/v1/contacts",
        bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("PUSHMAIL_KEY"))
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode >= 400 {
        return fmt.Errorf("pushmail: %d", resp.StatusCode)
    }
    return nil
}
Ruby
require "net/http"
require "json"

PUSHMAIL_KEY = ENV["PUSHMAIL_KEY"]

def add_contact(email, first_name: nil)
  uri = URI("https://pushmail.dev/api/v1/contacts")
  req = Net::HTTP::Post.new(uri)
  req["Authorization"] = "Bearer #{PUSHMAIL_KEY}"
  req["Content-Type"] = "application/json"
  req.body = {
    siteId: 1,
    email: email,
    firstName: first_name,
    tags: ["signup"]
  }.to_json

  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
    http.request(req)
  }

  JSON.parse(res.body)
end
PHP
<?php
function addContact(string $email, ?string $firstName = null): array {
    $ch = curl_init("https://pushmail.dev/api/v1/contacts");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer " . getenv("PUSHMAIL_KEY"),
            "Content-Type: application/json",
        ],
        CURLOPT_POSTFIELDS => json_encode([
            "siteId" => 1,
            "email" => $email,
            "firstName" => $firstName,
            "tags" => ["signup"],
        ]),
    ]);

    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

TypeScript helper (copy-paste)

Drop this into your project for a typed PushMail client. No dependencies required.

lib/pushmail.ts
const BASE = "https://pushmail.dev/api/v1";

class PushMail {
  constructor(private key: string, private siteId: number) {}

  private async request<T>(path: string, options?: RequestInit): Promise<T> {
    const res = await fetch(`${BASE}${path}`, {
      ...options,
      headers: {
        "Authorization": `Bearer ${this.key}`,
        "Content-Type": "application/json",
        ...options?.headers,
      },
    });
    const json = await res.json();
    if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`);
    return json.data;
  }

  // Contacts
  async addContact(data: {
    email: string;
    firstName?: string;
    lastName?: string;
    tags?: string[];
    customFields?: Record<string, string>;
  }) {
    return this.request("/contacts", {
      method: "POST",
      body: JSON.stringify({ siteId: this.siteId, ...data }),
    });
  }

  async getContact(id: number) {
    return this.request(`/contacts/${id}`);
  }

  async listContacts(params?: {
    q?: string;
    status?: string;
    page?: number;
    limit?: number;
  }) {
    const qs = new URLSearchParams({
      siteId: String(this.siteId),
      ...Object.fromEntries(
        Object.entries(params || {}).map(([k, v]) => [k, String(v)])
      ),
    });
    return this.request(`/contacts?${qs}`);
  }

  // Sequences
  async enroll(sequenceId: number, contactId: number, startAtStep?: number) {
    return this.request(
      `/sequences/${sequenceId}/enroll`,
      {
        method: "POST",
        body: JSON.stringify({ contactIds: [contactId], ...(startAtStep !== undefined && { startAtStep }) }),
      }
    );
  }

  // Campaigns
  async createCampaign(data: {
    name: string;
    templateId: number;
    listId?: number;
    tagId?: number;
  }) {
    return this.request(`/campaigns`, {
      method: "POST",
      body: JSON.stringify({ siteId: this.siteId, ...data }),
    });
  }

  async sendCampaign(campaignId: number) {
    return this.request(
      `/campaigns/${campaignId}/send`,
      { method: "POST" }
    );
  }
}

// Usage:
// const pm = new PushMail(process.env.PUSHMAIL_KEY!, 1);
// await pm.addContact({ email: "jane@example.com", tags: ["signup"] });

export { PushMail };

Framework guides

Since PushMail is a REST API, it works with any language or framework that can make HTTP requests. See the quickstart for integration examples.

FrameworkIntegration approach
Next.jsServer action or API route handler
Express / Node.jsMiddleware or route handler
Django / FlaskView function with requests
RailsController action with Net::HTTP

Error handling

All errors return a consistent JSON format:

{
  "error": "Human-readable error message"
}

HTTP status codes

CodeDescription
200Success
201Created
400Bad request — invalid parameters
401Unauthorized — missing or invalid API key
403Forbidden — insufficient permissions
404Not found — resource doesn't exist
409Conflict — duplicate (e.g., email already exists)
500Server error — please retry or contact support

On this page