{"id":1156,"date":"2026-05-22T13:00:00","date_gmt":"2026-05-22T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1156"},"modified":"2026-03-07T03:34:58","modified_gmt":"2026-03-07T08:34:58","slug":"webhook-email-automation","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/webhook-email-automation\/","title":{"rendered":"Email Automation Triggered by Deep Link Webhooks"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A user clicks your campaign link, installs the app, and creates an account. Three days later, they haven&#39;t opened the app again. If you had an automated email sequence triggered by the install event, that re-engagement email would already be in their inbox.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deep link webhooks turn passive link tracking into active marketing triggers. Instead of querying an analytics dashboard to find users who installed but didn&#39;t return, you let the events drive the workflow. This guide covers how to connect <a href=\"https:\/\/tolinku.com\/features\/webhooks\">Tolinku webhooks<\/a> to email platforms (SendGrid, Mailchimp, and generic SMTP) for automated sequences. For the webhook setup, see the <a href=\"https:\/\/tolinku.com\/blog\/webhook-setup-guide\/\">webhook setup guide<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><img decoding=\"async\" src=\"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/platform-webhooks.png\" alt=\"Tolinku webhook configuration for event notifications\">\n<em>The webhooks page with create form, webhook list, and delivery log.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Which Events Trigger Emails?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Not every webhook event should send an email. Here&#39;s how each event type maps to email use cases:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Event<\/th>\n<th>Email Use Case<\/th>\n<th>Who Receives It<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td><code>install.tracked<\/code><\/td>\n<td>Welcome sequence, onboarding tips<\/td>\n<td>The new user (if email is known)<\/td>\n<\/tr>\n<tr>\n<td><code>referral.created<\/code><\/td>\n<td>&quot;Your friend signed up!&quot; notification<\/td>\n<td>The referrer<\/td>\n<\/tr>\n<tr>\n<td><code>referral.completed<\/code><\/td>\n<td>Reward confirmation, thank-you email<\/td>\n<td>Both referrer and referred user<\/td>\n<\/tr>\n<tr>\n<td><code>deferred_link.claimed<\/code><\/td>\n<td>Personalized onboarding based on original link context<\/td>\n<td>The new user<\/td>\n<\/tr>\n<tr>\n<td><code>link.clicked<\/code><\/td>\n<td>Rarely used for email (too high volume)<\/td>\n<td>N\/A<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The key constraint: you need the recipient&#39;s email address. Webhook events carry attribution data (campaign, platform, token), not user PII. Your receiver needs to look up the email from your user database using context from the event.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Identity Bridge<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tolinku webhook events identify a deep link interaction, not a person. To send an email, you need to bridge from the event to a user record.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are two patterns:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 1: Lookup on Referral Token<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For referral events (<code>referral.created<\/code>, <code>referral.completed<\/code>), the event data includes a token that maps to a referral. Your backend knows which user created that referral link. Query your database to find the referrer&#39;s email.<\/p>\n\n\n\n<pre><code class=\"language-typescript\">async function getEmailForEvent(event: any): Promise&lt;string | null&gt; {\n  const { event: eventType, data } = event;\n\n  if (eventType === &#39;referral.created&#39; || eventType === &#39;referral.completed&#39;) {\n    \/\/ Look up the referrer by the referral token\n    const referrer = await db.query(\n      &#39;SELECT email FROM users WHERE referral_token = $1&#39;,\n      [data.token]\n    );\n    return referrer.rows[0]?.email || null;\n  }\n\n  if (eventType === &#39;install.tracked&#39;) {\n    \/\/ The install event itself doesn&#39;t carry an email.\n    \/\/ Store the attribution data and send the email when the user registers.\n    \/\/ Your registration flow should check for pending attribution and trigger the email.\n    return null;\n  }\n\n  return null;\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Pattern 2: Deferred Email Trigger<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For install events, you typically can&#39;t send an email immediately because you don&#39;t know the user&#39;s email yet. Instead:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Store the webhook event attribution data (campaign, platform, token) in a temporary table<\/li>\n<li>When the user registers and provides their email, match the attribution data<\/li>\n<li>Trigger the appropriate email sequence at that point<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This means the email isn&#39;t truly triggered by the webhook alone. The webhook provides the attribution context; the registration event provides the identity. Together, they trigger a personalized email.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SendGrid Integration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/docs.sendgrid.com\/api-reference\/mail-send\/mail-send\" rel=\"nofollow noopener\" target=\"_blank\">SendGrid&#39;s API<\/a> is straightforward for transactional emails.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Transactional Email on Referral Completion<\/h3>\n\n\n\n<pre><code class=\"language-typescript\">import crypto from &#39;crypto&#39;;\nimport express from &#39;express&#39;;\n\nconst app = express();\napp.use(&#39;\/webhooks&#39;, express.raw({ type: &#39;application\/json&#39; }));\n\nconst WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;\nconst SENDGRID_API_KEY = process.env.SENDGRID_API_KEY!;\n\napp.post(&#39;\/webhooks\/tolinku&#39;, async (req, res) =&gt; {\n  const signature = req.headers[&#39;x-webhook-signature&#39;] as string;\n  const expected = crypto\n    .createHmac(&#39;sha256&#39;, WEBHOOK_SECRET)\n    .update(req.body)\n    .digest(&#39;hex&#39;);\n\n  if (signature !== expected) {\n    return res.status(401).send(&#39;Invalid signature&#39;);\n  }\n\n  res.status(200).send(&#39;OK&#39;);\n\n  const event = JSON.parse(req.body.toString());\n  await handleEvent(event);\n});\n\nasync function handleEvent(event: any) {\n  if (event.event === &#39;referral.completed&#39;) {\n    await sendReferralRewardEmail(event);\n  }\n}\n\nasync function sendReferralRewardEmail(event: any) {\n  const referrerEmail = await getEmailForEvent(event);\n  if (!referrerEmail) return;\n\n  await fetch(&#39;https:\/\/api.sendgrid.com\/v3\/mail\/send&#39;, {\n    method: &#39;POST&#39;,\n    headers: {\n      &#39;Content-Type&#39;: &#39;application\/json&#39;,\n      &#39;Authorization&#39;: `Bearer ${SENDGRID_API_KEY}`,\n    },\n    body: JSON.stringify({\n      personalizations: [\n        {\n          to: [{ email: referrerEmail }],\n          dynamic_template_data: {\n            reward_type: &#39;credit&#39;,\n            reward_amount: &#39;$10&#39;,\n            referred_date: new Date(event.timestamp).toLocaleDateString(),\n          },\n        },\n      ],\n      from: { email: &#39;rewards@example.com&#39;, name: &#39;Your App&#39; },\n      template_id: process.env.SENDGRID_REFERRAL_TEMPLATE_ID,\n    }),\n  });\n\n  console.log(`Referral reward email sent to ${referrerEmail}`);\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Dynamic Templates<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SendGrid&#39;s <a href=\"https:\/\/docs.sendgrid.com\/ui\/sending-email\/how-to-send-an-email-with-dynamic-templates\" rel=\"nofollow noopener\" target=\"_blank\">dynamic templates<\/a> let you design the email in SendGrid&#39;s editor and pass variables from your webhook handler. Create templates for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Referral reward confirmation<\/strong>: &quot;Your friend just signed up! Here&#39;s your $10 credit.&quot;<\/li>\n<li><strong>Welcome email<\/strong>: &quot;Welcome to [App Name]. Here&#39;s how to get started.&quot; (triggered on registration, enriched with attribution data)<\/li>\n<li><strong>Re-engagement<\/strong>: &quot;You installed [App Name] from our [Campaign] campaign. Here are 3 things to try.&quot;<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Mailchimp Integration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/mailchimp.com\/developer\/marketing\/api\/\" rel=\"nofollow noopener\" target=\"_blank\">Mailchimp<\/a> is better suited for marketing sequences than individual transactional emails.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Adding Contacts to an Automation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of sending individual emails, add users to a Mailchimp audience with tags. The tags trigger pre-built automations.<\/p>\n\n\n\n<pre><code class=\"language-typescript\">const MAILCHIMP_API_KEY = process.env.MAILCHIMP_API_KEY!;\nconst MAILCHIMP_SERVER = process.env.MAILCHIMP_SERVER!; \/\/ e.g., &#39;us14&#39;\nconst MAILCHIMP_LIST_ID = process.env.MAILCHIMP_LIST_ID!;\n\nasync function addToMailchimpAudience(\n  email: string,\n  tags: string[],\n  mergeFields: Record&lt;string, string&gt;\n) {\n  const subscriberHash = crypto\n    .createHash(&#39;md5&#39;)\n    .update(email.toLowerCase())\n    .digest(&#39;hex&#39;);\n\n  \/\/ Add or update the contact\n  await fetch(\n    `https:\/\/${MAILCHIMP_SERVER}.api.mailchimp.com\/3.0\/lists\/${MAILCHIMP_LIST_ID}\/members\/${subscriberHash}`,\n    {\n      method: &#39;PUT&#39;,\n      headers: {\n        &#39;Content-Type&#39;: &#39;application\/json&#39;,\n        &#39;Authorization&#39;: `Basic ${Buffer.from(`anystring:${MAILCHIMP_API_KEY}`).toString(&#39;base64&#39;)}`,\n      },\n      body: JSON.stringify({\n        email_address: email,\n        status_if_new: &#39;subscribed&#39;,\n        merge_fields: mergeFields,\n      }),\n    }\n  );\n\n  \/\/ Apply tags\n  await fetch(\n    `https:\/\/${MAILCHIMP_SERVER}.api.mailchimp.com\/3.0\/lists\/${MAILCHIMP_LIST_ID}\/members\/${subscriberHash}\/tags`,\n    {\n      method: &#39;POST&#39;,\n      headers: {\n        &#39;Content-Type&#39;: &#39;application\/json&#39;,\n        &#39;Authorization&#39;: `Basic ${Buffer.from(`anystring:${MAILCHIMP_API_KEY}`).toString(&#39;base64&#39;)}`,\n      },\n      body: JSON.stringify({\n        tags: tags.map(name =&gt; ({ name, status: &#39;active&#39; })),\n      }),\n    }\n  );\n}\n\n\/\/ Usage in webhook handler\nasync function handleEvent(event: any) {\n  if (event.event === &#39;referral.completed&#39;) {\n    const email = await getEmailForEvent(event);\n    if (!email) return;\n\n    await addToMailchimpAudience(email, [&#39;referral-reward&#39;, &#39;active-referrer&#39;], {\n      CAMPAIGN: event.data.campaign || &#39;&#39;,\n      PLATFORM: event.data.platform || &#39;&#39;,\n      REF_DATE: new Date(event.timestamp).toISOString().split(&#39;T&#39;)[0],\n    });\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In Mailchimp, create automations triggered by tags:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Tag <code>referral-reward<\/code> triggers a &quot;Your reward is ready&quot; email sequence<\/li>\n<li>Tag <code>active-referrer<\/code> triggers a &quot;Share again and earn more&quot; follow-up after 7 days<\/li>\n<li>Tag <code>installed-from-campaign<\/code> triggers a welcome sequence with campaign-specific content<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Generic SMTP<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you use a custom email system or a provider without a REST API, send via SMTP using <a href=\"https:\/\/nodemailer.com\/\" rel=\"nofollow noopener\" target=\"_blank\">Nodemailer<\/a>:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">import nodemailer from &#39;nodemailer&#39;;\n\nconst transporter = nodemailer.createTransport({\n  host: process.env.SMTP_HOST,\n  port: parseInt(process.env.SMTP_PORT || &#39;587&#39;),\n  secure: false,\n  auth: {\n    user: process.env.SMTP_USER,\n    pass: process.env.SMTP_PASS,\n  },\n});\n\nasync function sendEmail(to: string, subject: string, html: string) {\n  await transporter.sendMail({\n    from: &#39;&quot;Your App&quot; &lt;notifications@example.com&gt;&#39;,\n    to,\n    subject,\n    html,\n  });\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Preventing Duplicate Emails<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tolinku retries failed webhook deliveries (3 retries at 1 minute, 5 minutes, and 30 minutes). If your receiver was temporarily down and processes the retry, the same event fires your email handler again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Emails are not idempotent: sending the same reward confirmation twice is a bad user experience.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Solution<\/strong>: Track which events you&#39;ve already processed.<\/p>\n\n\n\n<pre><code class=\"language-typescript\">const processedEvents = new Set&lt;string&gt;();\n\nasync function handleEvent(event: any) {\n  \/\/ Generate a dedup key from the event content\n  const eventHash = crypto\n    .createHash(&#39;sha256&#39;)\n    .update(JSON.stringify(event))\n    .digest(&#39;hex&#39;);\n\n  if (processedEvents.has(eventHash)) {\n    console.log(&#39;Duplicate event, skipping email&#39;);\n    return;\n  }\n\n  processedEvents.add(eventHash);\n\n  \/\/ Process the event...\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For production, replace the in-memory Set with a Redis key (with a TTL matching your retry window) or a database table. See the <a href=\"https:\/\/tolinku.com\/blog\/webhook-retry-logic\/\">webhook retry logic guide<\/a> for more on idempotent processing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Email Sequences by Event Type<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here are practical email sequences for each webhook event:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Install Tracked Sequence<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Day 0<\/strong>: Welcome email with getting-started tips (triggered on user registration, enriched with campaign attribution from the webhook)<\/li>\n<li><strong>Day 2<\/strong>: Feature highlight email based on the campaign that drove the install<\/li>\n<li><strong>Day 5<\/strong>: &quot;Need help?&quot; email with support links and onboarding resources<\/li>\n<li><strong>Day 14<\/strong>: Re-engagement email if the user hasn&#39;t returned<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Referral Created Sequence<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Immediate<\/strong>: &quot;Your friend [name] just signed up through your link!&quot;<\/li>\n<li><strong>Day 1<\/strong>: &quot;Here&#39;s what happens next&quot; (explaining the reward criteria)<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Referral Completed Sequence<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Immediate<\/strong>: &quot;You earned [reward]! Here&#39;s how to redeem it.&quot;<\/li>\n<li><strong>Day 3<\/strong>: &quot;Share your link again and earn more rewards.&quot;<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Testing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before connecting to your production email platform:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Use <a href=\"https:\/\/github.com\/mailhog\/MailHog\" rel=\"nofollow noopener\" target=\"_blank\">MailHog<\/a> or <a href=\"https:\/\/mailtrap.io\/\" rel=\"nofollow noopener\" target=\"_blank\">Mailtrap<\/a> as a local email sink during development<\/li>\n<li>Send test webhooks using the Test button in the Tolinku dashboard<\/li>\n<li>Verify your deduplication logic by sending the same event twice<\/li>\n<li>Check that emails render correctly with the dynamic data from webhook payloads<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">See the <a href=\"https:\/\/tolinku.com\/blog\/webhook-testing-tools\/\">webhook testing tools guide<\/a> for more on testing strategies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a no-code alternative, you can connect Tolinku webhooks to email platforms through <a href=\"https:\/\/tolinku.com\/blog\/webhook-zapier-integration\/\">Zapier<\/a> without building a custom receiver.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Trigger automated email sequences when users click deep links, install your app, or complete referrals. Connect Tolinku webhooks to your email platform.<\/p>\n","protected":false},"author":2,"featured_media":1155,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Email Automation Triggered by Deep Link Webhooks","rank_math_description":"Trigger automated email sequences when users click deep links, install your app, or complete referrals. Connect webhooks to SendGrid, Mailchimp, and more.","rank_math_focus_keyword":"webhook email automation","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-webhook-email-automation.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-webhook-email-automation.png","footnotes":""},"categories":[15],"tags":[165,20,82,264,263,110,61],"class_list":["post-1156","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-automation","tag-deep-linking","tag-email-marketing","tag-engineering","tag-integrations","tag-marketing","tag-webhooks"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1156","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1156"}],"version-history":[{"count":2,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1156\/revisions"}],"predecessor-version":[{"id":2261,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1156\/revisions\/2261"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1155"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}