# Examples

> Copy-paste recipes for the most common SendPing tasks — sending, batching, scheduling, attachments, contacts, and webhooks — using nothing but an HTTP client.

SendPing is a plain JSON REST API with official SDKs for Node.js, Ruby, PHP, Python, Go, Rust, Java, and .NET (plus a CLI) — see the [install table](https://www.sendping.co/docs/api/introduction). Every endpoint page in these docs carries a copyable tab for each of them alongside raw **cURL**.

The snippets below string those endpoints together into the recipes people ask for most. Swap `mb_xxxxxxxxx` for your API key and `yourdomain.com` for a [verified domain](https://www.sendping.co/docs/domains/managing).

## Send your first email

The smallest useful request — a single transactional email from a verified domain. See [Send an email](https://www.sendping.co/docs/api/emails-send).

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": ["delivered@test.sendping.co"],
  "subject": "Hello from SendPing",
  "html": "<p>Your first email 🎉</p>"
});
console.log({ data, error });
```

**Ruby**

```ruby
require "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::Emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": [
    "delivered@test.sendping.co"
  ],
  "subject": "Hello from SendPing",
  "html": "<p>Your first email 🎉</p>"
})
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->emails->send([
  'from' => "Acme <hello@yourdomain.com>",
  'to' => [
    "delivered@test.sendping.co"
  ],
  'subject' => "Hello from SendPing",
  'html' => "<p>Your first email 🎉</p>"
]);
```

**Python**

```python
import sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.Emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": [
    "delivered@test.sendping.co"
  ],
  "subject": "Hello from SendPing",
  "html": "<p>Your first email 🎉</p>"
})
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

sent, err := client.Emails.Send(&sendping.SendEmailRequest{
    From:    "Acme <hello@yourdomain.com>",
    To:      []string{"delivered@test.sendping.co"},
    Subject: "Hello from SendPing",
    Html:    "<p>Your first email 🎉</p>",
})
```

**Rust**

```rust
use sendping::{SendEmailOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let params = SendEmailOptions::new(
    "Acme <hello@yourdomain.com>",
    ["delivered@test.sendping.co"],
    "Hello from SendPing",
)
.with_html("<p>Your first email 🎉</p>");
let _sent = mb.emails.send(params).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.SendEmailRequest;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

SendEmailRequest request = SendEmailRequest.builder()
        .from("Acme <hello@yourdomain.com>")
        .to("delivered@test.sendping.co")
        .subject("Hello from SendPing")
        .html("<p>Your first email 🎉</p>")
        .build();

SendPingResponse response = sendping.emails().send(request);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.EmailSendAsync(new EmailMessage
{
    From = "Acme <hello@yourdomain.com>",
    To = "delivered@test.sendping.co",
    Subject = "Hello from SendPing",
    HtmlBody = "<p>Your first email 🎉</p>",
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/emails' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
  "from": "Acme <hello@yourdomain.com>",
  "to": ["delivered@test.sendping.co"],
  "subject": "Hello from SendPing",
  "html": "<p>Your first email 🎉</p>"
}'
```

**CLI**

```bash
sendping emails send \
  --from 'Acme <hello@yourdomain.com>' \
  --to 'delivered@test.sendping.co' \
  --subject 'Hello from SendPing' \
  --html '<p>Your first email 🎉</p>'
```

## Send with an attachment

Inline a file as base64 `content`, or hand SendPing a hosted `path` URL to fetch at send time. See [Attachments](https://www.sendping.co/docs/emails/attachments).

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": ["delivered@test.sendping.co"],
  "subject": "Your invoice",
  "html": "<p>Invoice attached.</p>",
  "attachments": [
    { "filename": "invoice.pdf", "path": "https://files.example.com/invoice.pdf" }
  ]
});
console.log({ data, error });
```

**Ruby**

```ruby
require "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::Emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": [
    "delivered@test.sendping.co"
  ],
  "subject": "Your invoice",
  "html": "<p>Invoice attached.</p>",
  "attachments": [
    {
      "filename": "invoice.pdf",
      "path": "https://files.example.com/invoice.pdf"
    }
  ]
})
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->emails->send([
  'from' => "Acme <hello@yourdomain.com>",
  'to' => [
    "delivered@test.sendping.co"
  ],
  'subject' => "Your invoice",
  'html' => "<p>Invoice attached.</p>",
  'attachments' => [
    [
      'filename' => "invoice.pdf",
      'path' => "https://files.example.com/invoice.pdf"
    ]
  ]
]);
```

**Python**

```python
import sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.Emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": [
    "delivered@test.sendping.co"
  ],
  "subject": "Your invoice",
  "html": "<p>Invoice attached.</p>",
  "attachments": [
    {
      "filename": "invoice.pdf",
      "path": "https://files.example.com/invoice.pdf"
    }
  ]
})
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

sent, err := client.Emails.Send(&sendping.SendEmailRequest{
    From:    "Acme <hello@yourdomain.com>",
    To:      []string{"delivered@test.sendping.co"},
    Subject: "Your invoice",
    Html:    "<p>Invoice attached.</p>",
    Attachments: []sendping.Attachment{
        {
            Filename: "invoice.pdf",
            Path:     "https://files.example.com/invoice.pdf",
        },
    },
})
```

**Rust**

```rust
use sendping::{Attachment, SendEmailOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let params = SendEmailOptions::new(
    "Acme <hello@yourdomain.com>",
    ["delivered@test.sendping.co"],
    "Your invoice",
)
.with_html("<p>Invoice attached.</p>")
.with_attachment(Attachment::from_path(
        "invoice.pdf",
        "https://files.example.com/invoice.pdf",
    ));
let _sent = mb.emails.send(params).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.Attachment;
import co.sendping.requests.SendEmailRequest;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

SendEmailRequest request = SendEmailRequest.builder()
        .from("Acme <hello@yourdomain.com>")
        .to("delivered@test.sendping.co")
        .subject("Your invoice")
        .html("<p>Invoice attached.</p>")
        .attachment(Attachment.builder()
                .filename("invoice.pdf")
                .path("https://files.example.com/invoice.pdf")
                .build())
        .build();

SendPingResponse response = sendping.emails().send(request);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.EmailSendAsync(new EmailMessage
{
    From = "Acme <hello@yourdomain.com>",
    To = "delivered@test.sendping.co",
    Subject = "Your invoice",
    HtmlBody = "<p>Invoice attached.</p>",
    Attachments = new List<EmailAttachment>
    {
        new()
        {
            Filename = "invoice.pdf",
            Path = "https://files.example.com/invoice.pdf",
        },
    },
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/emails' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
  "from": "Acme <hello@yourdomain.com>",
  "to": ["delivered@test.sendping.co"],
  "subject": "Your invoice",
  "html": "<p>Invoice attached.</p>",
  "attachments": [
    { "filename": "invoice.pdf", "path": "https://files.example.com/invoice.pdf" }
  ]
}'
```

## Schedule for later

Pass an ISO 8601 `scheduled_at` to hold an email until a future time. It can be rescheduled or canceled while still pending. See [Schedule email](https://www.sendping.co/docs/emails/schedule).

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": ["you@yourdomain.com"],
  "subject": "Your weekly digest",
  "html": "<p>Here is what you missed.</p>",
  "scheduled_at": "2026-06-30T09:00:00Z"
});
console.log({ data, error });
```

**Ruby**

```ruby
require "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::Emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": [
    "you@yourdomain.com"
  ],
  "subject": "Your weekly digest",
  "html": "<p>Here is what you missed.</p>",
  "scheduled_at": "2026-06-30T09:00:00Z"
})
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->emails->send([
  'from' => "Acme <hello@yourdomain.com>",
  'to' => [
    "you@yourdomain.com"
  ],
  'subject' => "Your weekly digest",
  'html' => "<p>Here is what you missed.</p>",
  'scheduled_at' => "2026-06-30T09:00:00Z"
]);
```

**Python**

```python
import sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.Emails.send({
  "from": "Acme <hello@yourdomain.com>",
  "to": [
    "you@yourdomain.com"
  ],
  "subject": "Your weekly digest",
  "html": "<p>Here is what you missed.</p>",
  "scheduled_at": "2026-06-30T09:00:00Z"
})
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

sent, err := client.Emails.Send(&sendping.SendEmailRequest{
    From:        "Acme <hello@yourdomain.com>",
    To:          []string{"you@yourdomain.com"},
    Subject:     "Your weekly digest",
    Html:        "<p>Here is what you missed.</p>",
    ScheduledAt: "2026-06-30T09:00:00Z",
})
```

**Rust**

```rust
use sendping::{SendEmailOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let params = SendEmailOptions::new(
    "Acme <hello@yourdomain.com>",
    ["you@yourdomain.com"],
    "Your weekly digest",
)
.with_html("<p>Here is what you missed.</p>")
.with_scheduled_at("2026-06-30T09:00:00Z");
let _sent = mb.emails.send(params).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.SendEmailRequest;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

SendEmailRequest request = SendEmailRequest.builder()
        .from("Acme <hello@yourdomain.com>")
        .to("you@yourdomain.com")
        .subject("Your weekly digest")
        .html("<p>Here is what you missed.</p>")
        .scheduledAt("2026-06-30T09:00:00Z")
        .build();

SendPingResponse response = sendping.emails().send(request);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.EmailSendAsync(new EmailMessage
{
    From = "Acme <hello@yourdomain.com>",
    To = "you@yourdomain.com",
    Subject = "Your weekly digest",
    HtmlBody = "<p>Here is what you missed.</p>",
    ScheduledAt = "2026-06-30T09:00:00Z",
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/emails' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
  "from": "Acme <hello@yourdomain.com>",
  "to": ["you@yourdomain.com"],
  "subject": "Your weekly digest",
  "html": "<p>Here is what you missed.</p>",
  "scheduled_at": "2026-06-30T09:00:00Z"
}'
```

**CLI**

```bash
sendping emails send \
  --from 'Acme <hello@yourdomain.com>' \
  --to 'you@yourdomain.com' \
  --subject 'Your weekly digest' \
  --html '<p>Here is what you missed.</p>' \
  --scheduled-at '2026-06-30T09:00:00Z'
```

## Send a batch

Deliver up to 100 distinct emails in one request by POSTing a JSON array to [POST /emails/batch](https://www.sendping.co/docs/api/emails-batch). Every item is validated before any email is sent. See [Batch sending](https://www.sendping.co/docs/emails/batch).

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.batch.send([
  { "from": "Acme <hello@yourdomain.com>", "to": ["delivered@test.sendping.co"], "subject": "Hi A", "html": "<p>Hello A</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["delivered@test.sendping.co"], "subject": "Hi B", "html": "<p>Hello B</p>" }
]);
console.log({ data, error });
```

**Ruby**

```ruby
require "sendping"

SendPing.api_key = "mb_xxxxxxxxx"

SendPing::Batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered@test.sendping.co"
    ],
    "subject": "Hi A",
    "html": "<p>Hello A</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered@test.sendping.co"
    ],
    "subject": "Hi B",
    "html": "<p>Hello B</p>"
  }
])
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->batch->send([
  [
    'from' => "Acme <hello@yourdomain.com>",
    'to' => [
      "delivered@test.sendping.co"
    ],
    'subject' => "Hi A",
    'html' => "<p>Hello A</p>"
  ],
  [
    'from' => "Acme <hello@yourdomain.com>",
    'to' => [
      "delivered@test.sendping.co"
    ],
    'subject' => "Hi B",
    'html' => "<p>Hello B</p>"
  ]
]);
```

**Python**

```python
import sendping

sendping.api_key = "mb_xxxxxxxxx"

sendping.Batch.send([
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered@test.sendping.co"
    ],
    "subject": "Hi A",
    "html": "<p>Hello A</p>"
  },
  {
    "from": "Acme <hello@yourdomain.com>",
    "to": [
      "delivered@test.sendping.co"
    ],
    "subject": "Hi B",
    "html": "<p>Hello B</p>"
  }
])
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

batch, err := client.Batch.SendEmails([]*sendping.BatchEmailRequest{
    {
        From:    "Acme <hello@yourdomain.com>",
        To:      []string{"delivered@test.sendping.co"},
        Subject: "Hi A",
        Html:    "<p>Hello A</p>",
    },
    {
        From:    "Acme <hello@yourdomain.com>",
        To:      []string{"delivered@test.sendping.co"},
        Subject: "Hi B",
        Html:    "<p>Hello B</p>",
    },
})
```

**Rust**

```rust
use sendping::{BatchEmailOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let items = vec![
    BatchEmailOptions::new(
        "Acme <hello@yourdomain.com>",
        ["delivered@test.sendping.co"],
        "Hi A",
    )
    .with_html("<p>Hello A</p>"),
    BatchEmailOptions::new(
        "Acme <hello@yourdomain.com>",
        ["delivered@test.sendping.co"],
        "Hi B",
    )
    .with_html("<p>Hello B</p>"),
];
let _batch = mb.batch.send_emails(items).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.BatchEmailRequest;
import java.util.List;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

List<BatchEmailRequest> requests = List.of(
        BatchEmailRequest.builder()
                .from("Acme <hello@yourdomain.com>")
                .to("delivered@test.sendping.co")
                .subject("Hi A")
                .html("<p>Hello A</p>")
                .build(),
        BatchEmailRequest.builder()
                .from("Acme <hello@yourdomain.com>")
                .to("delivered@test.sendping.co")
                .subject("Hi B")
                .html("<p>Hello B</p>")
                .build());

SendPingResponse response = sendping.batch().sendEmails(requests);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.EmailBatchSendAsync(new List<BatchEmailMessage>
{
    new()
    {
        From = "Acme <hello@yourdomain.com>",
        To = "delivered@test.sendping.co",
        Subject = "Hi A",
        HtmlBody = "<p>Hello A</p>",
    },
    new()
    {
        From = "Acme <hello@yourdomain.com>",
        To = "delivered@test.sendping.co",
        Subject = "Hi B",
        HtmlBody = "<p>Hello B</p>",
    },
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/emails/batch' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '[
  { "from": "Acme <hello@yourdomain.com>", "to": ["delivered@test.sendping.co"], "subject": "Hi A", "html": "<p>Hello A</p>" },
  { "from": "Acme <hello@yourdomain.com>", "to": ["delivered@test.sendping.co"], "subject": "Hi B", "html": "<p>Hello B</p>" }
]'
```

**CLI**

```bash
sendping emails batch \
  --data '[{"from":"Acme <hello@yourdomain.com>","to":["delivered@test.sendping.co"],"subject":"Hi A","html":"<p>Hello A</p>"},{"from":"Acme <hello@yourdomain.com>","to":["delivered@test.sendping.co"],"subject":"Hi B","html":"<p>Hello B</p>"}]'
```

## Retry safely with an idempotency key

When a network blip leaves you unsure whether a send went through, retry with the same `Idempotency-Key` — SendPing processes it once and replays the original response. See [Idempotency keys](https://www.sendping.co/docs/emails/idempotency).

```bash
curl -X POST 'https://www.sendping.co/api/emails' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-12345' \
  -d '{
    "from": "Acme <hello@yourdomain.com>",
    "to": ["delivered@test.sendping.co"],
    "subject": "Receipt",
    "html": "<p>Thanks!</p>"
  }'
```

## Add a contact to an audience

Grow a marketing list by creating a [contact](https://www.sendping.co/docs/audiences/contacts) inside an [audience](https://www.sendping.co/docs/audiences/overview). Contacts are the recipients of [campaigns](https://www.sendping.co/docs/campaigns/managing).

**Node.js**

```js
import { SendPing } from 'sendping';

const mb = new SendPing('mb_xxxxxxxxx');

const { data, error } = await mb.contacts.create({
  audienceId: '78261eea-8f8b-4381-83c6-79fa7120f1cf',
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace"
});
console.log({ data, error });
```

**Ruby**

```ruby
require 'net/http'
require 'uri'

uri = URI('https://www.sendping.co/api/audiences/78261eea-8f8b-4381-83c6-79fa7120f1cf/contacts')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri)
req['Authorization'] = 'Bearer mb_xxxxxxxxx'
req['Content-Type'] = 'application/json'
req.body = <<~JSON
{
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace"
}
JSON
res = http.request(req)
puts res.body
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

use SendPing\SendPing;

$sendping = SendPing::client('mb_xxxxxxxxx');

$sendping->contacts->create([
  'audienceId' => "78261eea-8f8b-4381-83c6-79fa7120f1cf",
  'email' => "ada@example.com",
  'first_name' => "Ada",
  'last_name' => "Lovelace"
]);
```

**Python**

```python
import requests

res = requests.post(
    "https://www.sendping.co/api/audiences/78261eea-8f8b-4381-83c6-79fa7120f1cf/contacts",
    headers={"Authorization": "Bearer mb_xxxxxxxxx", "Content-Type": "application/json"},
    json={
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace"
},
)
print(res.json())
```

**Go**

```go
import "github.com/shekhu10/sendping-sdks/sendping-go"

client := sendping.NewClient("mb_xxxxxxxxx")

contact, err := client.Contacts.Create(&sendping.CreateContactRequest{
    AudienceId: "78261eea-8f8b-4381-83c6-79fa7120f1cf",
    Email:      "ada@example.com",
    FirstName:  "Ada",
    LastName:   "Lovelace",
})
```

**Rust**

```rust
use sendping::{CreateContactOptions, SendPing};

let mb = SendPing::new("mb_xxxxxxxxx");

let params = CreateContactOptions::new("ada@example.com")
    .with_audience_id("78261eea-8f8b-4381-83c6-79fa7120f1cf")
    .with_first_name("Ada")
    .with_last_name("Lovelace");
let _contact = mb.contacts.create(params).await?;
```

**Java**

```java
import co.sendping.SendPing;
import co.sendping.SendPingResponse;
import co.sendping.requests.CreateContactRequest;

SendPing sendping = new SendPing("mb_xxxxxxxxx");

CreateContactRequest request = CreateContactRequest.builder()
        .audienceId("78261eea-8f8b-4381-83c6-79fa7120f1cf")
        .email("ada@example.com")
        .firstName("Ada")
        .lastName("Lovelace")
        .build();

SendPingResponse response = sendping.contacts().create(request);
```

**.NET**

```csharp
using SendPing;

ISendPing sendping = SendPingClient.Create("mb_xxxxxxxxx");

var resp = await sendping.ContactCreateAsync(new ContactCreateOptions
{
    AudienceId = "78261eea-8f8b-4381-83c6-79fa7120f1cf",
    Email = "ada@example.com",
    FirstName = "Ada",
    LastName = "Lovelace",
});
```

**cURL**

```bash
curl -X POST 'https://www.sendping.co/api/audiences/78261eea-8f8b-4381-83c6-79fa7120f1cf/contacts' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace"
}'
```

## Verify and handle a webhook

Subscribe to delivery events so your app reacts to bounces, complaints, opens, and clicks as they happen. Every payload is signed — verify the signature before trusting it. See [Webhooks](https://www.sendping.co/docs/webhooks/overview).

**Node.js**

```js
import { createHmac, timingSafeEqual } from 'node:crypto';

// Express handler — req.body is the raw request buffer
app.post('/webhooks/sendping', (req, res) => {
  // Signed content is "<timestamp>.<raw body>" — both from this request.
  const timestamp = req.header('X-SendPing-Timestamp');
  const signature = req.header('X-SendPing-Signature');
  const expected = createHmac('sha256', process.env.SENDPING_WEBHOOK_SECRET)
    .update(`${timestamp}.${req.body}`) // raw bytes, not parsed JSON
    .digest('hex');

  const ok = timestamp && signature && timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString());
  if (event.type === 'email.bounced') {
    // the recipient was auto-suppressed — update your own records
  }
  res.status(200).end();
});
```

**Python**

```python
import hmac, hashlib, json
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = "whsec_xxxxxxxxx".encode()

@app.post("/webhooks/sendping")
def handle():
    timestamp = request.headers.get("X-SendPing-Timestamp", "")
    signature = request.headers.get("X-SendPing-Signature", "")
    signed = timestamp.encode() + b"." + request.get_data()
    expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
    if not timestamp or not hmac.compare_digest(signature, expected):
        abort(401)

    event = json.loads(request.get_data())
    if event["type"] == "email.bounced":
        pass  # recipient auto-suppressed; sync your records
    return "", 200
```

> **Note:** Always compute the signature over the **raw** request body. Re-serializing parsed JSON can reorder keys or change whitespace and break verification.

## More recipes

The same endpoints cover the full set of common email use-cases. Each is a plain HTTP request you can build in any language — no framework-specific package required.

| Use case | How |
| --- | --- |
| Basic send | POST a single email to [`/emails`](https://www.sendping.co/docs/api/emails-send) with `from`, `to`, `subject`, and `html` or `text`. |
| Batch send | POST a JSON array (up to 100) to [`/emails/batch`](https://www.sendping.co/docs/api/emails-batch). |
| Attachments | Add an `attachments` array with base64 `content` or a hosted `path` URL — see [Attachments](https://www.sendping.co/docs/emails/attachments). |
| Inline images (CID) | Reference an attachment from your HTML via `cid:` and set the attachment's `content_id` — see [Attachments](https://www.sendping.co/docs/emails/attachments). |
| Templates | Send a stored template by `template_id` (or nested `template: { id | alias }`) with `variables` instead of inline `html` — see [Templates](https://www.sendping.co/docs/templates/overview). |
| Scheduling | Pass an ISO 8601 or natural-language `scheduled_at` to hold the email for later delivery — see [Schedule email](https://www.sendping.co/docs/emails/schedule). |
| Idempotency | Send an `Idempotency-Key` header to deduplicate retries — see [Idempotency keys](https://www.sendping.co/docs/emails/idempotency). |
| Contact form | Take form input on your backend and POST one (or a batch of) email(s) to SendPing. |
| Audiences & contacts | Create and manage list members under [`/audiences`](https://www.sendping.co/docs/audiences/overview) and their [contacts](https://www.sendping.co/docs/audiences/contacts). |
| Domains | Create and verify sending domains via [`/domains`](https://www.sendping.co/docs/domains/managing). |
| Double opt-in | On signup, send a confirmation email with a tokenized link; only add the [contact](https://www.sendping.co/docs/audiences/contacts) once they click it. |
| Webhooks | Subscribe to delivery events and verify each signed payload — see [Webhooks](https://www.sendping.co/docs/webhooks/overview). |
| Prevent threading (Gmail) | Vary the `subject` or set custom `headers` (e.g. a unique `X-Entity-Ref-ID`) so Gmail doesn't collapse messages into one thread. |

## Where to go next

- Browse the full [Send an email](https://www.sendping.co/docs/api/emails-send) reference for every body field (`cc`, `bcc`, `reply_to`, `headers`).
- Set up a sending domain and DNS in [Domains](https://www.sendping.co/docs/domains/managing).
- Send marketing email to a list with [Campaigns](https://www.sendping.co/docs/campaigns/managing).
- See what is and isn't available yet in the [roadmap](https://www.sendping.co/docs/resources/sdks).
