Skip to content

Commit c063a00

Browse files
fix: use public campaign links and configurable compliance footer (#2624)
* fix: use public campaign links and configurable compliance footer * remove redundant undefined --------- Co-authored-by: Malek Salem <malek.salem.14@gmail.com>
1 parent 7809af3 commit c063a00

File tree

9 files changed

+58
-9
lines changed

9 files changed

+58
-9
lines changed

frontend/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ POSTHOG_INSTANCE_ADDRESS=
44
AVERAGE_EXTRACTION_RATE=50
55
SAAS_SUPABASE_PROJECT_URL=http://127.0.0.1:54321
66
SAAS_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
7+
CAMPAIGN_COMPLIANCE_FOOTER=
78
IMAGE_REVERSE_PROXY=
89
NOMINATIM_URL=https://nominatim.openstreetmap.org/search
910

frontend/nuxt.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default defineNuxtConfig({
5656
// Supabase saas
5757
SAAS_SUPABASE_PROJECT_URL: process.env.SAAS_SUPABASE_PROJECT_URL,
5858
SAAS_SUPABASE_ANON_KEY: process.env.SAAS_SUPABASE_ANON_KEY,
59+
CAMPAIGN_COMPLIANCE_FOOTER: process.env.CAMPAIGN_COMPLIANCE_FOOTER,
5960
IMAGE_REVERSE_PROXY: process.env.IMAGE_REVERSE_PROXY,
6061
// Nominatim
6162
NOMINATIM_URL: process.env.NOMINATIM_URL,

frontend/src/components/campaigns/CampaignComposerDialog.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ const dialogHeader = computed(() =>
365365
366366
const availableSenderEmails = ref<string[]>([]);
367367
const fallbackSenderEmail = ref('');
368+
const runtimeConfig = useRuntimeConfig();
368369
369370
const DEFAULT_PROJECT_URL = 'https://example.com/project';
370371
const DEFAULT_PROJECT_IMAGE_SRC =
@@ -397,6 +398,7 @@ const DEFAULT_BODY_TEXT = () => {
397398
};
398399
399400
const DEFAULT_FOOTER_TEXT = () =>
401+
String(runtimeConfig.public.CAMPAIGN_COMPLIANCE_FOOTER || '').trim() ||
400402
t('default_footer_template', {
401403
ownerEmailToken: '{{ownerEmail}}',
402404
unsubscribeToken: '{{unsubscribeUrl}}',

frontend/src/pages/campaigns.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ onBeforeUnmount(() => {
473473
},
474474
"fr": {
475475
"campaigns": "Campagnes",
476-
"refresh": "Rafraichir",
476+
"refresh": "Rafraîchir",
477477
"no_campaigns": "Aucune campagne",
478478
"delivery": "Livraison",
479479
"opens": "Ouvertures",
@@ -489,11 +489,11 @@ onBeforeUnmount(() => {
489489
"top_clicked_links": "Liens les plus cliqués",
490490
"top_clicked_links_tooltip": "Les clics uniques sont comptés une fois par destinataire et par lien.",
491491
"unique_clicks_count": "{count} clics uniques",
492-
"campaign_sent": "Campagne envoyee",
493-
"campaign_sent_detail": "Votre campagne a ete entierement envoyee.",
492+
"campaign_sent": "Campagne envoyée",
493+
"campaign_sent_detail": "Votre campagne a été entièrement envoyée.",
494494
"campaign_failed": "Campagne échouée",
495495
"campaign_failed_detail": "La campagne n'a pu envoyer aucun email. Vérifiez la configuration d'expédition puis réessayez.",
496-
"see_results": "Voir les resultats",
496+
"see_results": "Voir les résultats",
497497
"load_failed": "Impossible de charger les campagnes",
498498
"cancel": "Annuler",
499499
"stop_campaign": "Stopper",

supabase/functions/.env.dev

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ SMTP_HOST=smtp.example.email
88
SMTP_PORT=587
99
SMTP_USER=your-secret-smtp-username
1010
SMTP_PASS=your-secret-smtp-password
11+
campaign_compliance_footer=
1112

1213
LOGO_URL="https://db.leadminer.io/storage/v1/object/public/templates/LogoWithIcon.png"
1314
FRONTEND_HOST="http://localhost:8082"
14-
SERVER_ENDPOINT = http://localhost:8081 # ( REQUIRED ) URL of the Backend server.
15+
SERVER_ENDPOINT = http://localhost:8081 # ( REQUIRED ) URL of the Backend server.

supabase/functions/.env.prod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ SMTP_HOST=
77
SMTP_PORT=
88
SMTP_USER=
99
SMTP_PASS=
10+
campaign_compliance_footer=
1011

1112
LOGO_URL="https://db.leadminer.io/storage/v1/object/public/templates/LogoWithIcon.png"
1213
FRONTEND_HOST="https://app.leadminer.io"
13-
SERVER_ENDPOINT = http://localhost:8081 # ( REQUIRED ) URL of the Backend server.
14+
SERVER_ENDPOINT = http://localhost:8081 # ( REQUIRED ) URL of the Backend server.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
assertEquals,
3+
assertThrows,
4+
} from "https://deno.land/std@0.224.0/assert/mod.ts";
5+
import { resolvePublicBaseUrl } from "./url.ts";
6+
7+
Deno.test("resolvePublicBaseUrl prefers explicit public URL", () => {
8+
const value = resolvePublicBaseUrl(
9+
"https://db-qa.domain.io/",
10+
"http://kong:8000",
11+
);
12+
assertEquals(value, "https://db-qa.domain.io");
13+
});
14+
15+
Deno.test("resolvePublicBaseUrl falls back to secondary URL", () => {
16+
const value = resolvePublicBaseUrl(undefined, "http://localhost:54321/");
17+
assertEquals(value, "http://localhost:54321");
18+
});
19+
20+
Deno.test("resolvePublicBaseUrl throws when both values are missing", () => {
21+
assertThrows(() => resolvePublicBaseUrl());
22+
});

supabase/functions/_shared/url.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function normalizeBaseUrl(url: string): string {
2+
return url.replace(/\/+$/, "");
3+
}
4+
5+
export function resolvePublicBaseUrl(
6+
explicitPublicUrl?: string,
7+
fallbackUrl?: string,
8+
): string {
9+
const candidate = (explicitPublicUrl || fallbackUrl || "").trim();
10+
if (!candidate) {
11+
throw new Error("Missing public base URL");
12+
}
13+
return normalizeBaseUrl(candidate);
14+
}

supabase/functions/email-campaigns/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import IMAPSettingsDetector from "npm:@ankaboot.io/imap-autoconfig";
33
import corsHeaders from "../_shared/cors.ts";
44
import { verifyServiceRole } from "../_shared/middlewares.ts";
55
import { createSupabaseAdmin, createSupabaseClient } from "../_shared/supabase.ts";
6+
import { resolvePublicBaseUrl } from "../_shared/url.ts";
67
import { sendEmail, verifyTransport } from "./email.ts";
78

89
const functionName = "email-campaigns";
910
const app = new Hono().basePath(`/${functionName}`);
1011

1112
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") as string;
13+
const LEADMINER_PROJECT_URL = Deno.env.get("LEADMINER_PROJECT_URL");
1214
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") as string;
1315
const LEADMINER_HASH_SECRET = Deno.env.get("LEADMINER_HASH_SECRET") as string;
16+
const CAMPAIGN_COMPLIANCE_FOOTER = (Deno.env.get("campaign_compliance_footer") || Deno.env.get("CAMPAIGN_COMPLIANCE_FOOTER") || "").trim();
17+
const PUBLIC_CAMPAIGN_BASE_URL = resolvePublicBaseUrl(LEADMINER_PROJECT_URL || undefined, SUPABASE_URL || undefined);
1418
const SMTP_USER = (Deno.env.get("SMTP_USER") || "").trim().toLowerCase();
1519
const DEFAULT_SENDER_DAILY_LIMIT = 1000;
1620
const MAX_SENDER_DAILY_LIMIT = 2000;
@@ -274,7 +278,7 @@ function toHtmlFromText(template: string): string {
274278
}
275279

276280
function buildUnsubscribeUrl(token: string): string {
277-
return `${SUPABASE_URL}/functions/v1/email-campaigns/unsubscribe/${token}`;
281+
return `${PUBLIC_CAMPAIGN_BASE_URL}/functions/v1/email-campaigns/unsubscribe/${token}`;
278282
}
279283

280284
async function triggerCampaignProcessorFromEdge() {
@@ -293,6 +297,9 @@ async function triggerCampaignProcessorFromEdge() {
293297
}
294298

295299
function defaultFooterTemplate(ownerEmail: string): string {
300+
if (CAMPAIGN_COMPLIANCE_FOOTER) {
301+
return CAMPAIGN_COMPLIANCE_FOOTER;
302+
}
296303
return `${COMPLIANCE_SEPARATOR}\n\nYou received this email because ${ownerEmail} used leadminer.io to extract contacts from their mailbox. Try https://leadminer.io yourself.\n\n${UNSUBSCRIBE_TEXT_SUFFIX}: {{unsubscribeUrl}}`;
297304
}
298305

@@ -742,13 +749,13 @@ async function injectTrackers(
742749
}
743750

744751
const token = await recordClickLink(supabaseAdmin, campaignId, recipientId, originalUrl);
745-
const trackedUrl = `${SUPABASE_URL}/functions/v1/email-campaigns/track/click/${token}`;
752+
const trackedUrl = `${PUBLIC_CAMPAIGN_BASE_URL}/functions/v1/email-campaigns/track/click/${token}`;
746753
updatedHtml = updatedHtml.replace(`href="${originalUrl}"`, `href="${trackedUrl}"`);
747754
}
748755
}
749756

750757
if (trackOpen) {
751-
const pixelUrl = `${SUPABASE_URL}/functions/v1/email-campaigns/track/open/${openToken}`;
758+
const pixelUrl = `${PUBLIC_CAMPAIGN_BASE_URL}/functions/v1/email-campaigns/track/open/${openToken}`;
752759
updatedHtml += `<img src="${pixelUrl}" alt="" width="1" height="1" style="display:none" />`;
753760
}
754761

0 commit comments

Comments
 (0)