# Sendfinch — Send Email via SMTP, Resend, SendGrid & Mailgun (`cybermax/send-email`) Actor

Send email from Apify runs, webhooks and AI agents through your own SMTP, Resend, SendGrid, Mailgun or Postmark: mail merge from datasets, result digests with CSV, attachments, per-recipient status. $0.001 per email; failures free.

- **URL**: https://apify.com/cybermax/send-email.md
- **Developed by:** [CyberMax](https://apify.com/cybermax) (community)
- **Categories:** Automation, Integrations, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 email sents

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Sendfinch: Send Email API via SMTP, Resend, SendGrid, Mailgun & Postmark

**Send email from Apify runs, webhooks, schedules and AI agents, to anyone, from your own domain.** Sendfinch sends
through **your own SMTP server** (Gmail / Google Workspace, Microsoft 365, Amazon SES, Zoho, Fastmail, any host) or
your **Resend, SendGrid, Mailgun or Postmark** API key. One Actor covers every email job an automation needs:

- **Single email:** alert, notification or report to one or many addresses, with cc/bcc, reply-to and attachments.
- **Digest:** email the results of any Actor run as a clean table with the full data attached as CSV. Monitor mode
  sends only the rows that are new since the last run.
- **Mail merge:** one personalised email per dataset row (`Hi {{firstName}}`), with invalid and duplicate addresses
  skipped for free.
- **List of emails:** an AI agent or script sends several different emails in one call.

Every email gets a **delivery record** with the provider's message ID, the SMTP/HTTP response, rejected recipients and
a readable error when something fails.

**$0.001 per email sent** (per recipient your provider accepts). Dry runs, failed sends, invalid addresses and empty
digests are free. No start fee, no subscription.

### Why Sendfinch

- **Email anyone, not just yourself.** The free Apify "Send Email" utility only delivers to the Apify account
  owner's own address (its runs fail with *"Users can only send an email to their own email address"*). Sendfinch
  sends to customers, teammates and clients, from your domain, so replies come back to you and SPF/DKIM pass.
- **Five providers, one input.** Switch from SMTP to Resend, SendGrid, Mailgun (US/EU) or Postmark by changing one
  field; the templates, digests and reports stay the same.
- **Made for chaining Actors.** Point `datasetId` at `{{resource.defaultDatasetId}}` in an Apify webhook or
  integration and every finished run is emailed as a table + CSV. Attach a PDF or screenshot that another Actor saved
  in its key-value store.
- **Safe by default.** Credentials are Apify secret inputs (stored encrypted, never logged). SMTP logins are checked
  before the first email, so a wrong password fails fast with a hint ("Gmail and Outlook need an app password") and
  nothing is sent or charged. `maxEmails` caps every run (default 100). `dryRun` previews every rendered email for free.
- **Reliable delivery.** Rate limits (HTTP 429), provider outages (5xx) and temporary SMTP errors (4xx) are retried
  with backoff; permanent errors are reported per email, never silently dropped. Pooled SMTP connections and parallel
  API sends keep runs short.
- **Better inbox placement.** A plain-text part is generated from your HTML automatically, and `unsubscribeUrl` adds
  the one-click `List-Unsubscribe` headers that Gmail and Yahoo expect from bulk senders.
- **Tiny and fast.** 256 MB, no browser; a single email run takes about 4 s on Apify.

### Ready-made inputs (first try in one click)

**Free dry run, no credentials needed:** renders a two-row mail merge into the dataset, sends nothing, charges nothing.

```json
{
  "mode": "merge",
  "dryRun": true,
  "from": "Acme Alerts <alerts@acme.com>",
  "subject": "Hi {{firstName}}, your {{plan}} report is ready",
  "html": "<p>Hi {{firstName}},</p><p>Your {{plan}} report for <b>{{company}}</b> is ready.</p>",
  "items": [
    { "email": "ann@example.com", "firstName": "Ann", "company": "Acme Corp", "plan": "Pro" },
    { "email": "bob@example.com", "firstName": "Bob", "company": "Beta LLC", "plan": "Team" }
  ]
}
```

**One alert email through Resend** (swap `provider` for `smtp`, `sendgrid`, `mailgun` or `postmark`):

```json
{ "provider": "resend", "apiKey": "re_...", "from": "Alerts <alerts@yourdomain.com>", "to": "you@yourdomain.com", "subject": "Run finished", "text": "Your scheduled run finished." }
```

### Example

Email yourself the results of any Actor run (here: new articles found by Inkpluck), every morning, only the new ones:

```json
{
  "provider": "resend",
  "apiKey": "re_...",
  "from": "Reports <reports@yourdomain.com>",
  "to": "you@yourdomain.com",
  "datasetId": "{{resource.defaultDatasetId}}",
  "digestFields": ["title", "siteName", "publishDate", "url"],
  "onlyNew": true,
  "monitorName": "morning-news",
  "idField": "url"
}
```

Output (one record per email; this is the real record from a test run on Apify, recipient redacted; `charged` is 0
only because the developer's own runs aren't billed, so for you it would be 1):

```json
{
  "index": 0,
  "mode": "digest",
  "provider": "smtp",
  "to": [
    "you@yourdomain.com"
  ],
  "cc": [],
  "bcc": [],
  "subject": "1 new result (2026-09-24)",
  "attachments": [
    "results-2026-09-24.csv"
  ],
  "recipients": 1,
  "rows": 1,
  "ok": true,
  "messageId": "<a4c8c4d3-b13b-d89f-f4e5-d9e6ebc24f04@ethereal.email>",
  "status": "250 Accepted [STATUS=new MSGID=arRuDHr.3nbIbauCarRvOvLvAxtdqkONAAAABlF4myyownocuD12LRj-bfc]",
  "charged": 0,
  "ms": 1575,
  "sentAt": "2026-09-24T00:30:50.325Z"
}
```

Mail merge from rows (or from a dataset of leads):

```json
{
  "provider": "smtp",
  "smtpHost": "smtp.gmail.com",
  "smtpUser": "you@yourdomain.com",
  "smtpPassword": "<app password>",
  "from": "You <you@yourdomain.com>",
  "mode": "merge",
  "items": [
    { "email": "ann@example.com", "firstName": "Ann", "company": "Acme Corp", "plan": "Pro" },
    { "email": "raj@example.org", "firstName": "Raj", "company": "Northwind", "plan": "Team" }
  ],
  "subject": "Hi {{firstName}}, your {{plan}} report is ready",
  "html": "<p>Hi {{firstName}},</p><p>Your {{plan}} report for <b>{{company}}</b> is ready.</p>",
  "dryRun": true
}
```

With `dryRun: true` the dataset shows each rendered email (recipient, subject, text and HTML) and nothing is sent.
More examples in [`examples/`](examples).

### Input

| Field | What it does |
|---|---|
| `provider` | `smtp` (default), `resend`, `sendgrid`, `mailgun`, `postmark` |
| `from` | Sender, e.g. `Acme Alerts <alerts@acme.com>`; must be allowed by your SMTP login or verified with your provider |
| `to`, `cc`, `bcc`, `replyTo` | Comma-separated addresses (`Name <a@b.com>` works) |
| `subject`, `html`, `text` | Content with `{{field}}` placeholders (`{{company.name}}`, `{{tags.0}}`; `{{{field}}}` = raw HTML) |
| `mode` | `auto`, `single`, `messages`, `merge`, `digest` (auto picks from the other fields) |
| `datasetId` / `items` | Rows for merge or digest: an Apify dataset (e.g. another run's) or inline JSON |
| `toField`, `nameField` | Merge: which row fields hold the address and the name (default: `email`, `emailAddress`, `contactEmail`…) |
| `digestFields`, `digestTitle`, `maxDigestRows`, `attachCsv`, `skipIfEmpty` | Digest layout |
| `onlyNew`, `monitorName`, `idField` | Monitor mode for scheduled runs |
| `messages` | A list of different emails `[{to, subject, html, text, cc, bcc, replyTo, attachments}]` |
| `attachments`, `keyValueStoreId` | Files by URL, from an Apify key-value store record, inline text or base64 (20 MB total) |
| `variables`, `headers`, `unsubscribeUrl`, `sendSeparately` | Extra placeholders, custom headers, List-Unsubscribe, one email per `to` address |
| `smtpHost`, `smtpPort`, `smtpSecure`, `smtpUser`, `smtpPassword` | SMTP settings (password is a secret input) |
| `apiKey`, `mailgunDomain`, `region`, `postmarkStream` | API provider settings (key is a secret input) |
| `dryRun`, `maxEmails`, `maxConcurrency`, `delayBetweenEmailsMs` | Preview, safety cap, speed and rate limiting |

Common SMTP settings: Gmail/Google Workspace `smtp.gmail.com:587` with an
[app password](https://support.google.com/accounts/answer/185833); Microsoft 365 `smtp.office365.com:587`; Amazon SES
`email-smtp.<region>.amazonaws.com:587` with SMTP credentials; Zoho `smtp.zoho.com:587`; Resend
`smtp.resend.com:587` (user `resend`, password = API key); SendGrid `smtp.sendgrid.net:587` (user `apikey`).

### Output

One dataset record per email: `ok`, `to`/`cc`/`bcc`, `subject`, `recipients`, `charged`, `messageId`, `status`,
`rejected` (SMTP recipients refused), `attachments`, `rows` (digest), `error`, `ms`, `sentAt`; in dry runs also the
rendered `text` and `html`. Skipped rows (invalid or duplicate addresses, bad messages) are records with `ok: false`
and the reason. The `SUMMARY` record in the run's key-value store has the totals (`prepared`, `sent`, `failed`,
`skipped`). The run fails, uncharged, only when every email failed or the input/credentials are wrong.

### Pricing

**$0.001 per email sent** (event `email-sent`, one per recipient your provider accepted: To + Cc + Bcc). 1,000
emails cost $1. Dry runs, failed sends, invalid or duplicate addresses and "no new rows" digests are free. You also
pay your email provider as usual (most have free tiers: Resend 3,000/month, Amazon SES from $0.10 per 1,000).

For comparison (Apify Store, 24 Sep 2026): the free Apify utility can only email the account owner; the other SMTP
sender on the Store charges $0.01 per email plus a start fee.

### Benchmark

Same job for each Actor: one HTML email to one external address (a test SMTP inbox), default settings, run on Apify on
24 Sep 2026. Rivals are the email-sending Actors on the Apify Store.

| | Delivered | Sends to any address | Run time | Memory | Price per email | Message ID + SMTP response in output |
|---|---|---|---|---|---|---|
| **Sendfinch** | **1/1** | **yes** | **4.0 s** | **256 MB** | **$0.001** | **yes** |
| Apify Send Email (free) | 0/1: run failed, "Users can only send an email to their own email address" | no | 1.4 s | 1,024 MB | free | no |
| SMTP bulk sender | 1/1 | yes | 10.2 s | 4,096 MB | $0.01 + start fee | no (status text only) |

Apify's public run statistics for the free utility show 10,462 failed runs out of 66,453 in the last 30 days (16%).

### Run it on a schedule (monitor mode)

Get a daily email with only what's new from any scraper:

1. Create a task from your scraper and a task from Sendfinch with `"datasetId"` set to the scraper task's dataset,
   or simpler: add an **integration** to the scraper task: *Integrations → Connect Actor or task → cybermax/send-email*, input
   ```json
   { "provider": "sendgrid", "apiKey": "SG....", "from": "Bot <bot@acme.com>", "to": "team@acme.com",
     "datasetId": "{{resource.defaultDatasetId}}", "onlyNew": true, "monitorName": "new-leads", "idField": "url" }
   ```
2. Put the scraper task on an Apify **Schedule** (e.g. `0 7 * * *`, 07:00 daily).
3. Every run, Sendfinch compares the rows with what it already emailed and sends one digest of the new ones. No new
   rows: no email, no charge.

Mail merge + monitor mode (`mode: "merge"`, `onlyNew: true`) emails each new address exactly once, e.g. a welcome
email to every new sign-up in a dataset.

### Integrations

- **Apify webhooks and integrations:** run Sendfinch when any Actor or task finishes, with
  `{{resource.defaultDatasetId}}` (digest of results) or `{{resource.defaultKeyValueStoreId}}` (attach its `OUTPUT`
  PDF/screenshot via `keyValueStoreId` + `attachments: [{"key": "OUTPUT", "filename": "page.png"}]`).
- **Make:** *Apify → Run an Actor* (`cybermax/send-email`) with the JSON input; map fields from earlier modules
  into `to`, `subject`, `html`.
- **Zapier:** *Apify → Run Actor* action; e.g. new Google Sheets row → personalised email from your domain.
- **n8n:** the Apify node (*Run Actor*) with `{"to": "{{ $json.email }}", "subject": "…", "html": "…"}`.
- **Google Sheets:** export a sheet as CSV rows into `items` (or a dataset) and use `mode: "merge"`; results
  (`ok`, `messageId`, `error`) come back as a dataset you can import with `IMPORTDATA`.
- **JavaScript:**
  ```js
  import { ApifyClient } from 'apify-client';
  const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
  const run = await client.actor('cybermax/send-email').call({
    provider: 'postmark', apiKey: process.env.POSTMARK_TOKEN, from: 'Acme <hi@acme.com>',
    to: 'ann@example.com', subject: 'Your export is ready', html: '<p>Download it <a href="https://acme.com/x">here</a>.</p>',
  });
  const { items } = await client.dataset(run.defaultDatasetId).listItems();
  console.log(items[0].ok, items[0].messageId);
  ```
- **Python:**
  ```python
  from apify_client import ApifyClient
  client = ApifyClient("<APIFY_TOKEN>")
  run = client.actor("cybermax/send-email").call(run_input={
      "provider": "smtp", "smtpHost": "smtp.office365.com", "smtpUser": "me@acme.com", "smtpPassword": "<password>",
      "from": "me@acme.com", "to": "boss@acme.com", "subject": "Weekly numbers", "text": "See attached.",
      "attachments": [{"filename": "numbers.csv", "text": csv_string}]})
  print(next(client.dataset(run["defaultDatasetId"]).iterate_items())["ok"])
  ```

### Use it from an AI agent (MCP)

- **Apify MCP server:** connect your MCP client (Claude, Cursor, VS Code, n8n…) to
  `https://mcp.apify.com/?tools=cybermax/send-email` with your Apify token. The agent gets one tool that takes the
  input above (put your provider key in the agent's configuration, not in prompts).
- **Local MCP server** (`send-email-mcp`, Node 18+): keeps your provider credentials in environment variables, so
  the agent only chooses recipients, subject and body, and previews with `dry_run` before sending.
- **API (synchronous):** `POST https://api.apify.com/v2/acts/cybermax~send-email/run-sync-get-dataset-items?token=<TOKEN>`
  returns the delivery records.
- **Typical agent prompts:** "Email this summary to the team", "Send each lead in this dataset a personalised intro",
  "Email me the new jobs every morning", "Send the PDF you just made to the client".

### When NOT to use it

- **Cold spam or bought lists.** Sendfinch sends through your own account; your provider's rules apply and spam gets
  your domain blocked. Use it for your own customers, sign-ups, team and people who expect your email. Add
  `unsubscribeUrl` for anything that isn't a one-to-one message.
- **You have no email provider.** You need an SMTP login or an API key. Free options: Resend (3,000 emails/month),
  Brevo, Gmail with an app password (about 500/day); check each provider's current limits.
- **Very large newsletters** (100,000+). Use your provider's own campaign tool; Sendfinch caps a run at 5,000 emails.
- **Receiving or reading email.** Sendfinch only sends.
- **Sending from port 25.** Cloud hosts block it; use 587 or 465.

### FAQ

**Is my password safe?** `smtpPassword` and `apiKey` are Apify secret inputs: encrypted at rest, hidden in the
Console and API after saving, and never written to logs or the dataset.

**Am I charged when an email bounces later?** Sendfinch charges when your provider accepts the email; later bounces
are reported by your provider. Recipients the SMTP server refuses during sending are listed in `rejected` and not
charged.

**Can I send HTML with images?** Yes: use absolute image URLs in the HTML, or attach files.

**How do I test without sending?** Set `dryRun: true`: every email is rendered into the dataset, nothing is sent,
nothing is charged, and no credentials are needed.

### Also from CyberMax

- [Printwren: HTML to PDF & URL to PDF API](https://huggingface.co/spaces/CyberMax-tools/docs-pages-as-pdfs?soon=html-to-pdf): make the PDF, then email it
- [SnapScout: Website Screenshot API for AI Agents](https://apify.com/cybermax/website-screenshot): email page snapshots
- [Hollerdex: Website Contact Scraper](https://apify.com/cybermax/website-contacts): find the addresses first
- [Inkpluck: Article Extractor](https://apify.com/cybermax/article-extract): email a daily reading digest
- [HireHeat Jobs: Career Site Job Listings](https://apify.com/cybermax/career-site-jobs): daily job alerts by email

All tools: [apify.com/cybermax](https://apify.com/cybermax).

© 2026 CyberMax. All rights reserved.

# Actor input Schema

## `provider` (type: `string`):

How to send: your own SMTP server (Gmail/Google Workspace, Microsoft 365, Amazon SES, Zoho, Fastmail, any host) or the API of Resend, SendGrid, Mailgun or Postmark. Sendfinch never sends through shared servers: mail goes out from your account and your domain.

## `from` (type: `string`):

Sender, e.g. "Acme Alerts <alerts@acme.com>". Must be an address your SMTP login or provider lets you send from (a verified sender/domain).

## `to` (type: `string`):

Recipient(s), separated by commas: "ann@acme.com, Bob <bob@acme.com>". In digest mode, who gets the digest. Not used in merge mode (addresses come from each row).

## `subject` (type: `string`):

Subject line. {{field}} placeholders are filled from each row (merge), from "variables", or with {{count}}/{{date}} (digest).

## `html` (type: `string`):

HTML body with {{field}} placeholders (values are HTML-escaped; use {{{field}}} for raw HTML). In digest mode, put {{{table}}} where the results table goes (leave empty for the default layout). A plain-text part is generated automatically unless you set "text".

## `text` (type: `string`):

Plain-text body (or the only body). Same {{field}} placeholders as HTML.

## `mode` (type: `string`):

auto = "messages" if given, else digest if a dataset/items are given, else one email to "to". single = one email to "to". messages = a list of different emails. merge = one personalised email per dataset row (mail merge). digest = one email containing all rows as a table + CSV (e.g. the results of another Actor run).

## `datasetId` (type: `string`):

An Apify dataset to read rows from, e.g. another run's defaultDatasetId. In an Apify webhook or integration use {{resource.defaultDatasetId}} to email the results of any Actor run.

## `items` (type: `array`):

Rows given directly instead of a dataset: a JSON array of objects, e.g. \[{"email": "ann@acme.com", "firstName": "Ann"}].

## `toField` (type: `string`):

Row field holding the recipient address. Default: the first of email, emailAddress, contactEmail, emails.0, Email.

## `nameField` (type: `string`):

Optional row field used as the recipient's display name, e.g. firstName or fullName.

## `digestFields` (type: `array`):

Which fields to show in the digest table (dotted paths allowed, e.g. company.name). Default: the first 6 simple fields. The attached CSV always has every field.

## `digestTitle` (type: `string`):

Heading above the digest table. Default "12 rows" ("12 new rows" in monitor mode); placeholders {{count}}, {{total}}, {{date}}.

## `maxDigestRows` (type: `integer`):

Rows beyond this go only into the attached CSV.

## `attachCsv` (type: `boolean`):

Adds results-YYYY-MM-DD.csv with every row and field.

## `skipIfEmpty` (type: `boolean`):

When there are no rows, send nothing (free).

## `maxItems` (type: `integer`):

Upper limit on rows read from the dataset.

## `onlyNew` (type: `boolean`):

Remembers what earlier runs with the same monitor name already emailed. Digest: the email contains only new rows (nothing is sent when there are none). Merge: each address (or idField) gets the email once. Put it on an Apify Schedule for daily "what's new" emails.

## `monitorName` (type: `string`):

Keeps separate memories for different schedules, e.g. "new-leads" and "price-drops".

## `idField` (type: `string`):

Field that identifies a row across runs (e.g. url or id). Default: digest compares whole rows; merge compares the recipient address.

## `messages` (type: `array`):

Send several different emails in one run: \[{"to": "a@x.com", "subject": "...", "html": "...", "text": "...", "cc": "...", "bcc": "...", "replyTo": "...", "attachments": \[{"filename": "a.txt", "text": "..."}]}]. Messages with problems are skipped for free and reported.

## `variables` (type: `object`):

Extra values for {{placeholders}} in every email, e.g. {"product": "Acme"}.

## `sendSeparately` (type: `boolean`):

One email per address instead of one email with everyone in To (recipients don't see each other).

## `cc` (type: `string`):

Comma-separated addresses.

## `bcc` (type: `string`):

Comma-separated addresses (in merge mode added to every email, e.g. a CRM dropbox).

## `replyTo` (type: `string`):

Where replies go, if not the From address.

## `attachments` (type: `array`):

Files added to every email: {"filename": "report.pdf", "url": "https://..."}, {"key": "OUTPUT", "filename": "report.pdf"} (a record from the key-value store set below, e.g. a PDF or screenshot made by another Actor), {"filename": "a.txt", "text": "..."} or {"filename": "a.png", "contentBase64": "..."}. 20 MB total.

## `keyValueStoreId` (type: `string`):

An Apify key-value store to read attachment records from (e.g. another run's defaultKeyValueStoreId holding a PDF, screenshot or report). Attachments given as {"key": "OUTPUT", "filename": "report.pdf"} are read from here.

## `headers` (type: `object`):

Custom headers, e.g. {"X-Campaign": "q3"}.

## `unsubscribeUrl` (type: `string`):

Adds List-Unsubscribe (and one-click List-Unsubscribe-Post for https URLs) so Gmail/Yahoo show an unsubscribe button. Placeholders allowed: https://acme.com/unsub?e={{email}}.

## `smtpHost` (type: `string`):

e.g. smtp.gmail.com, smtp.office365.com, email-smtp.us-east-1.amazonaws.com, smtp.zoho.com, smtp.resend.com, smtp.sendgrid.net.

## `smtpPort` (type: `integer`):

587 (STARTTLS, default) or 465 (TLS). Port 25 is usually blocked in the cloud.

## `smtpSecure` (type: `boolean`):

Default: on for port 465, off (STARTTLS) otherwise.

## `smtpUser` (type: `string`):

Usually your full email address.

## `smtpPassword` (type: `string`):

Stored encrypted by Apify (secret input). Gmail/Google Workspace and Outlook need an app password, not your normal password.

## `smtpAllowSelfSigned` (type: `boolean`):

Only for your own mail server with a self-signed certificate.

## `apiKey` (type: `string`):

Your provider API key (Postmark: server token). Stored encrypted by Apify (secret input).

## `mailgunDomain` (type: `string`):

e.g. mg.acme.com (Mailgun only).

## `region` (type: `string`):

eu for Mailgun EU domains or SendGrid EU subusers.

## `postmarkStream` (type: `string`):

Default outbound (transactional).

## `dryRun` (type: `boolean`):

Renders every email (recipients, subject, text and HTML) into the dataset without sending or charging. No credentials needed. Turn it off to send.

## `maxEmails` (type: `integer`):

Safety cap on emails sent in one run.

## `maxConcurrency` (type: `integer`):

How many emails are sent at the same time.

## `delayBetweenEmailsMs` (type: `integer`):

Per parallel sender; use it to stay under your provider's rate limit (e.g. Gmail).

## `autoText` (type: `boolean`):

Adds a plain-text version of HTML emails (better deliverability).

## Actor input object example

```json
{
  "provider": "smtp",
  "from": "Acme Alerts <alerts@acme.com>",
  "subject": "Hi {{firstName}}, your {{plan}} report is ready",
  "html": "<p>Hi {{firstName}},</p><p>Your {{plan}} report for <b>{{company}}</b> is ready.</p>",
  "mode": "merge",
  "items": [
    {
      "email": "ann@example.com",
      "firstName": "Ann",
      "company": "Acme Corp",
      "plan": "Pro"
    },
    {
      "email": "raj@example.org",
      "firstName": "Raj",
      "company": "Northwind",
      "plan": "Team"
    }
  ],
  "maxDigestRows": 200,
  "attachCsv": true,
  "skipIfEmpty": true,
  "maxItems": 5000,
  "onlyNew": false,
  "monitorName": "default",
  "sendSeparately": false,
  "smtpPort": 587,
  "smtpAllowSelfSigned": false,
  "region": "us",
  "dryRun": true,
  "maxEmails": 100,
  "maxConcurrency": 3,
  "delayBetweenEmailsMs": 0,
  "autoText": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "from": "Acme Alerts <alerts@acme.com>",
    "subject": "Hi {{firstName}}, your {{plan}} report is ready",
    "html": "<p>Hi {{firstName}},</p><p>Your {{plan}} report for <b>{{company}}</b> is ready.</p>",
    "mode": "merge",
    "items": [
        {
            "email": "ann@example.com",
            "firstName": "Ann",
            "company": "Acme Corp",
            "plan": "Pro"
        },
        {
            "email": "raj@example.org",
            "firstName": "Raj",
            "company": "Northwind",
            "plan": "Team"
        }
    ],
    "dryRun": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("cybermax/send-email").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "from": "Acme Alerts <alerts@acme.com>",
    "subject": "Hi {{firstName}}, your {{plan}} report is ready",
    "html": "<p>Hi {{firstName}},</p><p>Your {{plan}} report for <b>{{company}}</b> is ready.</p>",
    "mode": "merge",
    "items": [
        {
            "email": "ann@example.com",
            "firstName": "Ann",
            "company": "Acme Corp",
            "plan": "Pro",
        },
        {
            "email": "raj@example.org",
            "firstName": "Raj",
            "company": "Northwind",
            "plan": "Team",
        },
    ],
    "dryRun": True,
}

# Run the Actor and wait for it to finish
run = client.actor("cybermax/send-email").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "from": "Acme Alerts <alerts@acme.com>",
  "subject": "Hi {{firstName}}, your {{plan}} report is ready",
  "html": "<p>Hi {{firstName}},</p><p>Your {{plan}} report for <b>{{company}}</b> is ready.</p>",
  "mode": "merge",
  "items": [
    {
      "email": "ann@example.com",
      "firstName": "Ann",
      "company": "Acme Corp",
      "plan": "Pro"
    },
    {
      "email": "raj@example.org",
      "firstName": "Raj",
      "company": "Northwind",
      "plan": "Team"
    }
  ],
  "dryRun": true
}' |
apify call cybermax/send-email --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cybermax/send-email"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EJeN8AenOorjaPfsi/builds/CGMWkuH3HAVFi3qjW/openapi.json
