Skip to content
Skip to main content

How to import email signatures via an Agent Account

Users already have email signatures — in Gmail, Outlook, Apple Mail, their phone. But Nylas signatures are stored separately from the provider’s own settings, so when a user starts sending through Nylas, their signature doesn’t come with them. They’d have to recreate it by hand, which nobody wants to do.

A simpler flow: set up an Agent Account at something like [email protected], tell the user to forward any email that has the signature they want, and let your app parse it out and save it via the Signatures API. The user gets their signature imported in seconds without touching an HTML editor.

  1. Provision a dedicated Agent Account for signature imports.
  2. Subscribe to message.created so forwarded emails fire a webhook.
  3. Parse the signature from the forwarded email’s HTML body.
  4. Save it to the target grant via POST /v3/grants/{grant_id}/signatures.
  5. Confirm the import back to the user.

This Agent Account exists only to receive forwarded emails. It doesn’t need to send anything, and you can share one across all your users.

From the Nylas CLI:

nylas agent account create [email protected]

Or through the API:

curl --request POST \
--url "https://api.us.nylas.com/v3/connect/custom" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"provider": "nylas",
"settings": {
"email": "[email protected]"
}
}'

From the Nylas CLI:

nylas webhook create \
--url https://yourapp.example.com/webhooks/signature-import \
--triggers message.created

Or through the API:

curl --request POST \
--url "https://api.us.nylas.com/v3/webhooks" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"trigger_types": ["message.created"],
"webhook_url": "https://yourapp.example.com/webhooks/signature-import",
"description": "Signature import inbox"
}'

3. Parse the signature from the forwarded email

Section titled “3. Parse the signature from the forwarded email”

When a user forwards an email, the forwarded message body contains the original sender’s signature as HTML. The challenge is extracting just the signature block from the rest of the email body.

Email signatures are typically the last structured block before the end of the message or the forwarded-message boundary. Common signals:

  • A -- separator line (the RFC 3676 signature delimiter)
  • A block that contains a phone number, job title, company name, and social links
  • The last <table> or <div> in the body that has styling patterns typical of signatures (small font, gray text, social icons)

A regex-first approach catches the common cases. An LLM handles the rest.

app.post("/webhooks/signature-import", async (req, res) => {
res.status(200).end();
const event = req.body;
if (event.type !== "message.created") return;
const msg = event.data.object;
if (msg.grant_id !== IMPORT_GRANT_ID) return;
// Fetch the full message body.
const full = await nylas.messages.find({
identifier: IMPORT_GRANT_ID,
messageId: msg.id,
});
const body = full.data.body;
// Identify who forwarded this -- that's the user whose grant gets the signature.
const forwarder = msg.from[0].email;
const targetGrant = await db.grants.findByEmail(forwarder);
if (!targetGrant) return; // Unknown user -- ignore.
// Extract the signature.
const signatureHtml = await extractSignature(body);
if (!signatureHtml) return;
// Save it to the user's grant.
await saveSignature(targetGrant.grantId, signatureHtml, forwarder);
});

Most email signatures sit after a common delimiter or inside a predictable HTML structure.

function extractSignatureRegex(html) {
// Pattern 1: RFC 3676 delimiter "-- " followed by the signature block.
const delimiterMatch = html.match(/(?:--|\s*<br|<br[^>]*>\s*--\s*<br)([\s\S]{50,2000}?)(?:<\/div>\s*$|<\/body>|$)/i);
if (delimiterMatch) return delimiterMatch[1].trim();
// Pattern 2: A <table> near the end of the body with typical signature content
// (phone numbers, URLs, small images).
const tables = [...html.matchAll(/<table[^>]*>[\s\S]*?<\/table>/gi)];
if (tables.length > 0) {
const lastTable = tables[tables.length - 1][0];
const hasSignatureSignals =
/\d{3}[\s.-]\d{3,4}[\s.-]\d{4}/.test(lastTable) || // phone number
/linkedin|twitter|x\.com/i.test(lastTable) || // social links
/<img[^>]+(?:logo|photo|headshot)/i.test(lastTable); // profile image
if (hasSignatureSignals) return lastTable;
}
return null; // Fall back to LLM.
}

When regex doesn’t match — creative layouts, image-heavy signatures, unusual formatting — hand the body to an LLM with a focused prompt.

async function extractSignatureLlm(html) {
// Strip to a reasonable length. Signatures are near the end.
const tail = html.slice(-5000);
const response = await llm.chat({
messages: [
{
role: "system",
content:
"You extract email signatures from HTML email bodies. " +
"Return ONLY the signature HTML block -- the part with the sender's name, " +
"title, company, phone, and any social links or logos. " +
"If no signature is present, return exactly: NO_SIGNATURE",
},
{ role: "user", content: tail },
],
});
const result = response.content.trim();
if (result === "NO_SIGNATURE") return null;
return result;
}
async function extractSignature(html) {
return extractSignatureRegex(html) ?? (await extractSignatureLlm(html));
}

Once you have the HTML, create it on the user’s grant via the Signatures API.

async function saveSignature(grantId, signatureHtml, userEmail) {
const signature = await nylas.signatures.create({
identifier: grantId,
requestBody: {
name: "Imported signature",
body: signatureHtml,
},
});
// Optionally notify the user that their signature was imported.
// Store the signature ID so your app can reference it on outbound sends.
await db.userSettings.update(userEmail, {
defaultSignatureId: signature.data.id,
});
}

The signature is now available on that grant. Pass signature_id when sending messages to append it automatically:

curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<TARGET_GRANT_ID>/messages/send" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"to": [{ "email": "[email protected]" }],
"subject": "Hello",
"body": "<p>Message body here.</p>",
"signature_id": "<IMPORTED_SIGNATURE_ID>"
}'

This works on connected grants and Agent Accounts alike. An Agent Account that sends outreach from [email protected] can use a signature imported this way, so its emails look like they come from a real person.

A forwarded email can contain signatures from multiple people in the reply chain. If you only want the most recent one, extract from the top of the forwarded body (before the first ---------- Forwarded message ---------- or From: block).

Many corporate signatures use inline images hosted on the company’s servers. The <img src="..."> URLs in the extracted HTML will keep working as long as those servers are up. If you want the signatures to be self-contained, download the images, host them on your own CDN, and rewrite the URLs before saving.

The Signatures API sanitizes HTML on input, stripping unsafe tags and attributes. You don’t need to sanitize the extracted HTML yourself, but you should still check that the extracted block looks reasonable before saving — a 50 KB block is probably not just a signature.

if (signatureHtml.length > 20_000) {
// Probably extracted too much. Log and skip.
return;
}
  • One import inbox serves all users. You don’t need a separate Agent Account per user. Route by the from address on the forwarded email to match the right grant.
  • The user must be known. Your app needs a mapping from the user’s email address to their Nylas grant_id. If an unknown address forwards a message, log it and ignore it.
  • Signatures are HTML only. The Signatures API stores HTML. Plain-text signatures aren’t directly supported — if the forwarded email has no HTML body, there’s nothing to extract.
  • Each grant supports up to 10 signatures. Check the count before creating a new one. If the user already has 10, update an existing one instead.
  • Tell the user what to forward. In your UI, include instructions like: “Forward any email that has the signature you’d like to use to [email protected].” The simpler the instruction, the more likely they’ll do it.