Send email from Todoist when a task is completed, created, or updated by connecting Todoist webhooks to a small server-side integration that calls the Volanea email API. There is no native Volanea app, Todoist Marketplace plugin, or one-click Todoist installation for this workflow—but Todoist webhooks provide the event source needed for a robust custom integration.

The important distinction is architectural: Todoist can notify your application that something changed, while your application decides whether that change should produce an email and then calls Volanea with a protected API key. That receiver can be a Node.js service, serverless function, Cloudflare Worker, or an automation platform that can securely store credentials and make authenticated HTTP requests.

This guide uses a practical trigger: a Todoist task is completed. In Todoist’s webhook event vocabulary, that event is item:completed. The pattern is equally useful for task creation (item:added) or task updates (item:updated), but completion is a safer default for operational messages because it represents an explicit finished action.

What this Todoist email integration does

The finished workflow has four parts:

  1. A user completes a task in Todoist.
  2. Todoist sends an HTTPS webhook payload to your endpoint with event_name: "item:completed" and the affected task in event_data.
  3. Your endpoint verifies that the request came from Todoist, applies your business rules, and creates a stable idempotency key.
  4. Your endpoint sends a transactional email through Volanea’s POST /v1/send endpoint.

That middle service matters. A Todoist webhook is an inbound callback to your URL, not a generic workflow action where you can safely expose a third-party bearer token. Volanea’s key must remain on a server or in a secret manager; it must never be embedded in a Todoist task, client-side script, public webhook URL, browser extension, or repository.

A useful real-world example is a project handoff process. Your team completes a task named Send onboarding summary, adds a label such as email-customer, and includes an email address plus a short message in the task description. Your receiver checks the label, extracts only the expected fields, and sends a branded message from your verified Volanea domain.

The Todoist trigger: task completed

Todoist’s API and integrations use tasks as the central work item. In webhook payloads, task events use the historical item:* event names. For this guide, subscribe to item:completed.

A completion-triggered email works well for:

  • notifying a customer that an internal action is finished;
  • sending an owner a project-status confirmation;
  • confirming that a fulfillment, review, or follow-up task was completed;
  • alerting a manager when a high-priority task closes;
  • sending a carefully controlled internal escalation message.

It is less suitable for bulk marketing, recurring newsletters, or any workflow where simply checking off a task could email a large external audience. Treat Todoist as the trigger source—not as a contact database or marketing-consent system.

Why completion is usually safer than task creation

item:added fires when a task is created. That can be useful for notifications such as “a new support follow-up task was logged,” but task creation often happens before the task has final labels, a stable description, an assignee, or the full recipient information your email needs.

item:updated is more flexible but can be noisy. Editing a task title, moving it between sections, adding a label, setting a due date, or modifying the description can all create an update path. If you use item:updated, make the rule deliberately narrow—for example, send only when a task gains a specific label and has not already been processed.

With item:completed, you can make the intent obvious: a task in a dedicated project, with a dedicated label, is completed only when it is ready to cause an email.

Registering the webhook

Todoist offers webhooks as part of its developer platform. Configure the callback URL on a Todoist developer application and subscribe to the event you need. For a personal/internal integration, you can create an app for your own Todoist account; for a product used by other Todoist users, implement Todoist OAuth and associate each connected user with the appropriate integration configuration.

Your callback URL must be a publicly reachable HTTPS endpoint. It should be a dedicated endpoint such as:

https://automation.example.com/webhooks/todoist

Do not point Todoist at the Volanea API endpoint directly. The Todoist webhook format and Volanea send format are different, and Todoist is not a place to store or expose the Authorization: Bearer credential Volanea requires.

Understand the Todoist webhook payload before mapping it

A Todoist webhook delivery includes an event name and an event-data object. For a completed task, the payload shape is centered on event_name, event_data, user_id, the delivery metadata, and the user or integration that initiated the event. Exact optional fields can differ by event type, task state, account capabilities, and API evolution, so production code should validate only the fields it truly depends on.

A representative item:completed payload looks like this:

{
  "event_name": "item:completed",
  "user_id": "123456789",
  "event_data": {
    "id": "6X4Vw2Hfmg73Q2XR",
    "user_id": "123456789",
    "project_id": "6X4Vw2Hfmg73Q2XR",
    "section_id": null,
    "parent_id": null,
    "content": "Send onboarding summary",
    "description": "email: customer@example.com\nname: Jordan Lee\nmessage: Your onboarding checklist is complete.",
    "labels": ["email-customer"],
    "priority": 3,
    "due": null,
    "deadline": null,
    "completed_at": "2026-10-15T14:22:31.000000Z"
  },
  "initiator": {
    "id": "123456789"
  },
  "version": "9"
}

Treat that example as a schema guide, not as permission to assume every field always exists. A task may have no description, no due date, no section, no labels, or a different set of optional attributes. In particular, do not build a send rule that assumes due, deadline, section_id, or description is always populated.

Also note what the task payload does not reliably contain: a complete, trusted recipient profile. Todoist is a task system. If you put a recipient email address in task content or a description, you must validate it before sending, control who can edit that task, and avoid exposing sensitive customer details in broadly shared projects.

A recommended task convention for email sends

The smallest dependable integration has a predictable task convention. Rather than attempting to infer a recipient from every completed task, dedicate a label and use a structured description.

For example:

Task content
Send onboarding summary

Labels
email-customer

Description
email: customer@example.com
name: Jordan Lee
message: Your onboarding checklist is complete. Reply if you need help.

This design provides three safeguards:

  • The email-customer label is an explicit opt-in switch for the workflow.
  • The description is easy for a human to edit without changing application code.
  • Your receiver can reject incomplete or unsafe input before it calls Volanea.

For higher-volume or more sensitive uses, do not put raw customer addresses in Todoist. Store a customer ID in the task instead, then look up the recipient and consent status in your own application database. For example, a task description could contain customer_id: cus_12345, while the middleware obtains the approved recipient address and preferred name from your CRM or application.

Use an allowlist, not an accidental trigger

At minimum, require all of the following before a send:

  1. The event is exactly item:completed.
  2. The task carries email-customer.
  3. The task belongs to the expected project, if you use a dedicated sending project.
  4. The recipient address passes validation.
  5. The task has not already been sent for the same completion event.

This prevents an unrelated completed task from turning into an external message simply because somebody used a similar phrase in its title.

If the description is user-entered, enforce a maximum message length. Escape user-controlled text before embedding it in HTML. A task description can contain characters that are harmless as Todoist text but unsafe if inserted directly into an HTML email body.

Send email from Todoist with a secure Node.js receiver

The following Express example receives the Todoist webhook, checks Todoist’s HMAC signature against the raw request body, evaluates the task label, parses a small key-value description, validates the destination, and calls Volanea.

It uses the Todoist delivery identifier as part of Volanea’s Idempotency-Key. This is essential: webhook systems can retry a delivery after connection failures, non-2xx responses, or timeouts. Without idempotency, a webhook retry can result in a second email.

import express from "express";
import crypto from "node:crypto";

const app = express();

// Keep the raw bytes. Signature validation must use the unmodified body.
app.post(
  "/webhooks/todoist",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const todoistSignature = req.get("X-Todoist-Hmac-SHA256") || "";
    const deliveryId = req.get("X-Todoist-Delivery-ID") || "";

    // TODOIST_CLIENT_SECRET belongs to your Todoist developer app.
    const expectedSignature = crypto
      .createHmac("sha256", process.env.TODOIST_CLIENT_SECRET)
      .update(req.body)
      .digest("base64");

    const received = Buffer.from(todoistSignature, "utf8");
    const expected = Buffer.from(expectedSignature, "utf8");

    if (
      !todoistSignature ||
      received.length !== expected.length ||
      !crypto.timingSafeEqual(received, expected)
    ) {
      return res.status(401).json({ error: "Invalid Todoist signature" });
    }

    let payload;
    try {
      payload = JSON.parse(req.body.toString("utf8"));
    } catch {
      return res.status(400).json({ error: "Invalid JSON" });
    }

    // Concrete Todoist trigger: a task has been completed.
    if (payload.event_name !== "item:completed") {
      return res.sendStatus(204);
    }

    const task = payload.event_data || {};
    const labels = Array.isArray(task.labels) ? task.labels : [];

    // Only specifically marked tasks may generate customer email.
    if (!labels.includes("email-customer")) {
      return res.sendStatus(204);
    }

    // Parse description lines such as: email: customer@example.com
    const values = Object.fromEntries(
      String(task.description || "")
        .split(/\r?\n/)
        .map((line) => line.split(/:\s*(.*)/s))
        .filter(([key, value]) => key && value)
        .map(([key, value]) => [key.trim().toLowerCase(), value.trim()])
    );

    const to = values.email;
    const name = values.name || "there";
    const message = values.message || "Your requested task is complete.";

    // Keep recipient validation strict. Replace with your approved validator
    // or a database lookup for production customer mail.
    if (!to || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(to)) {
      return res.status(422).json({ error: "Task description has no valid email" });
    }

    const escapeHtml = (value) =>
      String(value)
        .replaceAll("&", "&")
        .replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll('"', "&quot;")
        .replaceAll("'", "&#039;");

    // Field mapping:
    // Todoist event_data.content     -> email subject context
    // Todoist description name       -> greeting
    // Todoist description message    -> email body
    // Todoist description email      -> Volanea recipient
    // Todoist delivery/task IDs      -> idempotency protection
    const subject = `Update: ${task.content || "Todoist task completed"}`;
    const html = `<p>Hi ${escapeHtml(name)},</p><p>${escapeHtml(message)}</p>`;

    const idempotencyKey =
      deliveryId || `todoist-completed-${task.id}-${task.completed_at || "unknown"}`;

    const response = await fetch("https://api.volanea.com/v1/send", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey
      },
      body: JSON.stringify({
        from: "Operations <updates@your-verified-domain.com>",
        to,
        subject,
        html,
        text: `Hi ${name},\n\n${message}`
      })
    });

    if (!response.ok) {
      const detail = await response.text();
      console.error("Volanea send failed", response.status, detail);
      // Returning a failure permits Todoist to retry the webhook delivery.
      return res.status(502).json({ error: "Volanea send failed" });
    }

    const result = await response.json();
    console.info("Todoist email accepted", {
      taskId: task.id,
      deliveryId,
      result
    });

    return res.status(200).json({ ok: true });
  }
);

app.listen(process.env.PORT || 3000);

The Volanea call is a standard server-to-server REST request: POST https://api.volanea.com/v1/send, a bearer API key in Authorization, JSON email fields, and an Idempotency-Key header. Before testing, verify the sender domain you use in from; sending from an unverified domain can cause the request to be rejected.

For more endpoint details and setup patterns, consult the Volanea email API reference and setup guides.

Where the Volanea API key belongs

The Volanea API key belongs in the environment or secret store of the middleware that receives Todoist webhooks. Examples include a server environment variable, a platform secret, or a managed secret store attached to a serverless function.

It does not belong in:

  • Todoist task content or descriptions;
  • a Todoist project comment;
  • the webhook URL query string;
  • browser JavaScript;
  • a mobile application bundle;
  • a public Git repository;
  • a shared spreadsheet used to configure automation;
  • a client-visible automation step that reveals request headers.

The reason is simple: a Volanea API key authorizes mail sending. Anyone who obtains it may be able to send messages from your configured account until you revoke or rotate the key. A key in a task is especially risky because tasks can be shared, exported, copied, viewed in notifications, or surfaced through integrations.

Separate Todoist secrets from Volanea secrets

This integration has two separate credentials with different jobs:

  • TODOIST_CLIENT_SECRET verifies that a webhook came from Todoist. Your receiver uses it to reproduce the HMAC signature sent in X-Todoist-Hmac-SHA256.
  • VOLANEA_API_KEY authorizes your receiver to send mail through Volanea.

Do not confuse them or reuse one as the other. Keep both in server-side secrets, rotate them when access changes, and use separate development and production values. If you suspect either secret leaked, revoke or replace it before investigating the rest of the integration.

Idempotency: avoid duplicate email when Todoist retries

A successful API response does not necessarily mean the webhook sender received the response. Consider this common sequence:

  1. Todoist delivers a completed-task webhook.
  2. Your receiver calls Volanea, which accepts the email.
  3. A network interruption occurs before Todoist receives your 200 response.
  4. Todoist retries the same webhook.
  5. Your receiver runs the send logic again.

Without an idempotency layer, the recipient receives two emails. With the same Idempotency-Key, Volanea can safely replay the original send result instead of creating another message during its idempotency window.

The best key is a stable identifier for the actual webhook delivery, such as X-Todoist-Delivery-ID. The example falls back to task ID plus completion timestamp only because a receiver should still have a deterministic strategy when the delivery header is absent. Prefer the explicit delivery ID whenever it is available.

Idempotency is not your only duplicate control

Store your own processed-event record as well. A database table or key-value store can keep a record containing:

  • Todoist delivery ID;
  • Todoist task ID;
  • event name;
  • task completion timestamp;
  • Volanea message identifier or response data;
  • processing status;
  • first-seen and last-seen timestamps.

That record helps with observability beyond the API’s idempotency period. It also lets you choose an intentional business policy. For example, should uncompleting and completing a task again send a new message? Some workflows should allow it; others should require a new task or a separate resend label.

When this breaks

Every integration has two hops: Todoist to your receiver, then your receiver to Volanea. Diagnose them separately. A task could be completed successfully while the webhook never reaches your endpoint; conversely, the webhook could arrive and be authenticated while Volanea rejects the email request.

Todoist retries cause duplicate sends

Symptom: a recipient reports duplicate messages after one task completion.

Likely cause: Todoist retried a delivery because your endpoint returned an error, timed out, or disconnected after Volanea had already accepted the send.

Fix: use the Todoist delivery ID in Idempotency-Key, persist processed delivery IDs, and return a successful response only after your system has safely recorded or queued the work. If your email processing involves slow database lookups, attachments, AI generation, or third-party calls, acknowledge the validated webhook quickly after persisting a durable job, then process that job asynchronously.

Do not solve this by blindly returning 200 before writing anything to durable storage. That eliminates retries but can silently lose email work if the process exits immediately afterward.

The webhook times out

Symptom: Todoist retries, your logs show repeated requests, or no completion email appears even though the task was checked off.

Likely cause: the receiver does too much during the webhook request. Common culprits include a cold serverless start, slow DNS, a blocked outbound request, calling multiple APIs before responding, or waiting for an email delivery event rather than a send acceptance.

Fix: validate the HMAC, parse the minimum required fields, write a job with a unique event key, and return a 2xx response. A worker can call Volanea after that. If you choose the synchronous pattern shown above, keep it lightweight and apply a request timeout to the Volanea call so a stalled connection cannot occupy the webhook endpoint indefinitely.

Required task fields are missing

Symptom: your integration rejects a completed task because there is no description, email, due date, section, or label.

Likely cause: those fields are optional in real Todoist tasks. A user may complete the task on mobile before finishing the description, or the task may have been created through a different integration that does not populate your convention.

Fix: validate required fields explicitly and fail closed. For example, do not send if email-customer is absent or description does not include a valid email: value. Log the task ID and rejection reason, then notify the operator through a separate internal channel if needed. Never guess an external recipient from a task title.

If your configuration relies on functionality that varies by Todoist account type or integration configuration, test it with the same account and permissions used in production. Keep the data contract small: a label, task ID, description field, and completion event are more durable than a complex dependency on every optional task attribute.

Signature verification fails

Symptom: every webhook returns 401 Invalid Todoist signature.

Likely cause: the server calculated the HMAC after JSON parsing or re-serialization, used the wrong Todoist app secret, or compared a transformed payload rather than the raw bytes Todoist signed.

Fix: preserve the raw application/json request body, calculate HMAC-SHA256 with the Todoist developer app’s client secret, Base64 encode it, and use a timing-safe comparison. Do not replace express.raw() with normal JSON middleware on this route unless you separately preserve raw bytes.

Volanea responds with an authentication or sender error

Symptom: your logs show 401, 403, or another non-success response from the Volanea send request.

Likely cause: the API key is missing, revoked, loaded from the wrong environment, or the from identity uses a domain that is not verified in the Volanea account.

Fix: confirm the runtime has the production key, rotate the key if compromise is possible, and use a sender address on an authenticated domain. Keep test and production domains separate so an accidental staging deploy cannot send production-looking email.

The task sends email to the wrong person

Symptom: the API returns success, but the destination was incorrect.

Likely cause: free-form task text was treated as authoritative customer data.

Fix: replace raw task-address parsing with a customer ID lookup when the workflow affects customers, invoices, account data, or regulated information. Where free-form addresses are acceptable, restrict who can add the sending label, validate email syntax, and consider checking addresses before sending with the email address verification tool.

Production design improvements

The direct webhook receiver is the most transparent route because it gives you signature verification, source control, logs, reliable retries, and full control over the Volanea request. It is also a small amount of code to own. For production workflows, add the following controls.

Put a queue between the webhook and the send

A queue is valuable when email volume is meaningful or the receiver does more than one fast API call. The webhook route should authenticate and normalize the event, atomically create a job keyed by the Todoist delivery ID, then return. A worker should perform recipient lookup, content rendering, Volanea send, retry handling, and final logging.

This separation prevents a temporary Volanea or network failure from forcing the webhook handler to hold the request open. It also lets you apply controlled retries on the send side while retaining the same Volanea idempotency key.

Store a business audit trail

Keep enough information to answer operational questions without retaining more personal data than necessary:

  • Which Todoist task started the email?
  • Which Todoist delivery ID was received?
  • Which rule or label allowed the send?
  • Which Volanea response or message ID was returned?
  • Was this the first processing attempt or a retry?
  • Did the send fail because of task input, authentication, sender verification, suppression, or transport failure?

This makes it possible to investigate a customer support issue without searching unstructured server logs.

Use templates for repeatable messages

The inline HTML example is intentionally small. For production notices, use a tested template and send only the variables your middleware owns: recipient name, task title, reference number, and a controlled status message. Templates keep formatting consistent and reduce the chance that an unexpected Todoist description becomes email markup.

A strong rule is to treat the Todoist task as a workflow event and reference, not as the complete email authoring system. The more external-facing the email is, the more the message content should come from versioned templates and trusted application data.

Middleware alternatives: Zapier and Make

If you do not want to run a webhook receiver, a workflow platform can serve as middleware. Todoist has integrations with automation platforms such as Zapier and Make, which can trigger on Todoist task events and make outbound web requests.

The security requirement remains unchanged: the Volanea key must be stored as a protected connection or secret in the automation platform, not copied into a task or exposed to an end user. Confirm that your selected plan supports the exact Todoist trigger, custom headers, JSON request body, error handling, and secret handling you need before relying on it for customer communications.

A no-code route generally looks like this:

  1. Create a Todoist trigger for a newly completed task.
  2. Filter for a specific label or project.
  3. Extract or look up the recipient.
  4. Add a Webhooks/custom-request action.
  5. Configure POST https://api.volanea.com/v1/send.
  6. Set Authorization: Bearer …, Content-Type: application/json, and a stable Idempotency-Key if the platform supports dynamic headers.
  7. Map the Todoist task fields into Volanea’s recipient, subject, HTML, and text fields.

The trade-off is control. A dedicated receiver lets you verify Todoist’s native webhook signature and own the retry/idempotency behavior precisely. A workflow platform reduces code but may limit raw-request signature access, custom retry policies, header mapping, or durable event storage. For customer-facing transactional mail, test retries and failure behavior—not only the happy-path test task.

Conclusion

The dependable way to send email from Todoist is not a marketplace installation. It is an event-driven integration: Todoist emits a task event, a secure server-side receiver decides whether the event qualifies, and Volanea sends the resulting transactional message through its REST API.

Start with one narrow rule: an item:completed event on a task carrying email-customer. Verify Todoist’s HMAC signature using the raw body, store the Volanea key only in server-side secrets, validate every recipient field, and send with an idempotency key derived from the Todoist delivery. Once that path is reliable, add templates, queues, customer-record lookups, and delivery-event monitoring as your workflow grows.

FAQ

Does Volanea have a native Todoist integration?

No. Volanea does not provide a native Todoist app, Todoist Marketplace listing, or installable Todoist plugin for this workflow. Use Todoist webhooks with your own server-side middleware, or use an automation platform that can securely call the Volanea REST API.

What Todoist event should trigger the email?

For a completion-based workflow, use Todoist’s item:completed webhook event. It is a clear event boundary and is usually less error-prone than sending on every task update. Use item:added or item:updated only when your rule includes strict label, project, and duplicate protections.

Can I call Volanea directly from Todoist?

Not safely as a direct webhook-to-email configuration. Todoist sends its webhook payload to your callback URL, while Volanea expects a different request body and bearer authentication. Use middleware to validate the Todoist request, map fields, and keep the Volanea API key secret.

How do I stop Todoist retries from sending duplicate email?

Use a stable idempotency key for the Volanea request—preferably based on Todoist’s delivery ID—and persist the processed event ID in your own data store. This protects against the case where Volanea accepted the email but Todoist did not receive your successful webhook response.

Should I put customer email addresses in Todoist task descriptions?

Only for low-risk, tightly controlled workflows. For customer-facing or sensitive mail, put a customer ID in Todoist and have middleware look up the approved address, name, consent status, and template variables from your own system instead.