HubSpot + MailKite
HubSpot's transactional email uses its own SMTP by default. Switch to MailKite to send from your own DKIM-signed domain, with full deliverability control and logs. You can also pipe inbound email back into HubSpot as CRM activities.
What you need
- A verified domain with SPF + DKIM published
- Your API key (
mk_live_…) - A HubSpot account (Free or paid) with Transactional Email add-on (or SMTP access)
Send transactional email via SMTP
In HubSpot, go to Settings → Transactional Email → Sending Domain and add your verified domain. Then configure the SMTP settings:
# HubSpot transactional email SMTP settings
SMTP Server: smtp.mailkite.dev
Port: 587
Username: mailkite
Password: mk_live_...
Requires SSL: true HubSpot's transactional email API can also send directly — but SMTP gives you the same MailKite pipeline (DKIM signing, logging, retries) as every other integration.
When to use SMTP vs HubSpot's API
| Use SMTP when… | Use HubSpot's API when… |
|---|---|
| You want zero config — point and send | You need HubSpot's email tracking (opens, clicks) |
| You're sending from a non-HubSpot app (Laravel, WordPress, etc.) | You want email to appear in HubSpot's timeline automatically |
| You want MailKite's deliverability controls and logs | You're building marketing sequences inside HubSpot |
Receive inbound email → HubSpot CRM
When a customer replies to your email, MailKite can POST the reply to a webhook. This example creates a note on the matching HubSpot contact:
// HubSpot -> MailKite: receive email replies as CRM activities
// Uses HubSpot's CRM API to create a note on the contact
export default {
async fetch(request, env) {
if (request.method !== "POST") return new Response("OK");
const payload = await request.json();
const { from, subject, text } = payload;
// Look up the contact by email in HubSpot
const searchRes = await fetch(
"https://api.hubapi.com/crm/v3/objects/contacts/search",
{
method: "POST",
headers: {
Authorization: "Bearer " + env.HUBSPOT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
filterGroups: [{
filters: [{
propertyName: "email",
operator: "EQ",
value: from,
}],
}],
properties: ["email", "firstname", "lastname"],
}),
}
);
const { results } = await searchRes.json();
if (!results.length) return new Response('{"ok":true}', { status: 200 });
const contactId = results[0].id;
// Create a note on the contact
await fetch(
"https://api.hubapi.com/crm/v3/objects/contacts/" + contactId + "/associations/notes",
{
method: "POST",
headers: {
Authorization: "Bearer " + env.HUBSPOT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
properties: {
hs_note_body: "<b>Inbound email</b><br>Subject: " + subject + "<br><br>" + text,
hs_timestamp: Date.now().toString(),
},
}),
}
);
return new Response('{"ok":true}', { status: 200 });
},
}; Set your domain's webhook to this endpoint. Every inbound email becomes a CRM activity — visible on the contact's timeline.
Troubleshooting
- No SMTP option — HubSpot's SMTP is part of the Transactional Email product. On Free/Starter, you may need the paid add-on or use the MailKite API directly.
- Wrong sender domain — HubSpot requires you to verify the sending domain. Use the same domain you verified in MailKite.
- Emails landing in spam — HubSpot adds its own headers. Switching to MailKite gives you a cleaner header chain since DKIM signs from your domain directly.
- CRM notes not creating — check that the webhook payload includes
fromand that the contact exists in HubSpot. The API returns 200 either way to avoid MailKite retries.
See the SMTP relay docs for connection details, or Send API for programmatic sending.